Files
store/github-utilities/fetch.sune

1 line
13 KiB
JSON

[{"id":"zp2je1g","name":"Github Fetch","pinned":false,"avatar":"data:image/webp;base64,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","url":"gh://sune-org/store/github-utilities/fetch.sune","updatedAt":1769552466499,"settings":{"model":"","temperature":"","top_p":"","top_k":"","frequency_penalty":"","repetition_penalty":"","min_p":"","top_a":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<div id=\"ghFetchSune\" class=\"p-1.5 bg-slate-100 rounded-lg shadow-sm font-sans\" x-data>\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-1.5\">\n <i data-lucide=\"github\" class=\"h-4 w-4 text-slate-600\"></i>\n <span class=\"text-xs font-medium text-slate-800\">GitHub Fetch</span>\n <span class=\"text-[10px] text-slate-400\">v1.9.7</span>\n </div>\n <div class=\"flex items-center gap-1\">\n <button id=\"ghFetchAllBtn\" type=\"button\" class=\"rounded-md px-2 py-1 text-xs font-semibold bg-slate-800 text-white hover:bg-slate-700 active:scale-95 transition-all\">\n Fetch All\n </button>\n <button type=\"button\" id=\"ghAddRowBtn\" class=\"h-7 w-7 rounded-md bg-slate-200/80 text-slate-600 hover:bg-white active:shadow-inner active:scale-95 flex items-center justify-center transition-all\" title=\"Add URL\">\n <i data-lucide=\"plus\" class=\"h-4 w-4\"></i>\n </button>\n </div>\n </div>\n <div id=\"ghRows\" class=\"mt-2 space-y-1.5\"></div>\n <div id=\"ghStatus\" class=\"mt-1.5 px-1 text-[11px] text-slate-600 min-h-[1em]\"></div>\n</div>\n\n<script>\n(()=>{\n const root=document.getElementById('ghFetchSune'),$=s=>root.querySelector(s);\n if(!root)return;\n const els={rows:$('#ghRows'),add:$('#ghAddRowBtn'),all:$('#ghFetchAllBtn'),status:$('#ghStatus')};\n const cache={};\n const icons=()=>{try{lucide.createIcons({attrs:{'aria-hidden':!0}})}catch(e){}};\n const getKey=()=>'gh_fetch_sune_shared_urls_v1',getIgKey=()=>'gh_fetch_sune_shared_ignores_v1';\n const load=()=>{try{const d=JSON.parse(localStorage.getItem(getKey()));return Array.isArray(d)?d.map(x=>String(x||'')):['']}catch{return['']}};\n const loadIg=()=>{try{const d=JSON.parse(localStorage.getItem(getIgKey()));return Array.isArray(d)?d.map(x=>String(x||'')):[]}catch{return[]}};\n const save=d=>localStorage.setItem(getKey(),JSON.stringify(d.map(u=>(u||'').trim())));\n const saveIg=ig=>localStorage.setItem(getIgKey(),JSON.stringify(ig.map(v=>String(v||''))));\n const setStatus=(el,msg,kind)=>{\n const k={error:'text-red-600',ok:'text-emerald-600',info:el.id==='ghStatus'?'text-slate-600':'text-slate-500'};\n el.textContent=msg||'';el.className=`${el.dataset.baseClass||''} ${k[kind]||k.info}`;\n };\n const setRStatus=(i,m,k='info')=>{const n=$(`[data-role=\"row-status\"][data-index=\"${i}\"]`);if(n)setStatus(n,m,k)};\n const spin=c=>`<svg class=\"animate-spin ${c}\" viewBox=\"0 0 24 24\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" fill=\"none\"></circle><path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4A4 4 0 004 12z\"></path></svg>`;\n\n const ghFetch=async(url,opts={})=>{\n const h={'Accept':'application/vnd.github.v3+json',...opts.headers},t=window.USER?.githubToken;\n if(t)h.Authorization=`token ${t}`;\n const res=await fetch(url,{...opts,headers:h});\n if(!res.ok)throw new Error(`HTTP ${res.status}`);\n return res;\n };\n\n const getInfo=async(o,r)=>{\n const k=`${o}/${r}`;if(cache[k])return cache[k];\n const d=await(await ghFetch(`https://api.github.com/repos/${o}/${r}`)).json();\n return cache[k]=d;\n };\n\n async function parse(u){\n try{\n const lM=u.match(/#L(\\d+)(?:-L(\\d+))?$/i),lines=lM?{start:+lM[1],end:+lM[2]||+lM[1]}:null,uS=u.split('#')[0];\n const url=new URL(uS.includes('://')?uS:`https://github.com/${uS}`);\n let o,r,ref,path,p=url.pathname.split('/').filter(Boolean);\n if(p.length>=2){\n [o,r]=p;\n if(p.length>4&&(p[2]==='blob'||p[2]==='raw')){ref=p[3];path=p.slice(4).join('/')}\n else if(url.hostname==='raw.githubusercontent.com'&&p.length>=3){ref=p[2];path=p.slice(3).join('/')}\n else if(p.length>2){path=p.slice(2).join('/')}\n if(!ref)ref=(await getInfo(o,r)).default_branch;\n return{type:path?'file':'repo',p:{owner:o,repo:r,ref,path,lines}}\n }\n }catch(e){return null}\n }\n\n const isBin=p=>/\\.(png|jpe?g|gif|webp|zip|pdf|exe|dll)$/i.test(p);\n const getLang=p=>(p.split('.').pop()||'').toLowerCase();\n const getFence=s=>'`'.repeat(Math.max(3,1+Math.max(0,...(s.match(/`+/g)||[]).map(x=>x.length))));\n\n const fetchFile=async({owner,repo,ref,path,lines})=>{\n const h=`[${owner}/${repo}@${ref}/${path}](https://github.com/${owner}/${repo}/blob/${ref}/${path})`;\n if(isBin(path))return{md:`${h}\\n\\n*Binary file*`};\n const res=await ghFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${ref}`,{headers:{'Accept':'application/vnd.github.v3.raw'}});\n let c=await res.text();\n if(lines)c=c.replace(/\\r\\n/g,'\\n').split('\\n').slice(lines.start-1,lines.end).join('\\n');\n const f=getFence(c);\n return{md:`${h}\\n\\n${f}${getLang(path)}\\n${c}\\n${f}`};\n };\n\n const matchIgnore=(p,pats)=>{\n return pats.some(raw=>{\n const s=raw.trim();if(!s||s.startsWith('#'))return!1;\n let q=s.replace(/[.+^${}()|[\\]\\\\]/g,'\\\\$&').replace(/\\\\\\*\\\\\\*/g,'.*').replace(/\\\\\\*/g,'[^/]*');\n return new RegExp(s.startsWith('/')?`^${q.slice(2)}`:`(^|/)${q}`).test(p);\n });\n };\n\n const runPool=async(jobs,limit,onProg)=>{\n const out=[];let next=0,active=0;\n return new Promise(res=>{\n const kick=()=>{\n if(next>=jobs.length&&!active)return res(out);\n while(active<limit&&next<jobs.length){\n const i=next++;active++;\n jobs[i]().then(r=>out[i]=r).catch(e=>out[i]={ok:!1}).finally(()=>{active--;onProg?.(i,out[i]);kick()});\n }\n };kick();\n });\n };\n\n const post=async md=>window.USER?.logMany?window.USER.logMany(Array.isArray(md)?md:[md]):window.USER?.log?.(md);\n\n const fetchRepo=async({owner,repo,ref},idx,igStr)=>{\n setRStatus(idx,'Loading tree...','info');\n const {tree}=await(await ghFetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`)).json();\n const pats=(igStr||'').split('\\n').filter(Boolean);\n const targets=tree.filter(n=>n.type==='blob'&&!matchIgnore(n.path,pats));\n if(!targets.length)throw new Error('No files found.');\n let done=0;\n const jobs=targets.map(f=>async()=>{\n try{\n const r=await ghFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${f.path}?ref=${ref}`,{headers:{'Accept':'application/vnd.github.v3.raw'}});\n const c=await r.text(),fnc=getFence(c);\n return{ok:!0,md:`[${owner}/${repo}/${f.path}](https://github.com/${owner}/${repo}/blob/${ref}/${f.path})\\n\\n${fnc}${getLang(f.path)}\\n${c}\\n${fnc}`}\n }catch(e){return{ok:!1}}\n });\n const results=await runPool(jobs,6,()=>setRStatus(idx,`Fetching ${++done}/${targets.length}...`));\n const msgs=results.filter(r=>r?.ok).map(r=>r.md);\n if(msgs.length)await post(msgs);\n return{s:msgs.length,t:targets.length};\n };\n\n const urls=load(),ignores=loadIg();\n const esc=s=>String(s).replace(/[&<>\"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[m]));\n const rowTpl=(i,v='')=>{\n const ig=(ignores[i]||'').trim();\n return`<div><div class=\"flex items-stretch gap-1\"><input data-role=\"url\" data-index=\"${i}\" type=\"text\" placeholder=\"user/repo...\" class=\"flex-1 min-w-0 rounded-md border-0 bg-white shadow-inner px-2.5 py-1.5 text-xs text-slate-800 focus:ring-2 focus:ring-slate-400 transition\" value=\"${esc(v)}\"><button data-role=\"ignore\" data-index=\"${i}\" class=\"shrink-0 rounded-md w-8 bg-slate-200/80 text-slate-600 hover:bg-white flex items-center justify-center transition-all\"><i data-lucide=\"filter\" class=\"h-4 w-4\"></i></button><button data-role=\"fetch-one\" data-index=\"${i}\" class=\"shrink-0 rounded-md px-2.5 text-xs font-semibold bg-slate-800 text-white hover:bg-slate-700 transition-all\">Fetch</button><button data-role=\"remove\" data-index=\"${i}\" class=\"shrink-0 rounded-md w-8 text-slate-500 bg-slate-200/80 hover:bg-red-100 hover:text-red-600 transition-all\"><i data-lucide=\"trash-2\" class=\"h-4 w-4\"></i></button></div><div data-role=\"ignore-preview\" class=\"mt-0.5 px-1 text-[10px] text-slate-400 truncate\">${ig?'Ignore: '+ig:''}</div><div data-role=\"row-status\" data-index=\"${i}\" data-base-class=\"mt-0.5 px-1 text-[11px] min-h-[1em]\" class=\"text-slate-500\"></div></div>`;\n };\n const render=()=>{els.rows.innerHTML=urls.map((u,i)=>rowTpl(i,u)).join('');icons()};\n async function fetchRow(i){\n const inp=$(`input[data-index=\"${i}\"]`),btn=$(`button[data-role=\"fetch-one\"][data-index=\"${i}\"]`);\n if(!inp||!btn)return;setRStatus(i,'');\n const u=inp.value.trim();if(!u)return setRStatus(i,'Empty URL','error');\n const old=btn.innerHTML;btn.innerHTML=spin('h-4 w-4');btn.disabled=inp.disabled=!0;\n try{\n const p=await parse(u);if(!p)throw new Error('Invalid path');\n if(p.type==='file'){const r=await fetchFile(p.p);await post(r.md);setRStatus(i,'Fetched','ok')}\n else{const r=await fetchRepo(p.p,i,ignores[i]);setRStatus(i,`Fetched ${r.s}/${r.t}`,'ok')}\n return!0;\n }catch(e){setRStatus(i,e.message,'error');return!1}\n finally{btn.innerHTML=old;btn.disabled=inp.disabled=!1}\n }\n els.rows.addEventListener('click',async e=>{\n const b=e.target.closest('button[data-role]'),i=+b?.dataset.index;\n if(!b)return;\n if(b.dataset.role==='ignore'){const n=prompt('Ignore patterns:',ignores[i]||'');if(n!==null){ignores[i]=n;saveIg(ignores);render()}}\n else if(b.dataset.role==='fetch-one')fetchRow(i);\n else if(b.dataset.role==='remove'){urls.splice(i,1);ignores.splice(i,1);save(urls);saveIg(ignores);render()}\n });\n els.rows.addEventListener('input',e=>{const n=e.target;if(n.dataset.index){urls[n.dataset.index]=n.value;save(urls)}});\n els.add.onclick=()=>{urls.push('');ignores.push('');save(urls);saveIg(ignores);render()};\n els.all.onclick=async()=>{for(let i=0;i<urls.length;i++)if(urls[i].trim())await fetchRow(i)};\n render();\n})();\n</script>\n","extension_html":"<sune src='https://raw.githubusercontent.com/multipleof4/.sune/refs/heads/main/inline-commit.sune' private></sune>\n<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>\n","hide_composer":false,"include_thoughts":false,"json_output":false,"img_output":false,"aspect_ratio":"1:1","image_size":"1K","ignore_master_prompt":false,"json_schema":"","presence_penalty":0,"max_tokens":0},"storage":{}}]