From f848154c077bbc69088e675534ecb7818668da19 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Sun, 5 Apr 2026 18:20:11 -0400 Subject: [PATCH] v0.4.2 --- pyproject.toml | 2 +- uv.lock | 2 +- web/static/app.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cd83a52..dd9903d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "atbbs" -version = "0.4.1" +version = "0.4.2" description = "Decentralized bulletin boards on atproto." readme = "README.md" requires-python = ">=3.14" diff --git a/uv.lock b/uv.lock index ac31175..0217161 100644 --- a/uv.lock +++ b/uv.lock @@ -97,7 +97,7 @@ wheels = [ [[package]] name = "atbbs" -version = "0.4.1" +version = "0.4.2" source = { editable = "." } dependencies = [ { name = "aiohttp" }, diff --git a/web/static/app.js b/web/static/app.js index c359665..3b2c18c 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -10,4 +10,4 @@ ${c}

${d(e.body)}

${s} - `}function C(){let e=m("replies","threadDid"),t=m("replies","threadTid"),n=m("replies","handle"),r=m("replies","userDid"),o=m("replies","sysopDid");document.getElementById("quote-clear")?.addEventListener("click",A),document.getElementById("replies")?.addEventListener("click",a=>{let i=a.target.closest(".quote-btn");i&&U(i.dataset.uri,i.dataset.handle)}),$({fetchUrl:a=>{let i=`/api/replies/${e}/${t}?handle=${encodeURIComponent(n)}`;return a&&(i+=`&cursor=${encodeURIComponent(a)}`),i},containerId:"replies",loadingId:"replies-loading",nextContainerId:"replies-next",loadMoreId:"load-more",dataKey:"replies",emptyMessage:r?void 0:"No replies yet.",onData:a=>{let i=a.replies;for(let l of i)R[l.uri]=l},renderItem:a=>P(a,n,e,t,r,o)})}var H="https://slingshot.microcosm.blue/xrpc",S="https://constellation.microcosm.blue/xrpc";function p(e){let t=e.split("/");return{did:t[2],collection:t[3],rkey:t[4]}}async function N(e){return y(`${H}/blue.microcosm.identity.resolveMiniDoc?identifier=${encodeURIComponent(e)}`)}async function v(e){let t=[...new Set(e)],n=await Promise.allSettled(t.map(N)),r={};for(let o of n)o.status==="fulfilled"&&(r[o.value.did]=o.value);return r}async function j(e,t,n){return y(`${H}/com.atproto.repo.getRecord?repo=${encodeURIComponent(e)}&collection=${encodeURIComponent(t)}&rkey=${encodeURIComponent(n)}`)}async function B(e){return(await Promise.allSettled(e.map(n=>j(n.did,n.collection,n.rkey)))).filter(n=>n.status==="fulfilled").map(n=>n.value)}async function k(e,t,n=25){return y(`${S}/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(e)}&source=${encodeURIComponent(t)}&limit=${n}`)}async function E(e,t,n,r=20){try{return(await y(`${e}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(t)}&collection=${encodeURIComponent(n)}&limit=${r}`)).records}catch{return[]}}var O="xyz.atboards.thread",L="xyz.atboards.reply",D=20,q=25;function F(){let e=["inbox","bbs"];document.querySelectorAll(".tab-btn[data-tab]").forEach(t=>{t.addEventListener("click",()=>{let n=t.dataset.tab;document.querySelectorAll(".tab-btn").forEach(r=>{r.classList.remove("text-neutral-200","border-neutral-200"),r.classList.add("text-neutral-500","border-transparent")}),t.classList.remove("text-neutral-500","border-transparent"),t.classList.add("text-neutral-200","border-neutral-200");for(let r of e)document.getElementById(`panel-${r}`)?.classList.toggle("hidden",r!==n)})})}function K(e,t){let{did:n,rkey:r}=p(e.thread_uri),o=document.createElement("a");o.href=`/bbs/${t}/thread/${n}/${r}`,o.className="block border border-neutral-800/50 rounded p-4 mb-2 hover:bg-neutral-900";let a=e.type==="quote"?"quoted your reply":"on: "+d(e.thread_title);return o.innerHTML=`
${d(e.handle)}${x(e.created_at)}

${a}

