Tools for the Atmosphere
tools.slices.network
quickslice
atproto
html
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}