an attempt to make a lightweight, easily self-hostable, scoped bluesky appview

another step towards something (goodbye xrpc server)

rimar1337 e4e7c8de 21ad9b4d

Changed files
+655 -66
index
utils
+38 -18
index/spacedust.ts
··· 1 - import { handleIndex, spacedustManager } from "../main.ts"; 1 + import { db, handleIndex, spacedustManager } from "../main.ts"; 2 2 import { parseAtUri } from "../utils/aturi.ts"; 3 3 import { resolveRecordFromURI } from "../utils/records.ts"; 4 4 ··· 59 59 console.log("Received Spacedust message: ", msg); 60 60 61 61 const op = msg.link.operation; 62 - // @ts-expect-error i really should enforce some smarter types for this 63 - const doer = parseAtUri(msg.link.source_record).did; 64 - if (!doer) return; 65 - const rev = msg.link.source_rev; 62 + const srcdid = parseAtUri(msg.link.source_record)?.did; 63 + const srccol = parseAtUri(msg.link.source_record)?.collection; 64 + if (!srcdid || !srccol) return; 65 + const subject = msg.link.subject; 66 + const subdid = parseAtUri(subject)?.did; 67 + const subscol = parseAtUri(subject)?.collection; 68 + if (!subdid || !subscol) return; 69 + //const rev = msg.link.source_rev; 66 70 const aturi = msg.link.source_record; 67 - const value = await resolveRecordFromURI({uri: msg.link.source_record}); 68 - 69 - const subject = msg.link.subject; 70 - 71 - if (!value) return; 71 + //const value = await resolveRecordFromURI({uri: msg.link.source_record}); 72 + db.exec(` 73 + INSERT INTO backlink_skeleton ( 74 + srcuri, 75 + srcdid, 76 + srcfield, 77 + srccol, 78 + suburi, 79 + subdid, 80 + subcol 81 + ) VALUES ( 82 + '${aturi}', 83 + '${srcdid}', 84 + '${srccol}', 85 + '${msg.link.source}', 86 + '${subject}', 87 + '${subdid}', 88 + '${subscol}' 89 + ); 90 + `); 91 + //if (!value) return; 72 92 73 - handleIndex({ 74 - op, 75 - doer, 76 - rev, 77 - aturi, 78 - value, 79 - indexsrc: "spacedust", 80 - }) 93 + // handleIndex({ 94 + // op, 95 + // doer, 96 + // rev, 97 + // aturi, 98 + // value, 99 + // indexsrc: "spacedust", 100 + // }) 81 101 return; 82 102 83 103 // switch (msg.link.source) {
+562 -35
main.ts
··· 5 5 import { handleSpacedust, startSpacedust } from "./index/spacedust.ts"; 6 6 import { handleJetstream, startJetstream } from "./index/jetstream.ts"; 7 7 import { Database } from "jsr:@db/sqlite@0.11"; 8 - import express from "npm:express"; 9 - import { createServer } from "./xrpc/index.ts"; 8 + //import express from "npm:express"; 9 + //import { createServer } from "./xrpc/index.ts"; 10 10 import { indexHandlerContext } from "./index/types.ts"; 11 - import * as XRPCTypes from "./utils/xrpc.ts" 11 + import * as XRPCTypes from "./utils/xrpc.ts"; 12 12 import { didDocument } from "./utils/diddoc.ts"; 13 13 14 14 // ------------------------------------------ ··· 33 33 //keyCacheTTL: 10 * 60 * 1000, 34 34 }); 35 35 36 - const app = express(); 37 - const server = createServer(); 38 - app.use(server.xrpc.router); 39 - app.listen(3768); 36 + // ------------------------------------------ 37 + // XRPC Method Implementations 38 + // ------------------------------------------ 39 + 40 + // begin the hell of implementing api requests and incoming records 41 + //const seenStrings = new Set<string>(); 42 + 43 + let preferences: any = undefined; 44 + Deno.serve({ port: 3768 }, async (req: Request): Promise<Response> => { 45 + const url = new URL(req.url); 46 + const pathname = new URL(req.url).pathname; 47 + let reqBody: undefined | string; 48 + let jsonbody: undefined | Record<string, unknown>; 49 + if (req.body) { 50 + const body = await req.json(); 51 + jsonbody = body; 52 + console.log( 53 + `called at euh reqreqreqreq: ${pathname}\n\n${JSON.stringify(body)}` 54 + ); 55 + reqBody = JSON.stringify(body, null, 2); 56 + } 57 + if (pathname === "/.well-known/did.json") { 58 + return new Response(JSON.stringify(didDocument), { 59 + headers: withCors({ "Content-Type": "application/json" }), 60 + }); 61 + } 62 + if (pathname === "/health") { 63 + return new Response("OK", { 64 + status: 200, 65 + headers: withCors({ 66 + "Content-Type": "text/plain", 67 + }), 68 + }); 69 + } 70 + 71 + // if (seenStrings.has(url.hash)) { 72 + // // The string has been seen before 73 + // seenStrings.delete(url.hash); 74 + // return new Response("OK", { 75 + // status: 204, 76 + // headers: withCors({ 77 + // "Content-Type": "text/plain", 78 + // }), 79 + // }); 80 + // } 81 + // seenStrings.add(url.hash); 82 + //const reqBody = req.body ? await req.text() : null; 83 + 84 + console.log("→ Path:", pathname); 85 + console.log("→ Auth:", req.headers.get("authorization")); 86 + console.log("→ Body:", reqBody); 87 + 88 + const bskyUrl = `https://api.bsky.app${pathname}${url.search}`; 89 + const hasAuth = req.headers.has("authorization"); 90 + const xrpcMethod = pathname.startsWith("/xrpc/") 91 + ? pathname.slice("/xrpc/".length) 92 + : null; 93 + 94 + //if (xrpcMethod !== 'app.bsky.actor.getPreferences' && xrpcMethod !== 'app.bsky.notification.listNotifications') { 95 + if ( 96 + (!hasAuth || 97 + xrpcMethod === "app.bsky.labeler.getServices" || 98 + xrpcMethod === "app.bsky.unspecced.getConfig") && 99 + xrpcMethod !== "app.bsky.notification.putPreferences" 100 + ) { 101 + const proxyHeaders = new Headers(req.headers); 102 + 103 + // Remove Authorization and set browser-like User-Agent 104 + proxyHeaders.delete("authorization"); 105 + proxyHeaders.delete("Access-Control-Allow-Origin"), 106 + proxyHeaders.set( 107 + "user-agent", 108 + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36" 109 + ); 110 + proxyHeaders.set("Access-Control-Allow-Origin", "*"); 111 + 112 + const proxyRes = await fetch(bskyUrl, { 113 + method: req.method, 114 + headers: proxyHeaders, 115 + body: ["GET", "HEAD"].includes(req.method.toUpperCase()) 116 + ? undefined 117 + : reqBody, 118 + }); 119 + 120 + const resBody = await proxyRes.text(); 121 + 122 + console.log( 123 + "← Response:", 124 + JSON.stringify(await JSON.parse(resBody), null, 2) 125 + ); 126 + 127 + return new Response(resBody, { 128 + status: proxyRes.status, 129 + headers: proxyRes.headers, 130 + }); 131 + } 132 + 133 + const authDID = "did:plc:mn45tewwnse5btfftvd3powc"; //getAuthenticatedDid(req); 134 + 135 + const jsonUntyped = jsonbody; 136 + 137 + switch (xrpcMethod) { 138 + case "app.bsky.actor.getPreferences": { 139 + const jsonTyped = 140 + jsonUntyped as XRPCTypes.AppBskyActorGetPreferences.QueryParams; 141 + // if (!(await authDID)) 142 + // return new Response("Unauthorized", { status: 401 }); 143 + 144 + const response: XRPCTypes.AppBskyActorGetPreferences.OutputSchema = { 145 + preferences: [ 146 + { 147 + $type: "app.bsky.actor.defs#savedFeedsPrefV2", 148 + items: [ 149 + { 150 + $type: "app.bsky.actor.defs#savedFeed", 151 + id: "3l6wlykrwdk2w", 152 + type: "feed", 153 + value: 154 + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 155 + pinned: true, 156 + }, 157 + ], 158 + }, 159 + { 160 + $type: "app.bsky.actor.defs#savedFeedsPref", 161 + pinned: [ 162 + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 163 + ], 164 + saved: [ 165 + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 166 + ], 167 + }, 168 + { 169 + $type: "app.bsky.actor.defs#bskyAppStatePref", 170 + nuxs: [ 171 + { 172 + $type: "app.bsky.actor.defs#nux", 173 + id: "TenMillionDialog", 174 + completed: true, 175 + }, 176 + { id: "NeueTypography", completed: true }, 177 + { id: "NeueChar", completed: true }, 178 + { id: "InitialVerificationAnnouncement", completed: true }, 179 + { id: "ActivitySubscriptions", completed: true }, 180 + { id: "PolicyUpdate202508", completed: true }, 181 + ], 182 + }, 183 + ], 184 + }; 185 + // { 186 + // preferences: [ 187 + // { 188 + // $type: "app.bsky.actor.defs#savedFeedsPref", 189 + // pinned: [""], 190 + // saved: [""], 191 + // }, 192 + // ], 193 + // }; 194 + 195 + if (preferences === undefined) { 196 + preferences = response.preferences; 197 + } 198 + 199 + const newprefswowowowow: XRPCTypes.AppBskyActorGetPreferences.OutputSchema = 200 + { 201 + preferences: preferences, 202 + }; 203 + 204 + return new Response(JSON.stringify(newprefswowowowow), { 205 + headers: withCors({ "Content-Type": "application/json" }), 206 + }); 207 + } 208 + case "app.bsky.actor.getProfile": { 209 + const jsonTyped = 210 + jsonUntyped as XRPCTypes.AppBskyActorGetProfile.QueryParams; 211 + 212 + const response: XRPCTypes.AppBskyActorGetProfile.OutputSchema = { 213 + $type: "app.bsky.actor.defs#profileViewDetailed", 214 + did: "did:plc:mn45tewwnse5btfftvd3powc", 215 + handle: "whey.party", 216 + displayName: "Whey!?@??#?", 217 + description: "idiot piece of shit", 218 + avatar: 219 + "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:mn45tewwnse5btfftvd3powc/bafkreid4nhd5pdbzqshkcfxwwcpfz4a5xk2n4gk5truu6hyfk6abynpaze@jpeg", 220 + associated: { 221 + $type: "app.bsky.actor.defs#profileAssociated", 222 + lists: 2, 223 + feedgens: 4, 224 + starterPacks: 6, 225 + labeler: false, 226 + chat: { 227 + $type: "app.bsky.actor.defs#profileAssociatedChat", 228 + allowIncoming: "all", 229 + }, 230 + activitySubscription: { 231 + $type: "app.bsky.actor.defs#profileAssociatedActivitySubscription", 232 + allowSubscriptions: "followers", 233 + }, 234 + }, 235 + indexedAt: "2024-10-23T08:55:16.641Z", 236 + createdAt: "2024-10-23T08:55:16.641Z", 237 + banner: 238 + "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:mn45tewwnse5btfftvd3powc/bafkreibqkz2eq3wzpqh44vuicbezckeo6i4g6m66v5ifek5hc5frc6ihdq@jpeg", 239 + followersCount: 69420, 240 + followsCount: 69420, 241 + postsCount: 69420, 242 + //associated?: ProfileAssociated 243 + //joinedViaStarterPack?: AppBskyGraphDefs.StarterPackViewBasic 244 + //indexedAt?: string 245 + //createdAt?: string 246 + viewer: { 247 + $type: "app.bsky.actor.defs#viewerState", 248 + muted: false, 249 + //mutedByList: AppBskyGraphDefs.ListViewBasic 250 + blockedBy: false, 251 + //blocking?: string 252 + //blockingByList?: AppBskyGraphDefs.ListViewBasic 253 + //following?: string 254 + //followedBy?: string 255 + //knownFollowers?: KnownFollowers 256 + activitySubscription: { 257 + $type: "app.bsky.notification.defs#activitySubscription", 258 + post: true, 259 + reply: true, 260 + }, 261 + }, 262 + labels: [ 263 + { 264 + $type: "com.atproto.label.defs#label", 265 + /** The AT Protocol version of the label object. */ 266 + ver: 1, 267 + /** DID of the actor who created this label. */ 268 + src: "did:plc:ar7c4by46qjdydhdevvrndac", 269 + /** AT URI of the record, repository (account), or other resource that this label applies to. */ 270 + uri: "at://did:plc:ar7c4by46qjdydhdevvrndac", 271 + /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 272 + //cid?: string 273 + /** The short string name of the value or type of this label. */ 274 + val: "idiot", 275 + /** If true, this is a negation label, overwriting a previous label. */ 276 + //neg?: boolean 277 + /** Timestamp when this label was created. */ 278 + cts: "2024-10-23T08:55:16.641Z", 279 + /** Timestamp at which this label expires (no longer applies). */ 280 + //exp?: string 281 + /** Signature of dag-cbor encoded label. */ 282 + //sig?: Uint8Array 283 + }, 284 + ], 285 + pinnedPost: { 286 + $type: "com.atproto.repo.strongRef", 287 + uri: "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h", 288 + cid: "bafyreie44fqjarvwv3n3se6fhpf2mvodlguiwahqh7ugm3nyyujlmd36ce", 289 + }, 290 + verification: { 291 + $type: "app.bsky.actor.defs#verificationState", 292 + /** All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included. */ 293 + verifications: [ 294 + { 295 + $type: "app.bsky.actor.defs#verificationView", 296 + /** The user who issued this verification. */ 297 + issuer: "did:plc:ar7c4by46qjdydhdevvrndac", 298 + /** The AT-URI of the verification record. */ 299 + uri: "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h", 300 + /** True if the verification passes validation, otherwise false. */ 301 + isValid: true, 302 + /** Timestamp when the verification was created. */ 303 + createdAt: "2024-10-23T08:55:16.641Z", 304 + }, 305 + ], 306 + /** The user's status as a verified account. */ 307 + verifiedStatus: "valid", 308 + /** The user's status as a trusted verifier. */ 309 + trustedVerifierStatus: "valid", 310 + }, 311 + status: { 312 + $type: "app.bsky.actor.defs#statusView", 313 + /** The status for the account. */ 314 + status: "app.bsky.actor.status#live", 315 + record: { 316 + $type: "app.bsky.graph.verification", 317 + createdAt: "2025-05-02T18:12:17.199Z", 318 + displayName: "teq (lowercase)", 319 + handle: "quilling.dev", 320 + subject: "did:plc:jrtgsidnmxaen4offglr5lsh", 321 + }, 322 + //embed?: $Typed<AppBskyEmbedExternal.View> | { $type: string } 323 + /** The date when this status will expire. The application might choose to no longer return the status after expiration. */ 324 + expiresAt: "2028-10-23T08:55:16.641Z", 325 + /** True if the status is not expired, false if it is expired. Only present if expiration was set. */ 326 + isActive: true, 327 + }, 328 + }; 329 + 330 + return new Response(JSON.stringify(response), { 331 + headers: withCors({ "Content-Type": "application/json" }), 332 + }); 333 + } 334 + case "app.bsky.actor.getProfiles": { 335 + const jsonTyped = 336 + jsonUntyped as XRPCTypes.AppBskyActorGetProfiles.QueryParams; 337 + 338 + const response: XRPCTypes.AppBskyActorGetProfiles.OutputSchema = { 339 + profiles: [ 340 + { 341 + $type: "app.bsky.actor.defs#profileViewDetailed", 342 + did: "did:plc:mn45tewwnse5btfftvd3powc", 343 + handle: "whey.party", 344 + displayName: "Whey!?@??#?", 345 + description: "idiot piece of shit", 346 + avatar: 347 + "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:mn45tewwnse5btfftvd3powc/bafkreid4nhd5pdbzqshkcfxwwcpfz4a5xk2n4gk5truu6hyfk6abynpaze@jpeg", 348 + //associated?: ProfileAssociated 349 + indexedAt: "2024-10-23T08:55:16.641Z", 350 + createdAt: "2024-10-23T08:55:16.641Z", 351 + //banner?: string 352 + followersCount: 69420, 353 + followsCount: 69420, 354 + postsCount: 69420, 355 + //associated?: ProfileAssociated 356 + //joinedViaStarterPack?: AppBskyGraphDefs.StarterPackViewBasic 357 + //indexedAt?: string 358 + //createdAt?: string 359 + //viewer?: ViewerState 360 + //labels?: ComAtprotoLabelDefs.Label[] 361 + //pinnedPost?: ComAtprotoRepoStrongRef.Main 362 + //verification?: VerificationState 363 + //status?: StatusView 364 + }, 365 + ], 366 + }; 367 + 368 + return new Response(JSON.stringify(response), { 369 + headers: withCors({ "Content-Type": "application/json" }), 370 + }); 371 + } 372 + case "app.bsky.notification.listNotifications": { 373 + const jsonTyped = 374 + jsonUntyped as XRPCTypes.AppBskyNotificationListNotifications.QueryParams; 375 + 376 + const response: XRPCTypes.AppBskyNotificationListNotifications.OutputSchema = 377 + { 378 + notifications: [ 379 + { 380 + $type: "app.bsky.notification.listNotifications#notification", 381 + uri: "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h", 382 + cid: "bafyreie44fqjarvwv3n3se6fhpf2mvodlguiwahqh7ugm3nyyujlmd36ce", 383 + author: { 384 + $type: "app.bsky.actor.defs#profileView", 385 + did: "did:plc:mn45tewwnse5btfftvd3powc", 386 + handle: "whey.party", 387 + displayName: "Whey!?@??#?", 388 + description: "idiot piece of shit", 389 + avatar: 390 + "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:mn45tewwnse5btfftvd3powc/bafkreid4nhd5pdbzqshkcfxwwcpfz4a5xk2n4gk5truu6hyfk6abynpaze@jpeg", 391 + //associated?: ProfileAssociated 392 + indexedAt: "2024-10-23T08:55:16.641Z", 393 + createdAt: "2024-10-23T08:55:16.641Z", 394 + viewer: { 395 + $type: "app.bsky.actor.defs#viewerState", 396 + muted: false, 397 + //mutedByList?: AppBskyGraphDefs.ListViewBasic 398 + //blockedBy?: boolean 399 + //blocking?: string 400 + //blockingByList?: AppBskyGraphDefs.ListViewBasic 401 + //following?: string 402 + //followedBy?: string 403 + //knownFollowers?: KnownFollowers 404 + //activitySubscription?: AppBskyNotificationDefs.ActivitySubscription 405 + }, 406 + //labels?: ComAtprotoLabelDefs.Label[] 407 + //verification?: VerificationState 408 + //status?: StatusView 409 + }, 410 + reason: "follow", 411 + //reasonSubject?: string 412 + record: { 413 + $type: "app.bsky.graph.follow", 414 + subject: "did:plc:lulmyldiq4sb2ikags5sfb25", 415 + createdAt: "2025-08-12T04:58:14.657Z", 416 + }, 417 + isRead: false, 418 + indexedAt: "2024-10-23T08:55:16.641Z", 419 + //labels?: ComAtprotoLabelDefs.Label[] 420 + }, 421 + ], 422 + }; 40 423 41 - app.get('/.well-known/did.json', (req, res) => { 42 - res.setHeader('Access-Control-Allow-Origin', '*'); 43 - res.setHeader('Content-Type', 'application/did+json'); 44 - res.json(didDocument); 45 - }); 424 + return new Response(JSON.stringify(response), { 425 + headers: withCors({ "Content-Type": "application/json" }), 426 + }); 427 + } 428 + case "app.bsky.notification.putPreferences": { 429 + if (jsonbody) { 430 + const body = jsonbody; 431 + //console.log("Body:", body); 432 + preferences = body.preferences; 433 + } 46 434 47 - app.get('/health', (req, res) => { 48 - res.send('OK'); 49 - }); 435 + const response: XRPCTypes.AppBskyUnspeccedGetConfig.OutputSchema = { 436 + checkEmailConfirmed: true, 437 + liveNow: [ 438 + { 439 + $type: "app.bsky.unspecced.getConfig#liveNowConfig", 440 + did: "did:plc:mn45tewwnse5btfftvd3powc", 441 + domains: ["local3768forumtest.whey.party"], 442 + }, 443 + ], 444 + }; 50 445 51 - // ------------------------------------------ 52 - // XRPC Method Implementations 53 - // ------------------------------------------ 446 + return new Response(JSON.stringify(response), { 447 + headers: withCors({ "Content-Type": "application/json" }), 448 + }); 54 449 55 - server.app.bsky.actor.getProfile({ 56 - auth: authVerifier, 57 - handler: async ({ auth, params }): Promise<XRPCTypes.AppBskyActorGetProfile.HandlerSuccess> => { 58 - console.log("xrpcbaby",auth,params) 59 - return { 60 - encoding: "application/json", 61 - body: { 62 - did: params.actor, 63 - handle: "example.com", 64 - displayName: "baby's first XRPC Method Implemented", 65 - avatar: undefined, 66 - description: `the auth is [${JSON.stringify(auth)}] and params are [${JSON.stringify(params)}]`, 67 - // @ts-expect-error its safe, probably 68 - "testudefinedfield": "wow youre are an idiotee", 69 - }, 70 - }; 71 - }, 450 + // const response: XRPCTypes.AppBskyActorPutPreferences.OutputSchema = 451 + // undefined; 452 + return new Response(`{"hello":"world"}`, { 453 + status: 200, 454 + headers: withCors(), 455 + }); 456 + } 457 + case "app.bsky.actor.putPreferencesshittyholleeeheoeoelelllo": { 458 + if (jsonbody) { 459 + const body = jsonbody; 460 + //console.log("Body:", body); 461 + preferences = body.preferences; 462 + } 463 + 464 + const response: XRPCTypes.AppBskyUnspeccedGetConfig.OutputSchema = { 465 + checkEmailConfirmed: true, 466 + liveNow: [ 467 + { 468 + $type: "app.bsky.unspecced.getConfig#liveNowConfig", 469 + did: "did:plc:mn45tewwnse5btfftvd3powc", 470 + domains: ["local3768forumtest.whey.party"], 471 + }, 472 + ], 473 + }; 474 + 475 + return new Response(JSON.stringify(response), { 476 + headers: withCors({ "Content-Type": "application/json" }), 477 + }); 478 + 479 + // const response: XRPCTypes.AppBskyActorPutPreferences.OutputSchema = 480 + // undefined; 481 + return new Response(`{"hello":"world"}`, { 482 + status: 200, 483 + headers: withCors(), 484 + }); 485 + } 486 + case "chat.bsky.convo.getLog": { 487 + const jsonTyped = 488 + jsonUntyped as XRPCTypes.ChatBskyConvoGetLog.QueryParams; 489 + 490 + const response: XRPCTypes.ChatBskyConvoGetLog.OutputSchema = { 491 + logs: [], 492 + }; 493 + 494 + return new Response(JSON.stringify(response), { 495 + headers: withCors({ "Content-Type": "application/json" }), 496 + }); 497 + } 498 + case "chat.bsky.convo.listConvos": { 499 + const jsonTyped = 500 + jsonUntyped as XRPCTypes.ChatBskyConvoListConvos.QueryParams; 501 + 502 + const response: XRPCTypes.ChatBskyConvoListConvos.OutputSchema = { 503 + convos: [], 504 + }; 505 + 506 + return new Response(JSON.stringify(response), { 507 + headers: withCors({ "Content-Type": "application/json" }), 508 + }); 509 + } 510 + case "app.bsky.unspecced.getConfig": { 511 + const jsonTyped = 512 + jsonUntyped as XRPCTypes.AppBskyUnspeccedGetConfig.QueryParams; 513 + 514 + const response: XRPCTypes.AppBskyUnspeccedGetConfig.OutputSchema = { 515 + checkEmailConfirmed: true, 516 + liveNow: [ 517 + { 518 + $type: "app.bsky.unspecced.getConfig#liveNowConfig", 519 + did: "did:plc:mn45tewwnse5btfftvd3powc", 520 + domains: ["local3768forumtest.whey.party"], 521 + }, 522 + ], 523 + }; 524 + 525 + return new Response(JSON.stringify(response), { 526 + headers: withCors({ "Content-Type": "application/json" }), 527 + }); 528 + } 529 + case "app.bsky.graph.getLists": { 530 + const jsonTyped = 531 + jsonUntyped as XRPCTypes.AppBskyGraphGetLists.QueryParams; 532 + 533 + const response: XRPCTypes.AppBskyGraphGetLists.OutputSchema = { 534 + lists: [], 535 + }; 536 + 537 + return new Response(JSON.stringify(response), { 538 + headers: withCors({ "Content-Type": "application/json" }), 539 + }); 540 + } 541 + //https://shimeji.us-east.host.bsky.network/xrpc/app.bsky.unspecced.getTrendingTopics?limit=14 542 + case "app.bsky.unspecced.getTrendingTopics": { 543 + const jsonTyped = 544 + jsonUntyped as XRPCTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams; 545 + 546 + const response: XRPCTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema = 547 + { 548 + topics: [ 549 + { 550 + $type: "app.bsky.unspecced.defs#trendingTopic", 551 + topic: "coolio", 552 + displayName: "coolio", 553 + description: "coolio", 554 + link: "https://custom-appview.deer-social.pages.dev/lists", 555 + }, 556 + ], 557 + suggested: [ 558 + { 559 + $type: "app.bsky.unspecced.defs#trendingTopic", 560 + topic: "coolio", 561 + displayName: "coolio", 562 + description: "coolio", 563 + link: "https://custom-appview.deer-social.pages.dev/lists", 564 + }, 565 + ], 566 + }; 567 + 568 + return new Response(JSON.stringify(response), { 569 + headers: withCors({ "Content-Type": "application/json" }), 570 + }); 571 + } 572 + default: { 573 + return new Response( 574 + JSON.stringify({ 575 + error: "XRPCNotSupported", 576 + message: "HEY hello there my name is whey dot party and you have used my custom appview that is very cool but have you considered that XRPC Not Supported", 577 + }), 578 + { 579 + status: 404, 580 + headers: withCors({ "Content-Type": "application/json" }), 581 + } 582 + ); 583 + } 584 + } 72 585 }); 586 + 587 + function withCors(headers: HeadersInit = {}) { 588 + return { 589 + "Access-Control-Allow-Origin": "*", 590 + ...headers, 591 + }; 592 + } 593 + const corsfree = { 594 + "Access-Control-Allow-Origin": "*", 595 + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", 596 + "Access-Control-Allow-Headers": "Content-Type, Authorization", 597 + }; 598 + const json = "application/json"; 599 + 73 600 74 601 // ------------------------------------------ 75 602 // Indexer
+34 -13
utils/auth.ts
··· 1 - import { AuthResult, MethodAuthVerifier, XRPCError } from "npm:@atproto/xrpc-server"; 1 + import { 2 + AuthResult, 3 + MethodAuthVerifier, 4 + XRPCError, 5 + } from "npm:@atproto/xrpc-server"; 2 6 import * as borrowed from "./auth.borrowed.ts"; 3 7 4 8 export interface AuthConfig { ··· 18 22 console.log("Authentication module initialized."); 19 23 } 20 24 21 - export async function getAuthenticatedDid(req: Request): Promise<string | null> { 25 + export async function getAuthenticatedDid( 26 + req: Request 27 + ): Promise<string | null> { 22 28 const authHeader = req.headers.get("Authorization"); 23 29 return await internalGetAuthenticatedDid(authHeader ?? undefined); 24 30 } 25 31 26 - async function internalGetAuthenticatedDid(authHeader: string | undefined): Promise<string | null> { 32 + async function internalGetAuthenticatedDid( 33 + authHeader: string | undefined 34 + ): Promise<string | null> { 27 35 if (!isInitialized) { 28 - console.error("Authentication module has not been initialized. Call setupAuth() first."); 36 + console.error( 37 + "Authentication module has not been initialized. Call setupAuth() first." 38 + ); 29 39 return null; 30 40 } 31 41 if (!authHeader || !authHeader.startsWith("Bearer ")) { ··· 44 54 45 55 return result.payload.iss as string; 46 56 } catch (err) { 47 - console.warn("JWT verification failed:", err instanceof Error ? err.message : String(err)); 57 + console.warn( 58 + "JWT verification failed:", 59 + err instanceof Error ? err.message : String(err) 60 + ); 48 61 return null; 49 62 } 50 63 } 51 64 52 65 export const authVerifier: MethodAuthVerifier<AuthResult> = async ({ req }) => { 53 - console.log("help us all fuck you",req) 54 - const authHeader = (req as any).headers['authorization']; 55 - 66 + //console.log("help us all fuck you",req) 67 + console.log("you are doing well") 68 + const url = (req as any).url; 69 + const params = (req as any).params ?? {}; 70 + console.log("Request info:", { url, params }); 71 + return { 72 + credentials: "did:plc:mn45tewwnse5btfftvd3powc", 73 + }; 74 + const authHeader = (req as any).headers["authorization"]; 75 + 56 76 const did = await internalGetAuthenticatedDid(authHeader); 57 77 58 - if (!did) { 59 - // i dont know the correct xrpc spec for this 60 - throw new XRPCError(401, 'AuthenticationRequired', 'Invalid or missing authentication token.'); 61 - } 78 + // throw this later dont do it here 79 + // if (!did) { 80 + // // i dont know the correct xrpc spec for this 81 + // throw new XRPCError(401, 'AuthenticationRequired', 'Invalid or missing authentication token.'); 82 + // } 62 83 63 84 console.log(`Successfully authenticated DID: ${did}`); 64 85 ··· 67 88 did: did, 68 89 }, 69 90 }; 70 - }; 91 + };
+21
utils/dbsetup.ts
··· 26 26 handle TEXT 27 27 ); 28 28 ${createIndexINE} idx_did_handle ON did(handle); 29 + 30 + ${createTableINE} prefs ( 31 + did TEXT PRIMARY KEY NOT NULL, 32 + json TEXT 33 + ); 34 + ${createIndexINE} idx_prefs_did ON prefs(did); 35 + 36 + ${createTableINE} backlink_skeleton ( 37 + id INTEGER PRIMARY KEY AUTOINCREMENT, 38 + srcuri TEXT, 39 + srcdid TEXT, 40 + srcfield TEXT, 41 + srccol TEXT, 42 + suburi TEXT, 43 + subdid TEXT, 44 + subcol TEXT 45 + ); 46 + ${createIndexINE} idx_backlink_subdid_mod ON backlink_skeleton(subdid, srcdid); 47 + ${createIndexINE} idx_backlink_suburi_mod ON backlink_skeleton(suburi, srcdid); 48 + ${createIndexINE} idx_backlink_subdid_filter_mod ON backlink_skeleton(subdid, srccol, srcdid); 49 + ${createIndexINE} idx_backlink_suburi_filter_mod ON backlink_skeleton(suburi, srccol, srcdid); 29 50 30 51 ${createTableINE} app_bsky_actor_profile ( 31 52 ${baseColumns},