${d(e.body)}

`,o}async function J(e,t,n){let r=e.uri,o=e.value.title??"",a=e.value.board??"",i=a?p(a).did:t,l=n[i]?.handle??"";try{let u=await k(r,`${L}:subject`,q);if(!u.records.length)return[];let b=(await B(u.records)).filter(s=>p(s.uri).did!==t);if(!b.length)return[];let h=b.map(s=>p(s.uri).did),g=await v(h);return b.filter(s=>p(s.uri).did in g).map(s=>({type:"reply",thread_title:o,thread_uri:r,handle:g[p(s.uri).did].handle,body:(s.value.body??"").substring(0,200),created_at:s.value.createdAt??"",bbs_handle:l}))}catch{return[]}}async function z(e,t){let n=e.uri,r=e.value.subject??"";try{let o=await k(n,`${L}:quote`,q);if(!o.records.length)return[];let i=(await B(o.records)).filter(c=>p(c.uri).did!==t);if(!i.length)return[];let l=i.map(c=>p(c.uri).did),u=await v(l);return i.filter(c=>p(c.uri).did in u).map(c=>({type:"quote",thread_title:"",thread_uri:r,handle:u[p(c.uri).did].handle,body:(c.value.body??"").substring(0,200),created_at:c.value.createdAt??"",bbs_handle:""}))}catch{return[]}}async function Q(e,t,n){let r=document.getElementById("inbox"),o=document.getElementById("inbox-loading");try{let[a,i]=await Promise.all([E(t,e,O,D),E(t,e,L,D)]),l=[...new Set(a.map(s=>s.value.board??"").filter(Boolean).map(s=>p(s).did))],u=l.length?await v(l):{},b=(await Promise.all([...a.map(s=>J(s,e,u)),...i.map(s=>z(s,e))])).flat(),h=new Map;for(let s of b){let f=s.handle+s.body+s.created_at;(!h.has(f)||s.type==="quote")&&h.set(f,s)}let g=[...h.values()].sort((s,f)=>f.created_at.localeCompare(s.created_at));if(o&&o.remove(),g.length===0){r.innerHTML='

No messages yet.

';return}for(let s of g.slice(0,50))r.appendChild(K(s,n))}catch{o&&(o.textContent="Failed to fetch messages.")}}function _(){F();let e=m("inbox","handle"),t=m("inbox","did"),n=m("inbox","pds");t&&n&&Q(t,n,e)}T();document.getElementById("handle-form")&&w();document.getElementById("threads")&&M();document.getElementById("replies")&&C();document.getElementById("inbox")&&_();})(); + `}function C(){let e=m("replies","threadDid"),t=m("replies","threadTid"),n=m("replies","handle"),r=m("replies","userDid"),o=m("replies","sysopDid");document.getElementById("quote-clear")?.addEventListener("click",A),document.getElementById("replies")?.addEventListener("click",a=>{let i=a.target.closest(".quote-btn");i&&U(i.dataset.uri,i.dataset.handle)}),$({fetchUrl:a=>{let i=`/api/replies/${e}/${t}?handle=${encodeURIComponent(n)}`;return a&&(i+=`&cursor=${encodeURIComponent(a)}`),i},containerId:"replies",loadingId:"replies-loading",nextContainerId:"replies-next",loadMoreId:"load-more",dataKey:"replies",emptyMessage:r?void 0:"No replies yet.",onData:a=>{let i=a.replies;for(let l of i)R[l.uri]=l},renderItem:a=>P(a,n,e,t,r,o)})}var H="https://slingshot.microcosm.blue/xrpc",S="https://constellation.microcosm.blue/xrpc";function p(e){let t=e.split("/");return{did:t[2],collection:t[3],rkey:t[4]}}async function N(e){return y(`${H}/blue.microcosm.identity.resolveMiniDoc?identifier=${encodeURIComponent(e)}`)}async function v(e){let t=[...new Set(e)],n=await Promise.allSettled(t.map(N)),r={};for(let o of n)o.status==="fulfilled"&&(r[o.value.did]=o.value);return r}async function j(e,t,n){return y(`${H}/com.atproto.repo.getRecord?repo=${encodeURIComponent(e)}&collection=${encodeURIComponent(t)}&rkey=${encodeURIComponent(n)}`)}async function B(e){return(await Promise.allSettled(e.map(n=>j(n.did,n.collection,n.rkey)))).filter(n=>n.status==="fulfilled").map(n=>n.value)}async function k(e,t,n=25){return y(`${S}/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(e)}&source=${encodeURIComponent(t)}&limit=${n}`)}async function E(e,t,n,r=20){try{return(await y(`${e}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(t)}&collection=${encodeURIComponent(n)}&limit=${r}`)).records}catch{return[]}}var O="xyz.atboards.thread",L="xyz.atboards.reply",D=50,q=50;function F(){let e=["inbox","bbs"];document.querySelectorAll(".tab-btn[data-tab]").forEach(t=>{t.addEventListener("click",()=>{let n=t.dataset.tab;document.querySelectorAll(".tab-btn").forEach(r=>{r.classList.remove("text-neutral-200","border-neutral-200"),r.classList.add("text-neutral-500","border-transparent")}),t.classList.remove("text-neutral-500","border-transparent"),t.classList.add("text-neutral-200","border-neutral-200");for(let r of e)document.getElementById(`panel-${r}`)?.classList.toggle("hidden",r!==n)})})}function K(e,t){let{did:n,rkey:r}=p(e.thread_uri),o=document.createElement("a");o.href=`/bbs/${t}/thread/${n}/${r}`,o.className="block border border-neutral-800/50 rounded p-4 mb-2 hover:bg-neutral-900";let a=e.type==="quote"?"quoted your reply":"on: "+d(e.thread_title);return o.innerHTML=`
${d(e.handle)}${x(e.created_at)}

${a}

${d(e.body)}

`,o}async function J(e,t,n){let r=e.uri,o=e.value.title??"",a=e.value.board??"",i=a?p(a).did:t,l=n[i]?.handle??"";try{let u=await k(r,`${L}:subject`,q);if(!u.records.length)return[];let b=(await B(u.records)).filter(s=>p(s.uri).did!==t);if(!b.length)return[];let h=b.map(s=>p(s.uri).did),g=await v(h);return b.filter(s=>p(s.uri).did in g).map(s=>({type:"reply",thread_title:o,thread_uri:r,handle:g[p(s.uri).did].handle,body:(s.value.body??"").substring(0,200),created_at:s.value.createdAt??"",bbs_handle:l}))}catch{return[]}}async function z(e,t){let n=e.uri,r=e.value.subject??"";try{let o=await k(n,`${L}:quote`,q);if(!o.records.length)return[];let i=(await B(o.records)).filter(c=>p(c.uri).did!==t);if(!i.length)return[];let l=i.map(c=>p(c.uri).did),u=await v(l);return i.filter(c=>p(c.uri).did in u).map(c=>({type:"quote",thread_title:"",thread_uri:r,handle:u[p(c.uri).did].handle,body:(c.value.body??"").substring(0,200),created_at:c.value.createdAt??"",bbs_handle:""}))}catch{return[]}}async function Q(e,t,n){let r=document.getElementById("inbox"),o=document.getElementById("inbox-loading");try{let[a,i]=await Promise.all([E(t,e,O,D),E(t,e,L,D)]),l=[...new Set(a.map(s=>s.value.board??"").filter(Boolean).map(s=>p(s).did))],u=l.length?await v(l):{},b=(await Promise.all([...a.map(s=>J(s,e,u)),...i.map(s=>z(s,e))])).flat(),h=new Map;for(let s of b){let f=s.handle+s.body+s.created_at;(!h.has(f)||s.type==="quote")&&h.set(f,s)}let g=[...h.values()].sort((s,f)=>f.created_at.localeCompare(s.created_at));if(o&&o.remove(),g.length===0){r.innerHTML='

No messages yet.

';return}for(let s of g.slice(0,50))r.appendChild(K(s,n))}catch{o&&(o.textContent="Failed to fetch messages.")}}function _(){F();let e=m("inbox","handle"),t=m("inbox","did"),n=m("inbox","pds");t&&n&&Q(t,n,e)}T();document.getElementById("handle-form")&&w();document.getElementById("threads")&&M();document.getElementById("replies")&&C();document.getElementById("inbox")&&_();})(); -- 2.51.2