Tools for the Atmosphere tools.slices.network
quickslice atproto html
at main 168 lines 3.6 kB view raw
1const GRAPHQL_URL = 2 process.env.GRAPHQL_URL || 3 "https://quickslice-production-cc52.up.railway.app/graphql"; 4 5const DOCUMENT_QUERY = ` 6 query GetDocument($handle: String!, $slug: String!) { 7 networkSlicesToolsDocument( 8 first: 1 9 where: { 10 actorHandle: { eq: $handle } 11 slug: { eq: $slug } 12 } 13 ) { 14 edges { 15 node { 16 uri 17 cid 18 did 19 title 20 slug 21 actorHandle 22 blocks 23 appBskyActorProfileByDid { 24 displayName 25 avatar { url } 26 } 27 } 28 } 29 } 30 } 31`; 32 33export interface DocumentBlock { 34 $type: string; 35 text?: string; 36} 37 38export interface Document { 39 uri: string; 40 cid: string; 41 did: string; 42 title: string; 43 slug: string; 44 actorHandle: string; 45 blocks: DocumentBlock[]; 46 appBskyActorProfileByDid?: { 47 displayName?: string; 48 avatar?: { 49 url: string; 50 }; 51 }; 52} 53 54export async function fetchDocument( 55 handle: string, 56 slug: string 57): Promise<Document | null> { 58 const res = await fetch(GRAPHQL_URL, { 59 method: "POST", 60 headers: { "Content-Type": "application/json" }, 61 body: JSON.stringify({ 62 query: DOCUMENT_QUERY, 63 variables: { handle, slug }, 64 }), 65 }); 66 67 const json = await res.json(); 68 if (json.errors) { 69 throw new Error(json.errors[0].message); 70 } 71 72 const edges = json.data?.networkSlicesToolsDocument?.edges; 73 if (!edges || edges.length === 0) { 74 return null; 75 } 76 77 return edges[0].node; 78} 79 80export function extractPreview(blocks: DocumentBlock[], maxLength = 500): string { 81 const textBlocks = blocks.filter( 82 (b) => b.$type === "network.slices.tools.document#paragraph" && b.text 83 ); 84 85 let preview = ""; 86 for (const block of textBlocks) { 87 if (block.text) { 88 preview += block.text + " "; 89 } 90 if (preview.length >= maxLength) break; 91 } 92 93 const trimmed = preview.trim(); 94 if (trimmed.length > maxLength) { 95 return trimmed.substring(0, maxLength) + "..."; 96 } 97 return trimmed; 98} 99 100// Lexicon queries 101const LEXICON_GRAPHQL_URL = 102 process.env.LEXICON_GRAPHQL_URL || 103 "https://quickslice-production-4b57.up.railway.app/graphql"; 104 105const LEXICON_QUERY = ` 106 query GetLexicon($nsid: String!) { 107 comAtprotoLexiconSchema(first: 1, where: { id: { eq: $nsid } }) { 108 edges { 109 node { 110 id 111 description 112 defs 113 actorHandle 114 } 115 } 116 } 117 } 118`; 119 120export interface Lexicon { 121 id: string; 122 description?: string; 123 defs: Record<string, unknown>; 124 actorHandle: string; 125} 126 127export async function fetchLexicon(nsid: string): Promise<Lexicon | null> { 128 const res = await fetch(LEXICON_GRAPHQL_URL, { 129 method: "POST", 130 headers: { "Content-Type": "application/json" }, 131 body: JSON.stringify({ 132 query: LEXICON_QUERY, 133 variables: { nsid }, 134 }), 135 }); 136 137 const json = await res.json(); 138 if (json.errors) { 139 throw new Error(json.errors[0].message); 140 } 141 142 const edges = json.data?.comAtprotoLexiconSchema?.edges; 143 if (!edges || edges.length === 0) { 144 return null; 145 } 146 147 return edges[0].node; 148} 149 150export function getLexiconType(lexicon: Lexicon): string { 151 const main = lexicon.defs?.main as Record<string, unknown> | undefined; 152 if (main?.type && typeof main.type === "string") { 153 return main.type; 154 } 155 return "unknown"; 156} 157 158export function getLexiconPreviewDef(lexicon: Lexicon): unknown { 159 // Prefer defs.main, otherwise use first def 160 if (lexicon.defs?.main) { 161 return lexicon.defs.main; 162 } 163 const keys = Object.keys(lexicon.defs || {}); 164 if (keys.length > 0) { 165 return lexicon.defs[keys[0]]; 166 } 167 return {}; 168}