tangled
alpha
login
or
join now
whey.party
/
skylite
38
fork
atom
an attempt to make a lightweight, easily self-hostable, scoped bluesky appview
38
fork
atom
overview
issues
pulls
pipelines
initial view server stuff
rimar1337
6 months ago
52208474
b91eeceb
+1111
-657
6 changed files
expand all
collapse all
unified
split
indexserver.ts
main-index.ts
main-view.ts
utils
auth.borrowed.ts
auth.ts
viewserver.ts
+240
-155
indexserver.ts
···
1
1
import { indexHandlerContext } from "./index/types.ts";
2
2
3
3
import { assertRecord, validateRecord } from "./utils/records.ts";
4
4
-
import { searchParamsToJson, withCors } from "./utils/server.ts";
4
4
+
import {
5
5
+
buildBlobUrl,
6
6
+
resolveIdentity,
7
7
+
searchParamsToJson,
8
8
+
withCors,
9
9
+
} from "./utils/server.ts";
5
10
import * as IndexServerTypes from "./utils/indexservertypes.ts";
6
11
import { Database } from "jsr:@db/sqlite@0.11";
7
12
import { setupUserDb } from "./utils/dbuser.ts";
···
18
23
export interface IndexServerConfig {
19
24
baseDbPath: string;
20
25
systemDbPath: string;
21
21
-
jetstreamUrl: string;
22
26
}
23
27
24
28
interface BaseRow {
···
86
90
}
87
91
88
92
// We will move all the global functions into this class as methods...
89
89
-
indexServerHandler(req: Request): Response {
93
93
+
async indexServerHandler(req: Request): Promise<Response> {
90
94
const url = new URL(req.url);
91
95
const pathname = url.pathname;
92
96
//const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
···
103
107
const jsonTyped =
104
108
jsonUntyped as IndexServerTypes.AppBskyActorGetProfile.QueryParams;
105
109
106
106
-
const res = this.queryProfileView(jsonTyped.actor, "Detailed");
110
110
+
const res = await this.queryProfileView(jsonTyped.actor, "Detailed");
107
111
if (!res)
108
112
return new Response(
109
113
JSON.stringify({
···
126
130
jsonUntyped as IndexServerTypes.AppBskyActorGetProfiles.QueryParams;
127
131
128
132
if (typeof jsonUntyped?.actors === "string") {
129
129
-
const res = this.queryProfileView(
133
133
+
const res = await this.queryProfileView(
130
134
jsonUntyped.actors as string,
131
135
"Detailed"
132
136
);
···
151
155
}
152
156
153
157
const res: ATPAPI.AppBskyActorDefs.ProfileViewDetailed[] =
154
154
-
jsonTyped.actors
155
155
-
.map((actor) => {
156
156
-
return this.queryProfileView(actor, "Detailed");
157
157
-
})
158
158
-
.filter(
159
159
-
(x): x is ATPAPI.AppBskyActorDefs.ProfileViewDetailed =>
160
160
-
x !== undefined
161
161
-
);
158
158
+
await Promise.all(
159
159
+
jsonTyped.actors
160
160
+
.map(async (actor) => {
161
161
+
return await this.queryProfileView(actor, "Detailed");
162
162
+
})
163
163
+
.filter(
164
164
+
(
165
165
+
x
166
166
+
): x is Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed> =>
167
167
+
x !== undefined
168
168
+
)
169
169
+
);
162
170
163
171
if (!res)
164
172
return new Response(
···
184
192
const jsonTyped =
185
193
jsonUntyped as IndexServerTypes.AppBskyFeedGetActorFeeds.QueryParams;
186
194
187
187
-
const qresult = this.queryActorFeeds(jsonTyped.actor);
195
195
+
const qresult = await this.queryActorFeeds(jsonTyped.actor);
188
196
189
197
const response: IndexServerTypes.AppBskyFeedGetActorFeeds.OutputSchema =
190
198
{
···
199
207
const jsonTyped =
200
208
jsonUntyped as IndexServerTypes.AppBskyFeedGetFeedGenerator.QueryParams;
201
209
202
202
-
const qresult = this.queryFeedGenerator(jsonTyped.feed);
210
210
+
const qresult = await this.queryFeedGenerator(jsonTyped.feed);
203
211
if (!qresult) {
204
212
return new Response(
205
213
JSON.stringify({
···
227
235
const jsonTyped =
228
236
jsonUntyped as IndexServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
229
237
230
230
-
const qresult = this.queryFeedGenerators(jsonTyped.feeds);
238
238
+
const qresult = await this.queryFeedGenerators(jsonTyped.feeds);
231
239
if (!qresult) {
232
240
return new Response(
233
241
JSON.stringify({
···
254
262
jsonUntyped as IndexServerTypes.AppBskyFeedGetPosts.QueryParams;
255
263
256
264
const posts: IndexServerTypes.AppBskyFeedGetPosts.OutputSchema["posts"] =
257
257
-
jsonTyped.uris
258
258
-
.map((uri) => {
259
259
-
return this.queryPostView(uri);
260
260
-
})
261
261
-
.filter(Boolean) as ATPAPI.AppBskyFeedDefs.PostView[];
265
265
+
(
266
266
+
await Promise.all(
267
267
+
jsonTyped.uris.map((uri) => this.queryPostView(uri))
268
268
+
)
269
269
+
).filter((p): p is ATPAPI.AppBskyFeedDefs.PostView => Boolean(p));
262
270
263
271
const response: IndexServerTypes.AppBskyFeedGetPosts.OutputSchema = {
264
272
posts,
···
274
282
275
283
// TODO: not partial yet, currently skips refs
276
284
277
277
-
const qresult = this.queryActorLikesPartial(
285
285
+
const qresult = await this.queryActorLikesPartial(
278
286
jsonTyped.actor,
279
287
jsonTyped.cursor
280
288
);
···
306
314
307
315
// TODO: not partial yet, currently skips refs
308
316
309
309
-
const qresult = this.queryAuthorFeedPartial(jsonTyped.actor, jsonTyped.cursor);
317
317
+
const qresult = await this.queryAuthorFeedPartial(
318
318
+
jsonTyped.actor,
319
319
+
jsonTyped.cursor
320
320
+
);
310
321
if (!qresult) {
311
322
return new Response(
312
323
JSON.stringify({
···
363
374
364
375
// TODO: not partial yet, currently skips refs
365
376
366
366
-
const qresult = this.queryPostThreadPartial(jsonTyped.uri);
377
377
+
const qresult = await this.queryPostThreadPartial(jsonTyped.uri);
367
378
if (!qresult) {
368
379
return new Response(
369
380
JSON.stringify({
···
388
399
389
400
// TODO: not partial yet, currently skips refs
390
401
391
391
-
const qresult = this.queryQuotes(jsonTyped.uri);
402
402
+
const qresult = await this.queryQuotes(jsonTyped.uri);
392
403
if (!qresult) {
393
404
return new Response(
394
405
JSON.stringify({
···
418
429
419
430
// TODO: not partial yet, currently skips refs
420
431
421
421
-
const qresult = this.queryReposts(jsonTyped.uri);
432
432
+
const qresult = await this.queryReposts(jsonTyped.uri);
422
433
if (!qresult) {
423
434
return new Response(
424
435
JSON.stringify({
···
870
881
}
871
882
872
883
// user data
873
873
-
queryProfileView(
884
884
+
async queryProfileView(
874
885
did: string,
875
886
type: ""
876
876
-
): ATPAPI.AppBskyActorDefs.ProfileView | undefined;
877
877
-
queryProfileView(
887
887
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileView | undefined>;
888
888
+
async queryProfileView(
878
889
did: string,
879
890
type: "Basic"
880
880
-
): ATPAPI.AppBskyActorDefs.ProfileViewBasic | undefined;
881
881
-
queryProfileView(
891
891
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewBasic | undefined>;
892
892
+
async queryProfileView(
882
893
did: string,
883
894
type: "Detailed"
884
884
-
): ATPAPI.AppBskyActorDefs.ProfileViewDetailed | undefined;
885
885
-
queryProfileView(
895
895
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileViewDetailed | undefined>;
896
896
+
async queryProfileView(
886
897
did: string,
887
898
type: "" | "Basic" | "Detailed"
888
888
-
):
899
899
+
): Promise<
889
900
| ATPAPI.AppBskyActorDefs.ProfileView
890
901
| ATPAPI.AppBskyActorDefs.ProfileViewBasic
891
902
| ATPAPI.AppBskyActorDefs.ProfileViewDetailed
892
892
-
| undefined {
903
903
+
| undefined
904
904
+
> {
893
905
if (!this.isRegisteredIndexUser(did)) return;
894
906
const db = this.userManager.getDbForDid(did);
895
907
if (!db) return;
···
903
915
904
916
const row = stmt.get(did) as ProfileRow;
905
917
918
918
+
const identity = await resolveIdentity(did);
919
919
+
const avatar = row.avatarcid ? buildBlobUrl(
920
920
+
identity.pds,
921
921
+
identity.did,
922
922
+
row.avatarcid
923
923
+
) : undefined
924
924
+
const banner = row.bannercid ? buildBlobUrl(
925
925
+
identity.pds,
926
926
+
identity.did,
927
927
+
row.bannercid
928
928
+
) : undefined
906
929
// simulate different types returned
907
930
switch (type) {
908
931
case "": {
909
932
const result: ATPAPI.AppBskyActorDefs.ProfileView = {
910
933
$type: "app.bsky.actor.defs#profileView",
911
934
did: did,
912
912
-
handle: "idiot.fuck.shit.example.com", // TODO: Resolve user identity here for the handle
935
935
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
913
936
displayName: row.displayname ?? undefined,
914
937
description: row.description ?? undefined,
915
915
-
avatar: "https://google.com/", // create profile URL from resolved identity
938
938
+
avatar: avatar, // create profile URL from resolved identity
916
939
//associated?: ProfileAssociated,
917
940
indexedAt: row.createdat
918
941
? new Date(row.createdat).toISOString()
···
931
954
const result: ATPAPI.AppBskyActorDefs.ProfileViewBasic = {
932
955
$type: "app.bsky.actor.defs#profileViewBasic",
933
956
did: did,
934
934
-
handle: "idiot.fuck.shit.example.com", // TODO: Resolve user identity here for the handle
957
957
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
935
958
displayName: row.displayname ?? undefined,
936
936
-
avatar: "https://google.com/", // create profile URL from resolved identity
959
959
+
avatar: avatar, // create profile URL from resolved identity
937
960
//associated?: ProfileAssociated,
938
961
createdAt: row.createdat
939
962
? new Date(row.createdat).toISOString()
···
976
999
const result: ATPAPI.AppBskyActorDefs.ProfileViewDetailed = {
977
1000
$type: "app.bsky.actor.defs#profileViewDetailed",
978
1001
did: did,
979
979
-
handle: "idiot.fuck.shit.example.com", // TODO: Resolve user identity here for the handle
1002
1002
+
handle: identity.handle, // TODO: Resolve user identity here for the handle
980
1003
displayName: row.displayname ?? undefined,
981
1004
description: row.description ?? undefined,
982
982
-
avatar: "https://google.com/", // TODO: create profile URL from resolved identity
983
983
-
banner: "https://youtube.com/", // same here
1005
1005
+
avatar: avatar, // TODO: create profile URL from resolved identity
1006
1006
+
banner: banner, // same here
984
1007
followersCount: followersCount,
985
1008
followsCount: followsCount,
986
1009
postsCount: postsCount,
···
1006
1029
}
1007
1030
1008
1031
// post hydration
1009
1009
-
queryPostView(uri: string): ATPAPI.AppBskyFeedDefs.PostView | undefined {
1032
1032
+
async queryPostView(
1033
1033
+
uri: string
1034
1034
+
): Promise<ATPAPI.AppBskyFeedDefs.PostView | undefined> {
1010
1035
const URI = new AtUri(uri);
1011
1036
const did = URI.host;
1012
1037
if (!this.isRegisteredIndexUser(did)) return;
···
1021
1046
`);
1022
1047
1023
1048
const row = stmt.get(uri) as PostRow;
1024
1024
-
const profileView = this.queryProfileView(did, "Basic");
1049
1049
+
const profileView = await this.queryProfileView(did, "Basic");
1025
1050
if (!row || !row.cid || !profileView || !row.json) return;
1026
1051
const value = JSON.parse(row.json) as ATPAPI.AppBskyFeedPost.Record;
1027
1052
···
1048
1073
return post;
1049
1074
}
1050
1075
1051
1051
-
queryFeedViewPost(
1076
1076
+
async queryFeedViewPost(
1052
1077
uri: string
1053
1053
-
): ATPAPI.AppBskyFeedDefs.FeedViewPost | undefined {
1054
1054
-
const post = this.queryPostView(uri);
1078
1078
+
): Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost | undefined> {
1079
1079
+
const post = await this.queryPostView(uri);
1055
1080
if (!post) return;
1056
1081
1057
1082
const feedviewpost: ATPAPI.AppBskyFeedDefs.FeedViewPost = {
···
1080
1105
1081
1106
// user feedgens
1082
1107
1083
1083
-
queryActorFeeds(did: string): ATPAPI.AppBskyFeedDefs.GeneratorView[] {
1108
1108
+
async queryActorFeeds(
1109
1109
+
did: string
1110
1110
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView[]> {
1084
1111
if (!this.isRegisteredIndexUser(did)) return [];
1085
1112
const db = this.userManager.getDbForDid(did);
1086
1113
if (!db) return [];
···
1093
1120
`);
1094
1121
1095
1122
const rows = stmt.all(did) as unknown as GeneratorRow[];
1096
1096
-
const creatorView = this.queryProfileView(did, "Basic");
1123
1123
+
const creatorView = await this.queryProfileView(did, "Basic");
1097
1124
if (!creatorView) return [];
1098
1125
1099
1126
return rows
···
1123
1150
.filter((v): v is ATPAPI.AppBskyFeedDefs.GeneratorView => !!v);
1124
1151
}
1125
1152
1126
1126
-
queryFeedGenerator(
1153
1153
+
async queryFeedGenerator(
1127
1154
uri: string
1128
1128
-
): ATPAPI.AppBskyFeedDefs.GeneratorView | undefined {
1129
1129
-
return this.queryFeedGenerators([uri])[0];
1155
1155
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView | undefined> {
1156
1156
+
const gens = await this.queryFeedGenerators([uri]); // gens: GeneratorView[]
1157
1157
+
return gens[0];
1130
1158
}
1131
1159
1132
1132
-
queryFeedGenerators(uris: string[]): ATPAPI.AppBskyFeedDefs.GeneratorView[] {
1160
1160
+
async queryFeedGenerators(
1161
1161
+
uris: string[]
1162
1162
+
): Promise<ATPAPI.AppBskyFeedDefs.GeneratorView[]> {
1133
1163
const generators: ATPAPI.AppBskyFeedDefs.GeneratorView[] = [];
1134
1164
const urisByDid = new Map<string, string[]>();
1135
1165
···
1158
1188
const rows = stmt.all(...didUris) as unknown as GeneratorRow[];
1159
1189
if (rows.length === 0) continue;
1160
1190
1161
1161
-
const creatorView = this.queryProfileView(did, "");
1191
1191
+
const creatorView = await this.queryProfileView(did, "");
1162
1192
if (!creatorView) continue;
1163
1193
1164
1194
for (const row of rows) {
···
1188
1218
1189
1219
// user feeds
1190
1220
1191
1191
-
queryAuthorFeedPartial(
1221
1221
+
async queryAuthorFeedPartial(
1192
1222
did: string,
1193
1223
cursor?: string
1194
1194
-
):
1224
1224
+
): Promise<
1195
1225
| {
1196
1226
items: (
1197
1227
| ATPAPI.AppBskyFeedDefs.FeedViewPost
···
1199
1229
)[];
1200
1230
cursor: string | undefined;
1201
1231
}
1202
1202
-
| undefined {
1232
1232
+
| undefined
1233
1233
+
> {
1203
1234
if (!this.isRegisteredIndexUser(did)) return;
1204
1235
const db = this.userManager.getDbForDid(did);
1205
1236
if (!db) return;
···
1234
1265
subject: string | null;
1235
1266
}[];
1236
1267
1237
1237
-
const authorProfile = this.queryProfileView(did,"Basic");
1268
1268
+
const authorProfile = await this.queryProfileView(did, "Basic");
1238
1269
1239
1239
-
const items = rows
1240
1240
-
.map((row) => {
1241
1241
-
if (row.type === "repost" && row.subject) {
1242
1242
-
const subjectDid = new AtUri(row.subject).host
1270
1270
+
const items = await Promise.all(
1271
1271
+
rows
1272
1272
+
.map((row) => {
1273
1273
+
if (row.type === "repost" && row.subject) {
1274
1274
+
const subjectDid = new AtUri(row.subject).host;
1243
1275
1244
1244
-
const originalPost = this.handlesDid(subjectDid)
1245
1245
-
? this.queryFeedViewPost(row.subject)
1246
1246
-
: this.constructFeedViewPostRef(row.subject);
1276
1276
+
const originalPost = this.handlesDid(subjectDid)
1277
1277
+
? this.queryFeedViewPost(row.subject)
1278
1278
+
: this.constructFeedViewPostRef(row.subject);
1247
1279
1248
1248
-
if (!originalPost || !authorProfile) return null;
1280
1280
+
if (!originalPost || !authorProfile) return null;
1249
1281
1250
1250
-
return {
1251
1251
-
post: originalPost,
1252
1252
-
reason: {
1253
1253
-
$type: "app.bsky.feed.defs#reasonRepost",
1254
1254
-
by: authorProfile,
1255
1255
-
indexedAt: new Date(row.indexedat).toISOString(),
1256
1256
-
},
1257
1257
-
};
1258
1258
-
} else {
1259
1259
-
return this.queryFeedViewPost(row.uri);
1260
1260
-
}
1261
1261
-
})
1262
1262
-
.filter((p): p is ATPAPI.AppBskyFeedDefs.FeedViewPost => !!p);
1282
1282
+
return {
1283
1283
+
post: originalPost,
1284
1284
+
reason: {
1285
1285
+
$type: "app.bsky.feed.defs#reasonRepost",
1286
1286
+
by: authorProfile,
1287
1287
+
indexedAt: new Date(row.indexedat).toISOString(),
1288
1288
+
},
1289
1289
+
};
1290
1290
+
} else {
1291
1291
+
return this.queryFeedViewPost(row.uri);
1292
1292
+
}
1293
1293
+
})
1294
1294
+
.filter((p): p is Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost> => !!p)
1295
1295
+
);
1263
1296
1264
1297
const lastItem = rows[rows.length - 1];
1265
1298
const nextCursor = lastItem
···
1281
1314
return { items: [], cursor: undefined };
1282
1315
}
1283
1316
1284
1284
-
queryActorLikesPartial(
1317
1317
+
async queryActorLikesPartial(
1285
1318
did: string,
1286
1319
cursor?: string
1287
1287
-
):
1320
1320
+
): Promise<
1288
1321
| {
1289
1322
items: (
1290
1323
| ATPAPI.AppBskyFeedDefs.FeedViewPost
···
1292
1325
)[];
1293
1326
cursor: string | undefined;
1294
1327
}
1295
1295
-
| undefined {
1328
1328
+
| undefined
1329
1329
+
> {
1296
1330
// early return only if the actor did is not registered
1297
1331
if (!this.isRegisteredIndexUser(did)) return;
1298
1332
const db = this.userManager.getDbForDid(did);
···
1320
1354
cid: string;
1321
1355
}[];
1322
1356
1323
1323
-
const items = rows
1324
1324
-
.map((row) => {
1325
1325
-
const subjectDid = new AtUri(row.subject).host;
1357
1357
+
const items = await Promise.all(
1358
1358
+
rows
1359
1359
+
.map(async (row) => {
1360
1360
+
const subjectDid = new AtUri(row.subject).host;
1326
1361
1327
1327
-
if (this.handlesDid(subjectDid)) {
1328
1328
-
return this.queryFeedViewPost(row.subject);
1329
1329
-
} else {
1330
1330
-
return this.constructFeedViewPostRef(row.subject);
1331
1331
-
}
1332
1332
-
})
1333
1333
-
.filter(
1334
1334
-
(
1335
1335
-
p
1336
1336
-
): p is
1337
1337
-
| ATPAPI.AppBskyFeedDefs.FeedViewPost
1338
1338
-
| IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef => !!p
1339
1339
-
);
1362
1362
+
if (this.handlesDid(subjectDid)) {
1363
1363
+
return await this.queryFeedViewPost(row.subject);
1364
1364
+
} else {
1365
1365
+
return this.constructFeedViewPostRef(row.subject);
1366
1366
+
}
1367
1367
+
})
1368
1368
+
.filter(
1369
1369
+
(
1370
1370
+
p
1371
1371
+
): p is Promise<
1372
1372
+
| ATPAPI.AppBskyFeedDefs.FeedViewPost
1373
1373
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.FeedViewPostRef
1374
1374
+
> => !!p
1375
1375
+
)
1376
1376
+
);
1340
1377
1341
1378
const lastItem = rows[rows.length - 1];
1342
1379
const nextCursor = lastItem
···
1348
1385
1349
1386
// post metadata
1350
1387
1351
1351
-
queryLikes(uri: string): ATPAPI.AppBskyFeedGetLikes.Like[] | undefined {
1388
1388
+
async queryLikes(
1389
1389
+
uri: string
1390
1390
+
): Promise<ATPAPI.AppBskyFeedGetLikes.Like[] | undefined> {
1352
1391
const postUri = new AtUri(uri);
1353
1392
const postAuthorDid = postUri.hostname;
1354
1393
if (!this.isRegisteredIndexUser(postAuthorDid)) return;
···
1364
1403
1365
1404
const rows = stmt.all(uri) as unknown as BacklinkRow[];
1366
1405
1367
1367
-
return rows
1368
1368
-
.map((row) => {
1369
1369
-
const actor = this.queryProfileView(row.srcdid, "");
1370
1370
-
if (!actor) return;
1406
1406
+
return await Promise.all(
1407
1407
+
rows
1408
1408
+
.map(async (row) => {
1409
1409
+
const actor = await this.queryProfileView(row.srcdid, "");
1410
1410
+
if (!actor) return;
1371
1411
1372
1372
-
return {
1373
1373
-
// TODO write indexedAt for spacedust indexes
1374
1374
-
createdAt: new Date(Date.now()).toISOString(),
1375
1375
-
indexedAt: new Date(Date.now()).toISOString(),
1376
1376
-
actor: actor,
1377
1377
-
};
1378
1378
-
})
1379
1379
-
.filter((like): like is ATPAPI.AppBskyFeedGetLikes.Like => !!like);
1412
1412
+
return {
1413
1413
+
// TODO write indexedAt for spacedust indexes
1414
1414
+
createdAt: new Date(Date.now()).toISOString(),
1415
1415
+
indexedAt: new Date(Date.now()).toISOString(),
1416
1416
+
actor: actor,
1417
1417
+
};
1418
1418
+
})
1419
1419
+
.filter(
1420
1420
+
(like): like is Promise<ATPAPI.AppBskyFeedGetLikes.Like> => !!like
1421
1421
+
)
1422
1422
+
);
1380
1423
}
1381
1424
1382
1382
-
queryReposts(uri: string): ATPAPI.AppBskyActorDefs.ProfileView[] {
1425
1425
+
async queryReposts(
1426
1426
+
uri: string
1427
1427
+
): Promise<ATPAPI.AppBskyActorDefs.ProfileView[]> {
1383
1428
const postUri = new AtUri(uri);
1384
1429
const postAuthorDid = postUri.hostname;
1385
1430
if (!this.isRegisteredIndexUser(postAuthorDid)) return [];
···
1395
1440
1396
1441
const rows = stmt.all(uri) as { srcdid: string }[];
1397
1442
1398
1398
-
return rows
1399
1399
-
.map((row) => this.queryProfileView(row.srcdid, ""))
1400
1400
-
.filter((p): p is ATPAPI.AppBskyActorDefs.ProfileView => !!p);
1443
1443
+
return await Promise.all(
1444
1444
+
rows
1445
1445
+
.map(async (row) => await this.queryProfileView(row.srcdid, ""))
1446
1446
+
.filter((p): p is Promise<ATPAPI.AppBskyActorDefs.ProfileView> => !!p)
1447
1447
+
);
1401
1448
}
1402
1449
1403
1403
-
queryQuotes(uri: string): ATPAPI.AppBskyFeedDefs.FeedViewPost[] {
1450
1450
+
async queryQuotes(
1451
1451
+
uri: string
1452
1452
+
): Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost[]> {
1404
1453
const postUri = new AtUri(uri);
1405
1454
const postAuthorDid = postUri.hostname;
1406
1455
if (!this.isRegisteredIndexUser(postAuthorDid)) return [];
···
1416
1465
1417
1466
const rows = stmt.all(uri) as { srcuri: string }[];
1418
1467
1419
1419
-
return rows
1420
1420
-
.map((row) => this.queryFeedViewPost(row.srcuri))
1421
1421
-
.filter((p): p is ATPAPI.AppBskyFeedDefs.FeedViewPost => !!p);
1468
1468
+
return await Promise.all(
1469
1469
+
rows
1470
1470
+
.map(async (row) => await this.queryFeedViewPost(row.srcuri))
1471
1471
+
.filter((p): p is Promise<ATPAPI.AppBskyFeedDefs.FeedViewPost> => !!p)
1472
1472
+
);
1422
1473
}
1423
1423
-
_getPostViewUnion(
1474
1474
+
async _getPostViewUnion(
1424
1475
uri: string
1425
1425
-
):
1476
1476
+
): Promise<
1426
1477
| ATPAPI.AppBskyFeedDefs.PostView
1427
1478
| IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef
1428
1428
-
| undefined {
1479
1479
+
| undefined
1480
1480
+
> {
1429
1481
try {
1430
1482
const postDid = new AtUri(uri).hostname;
1431
1483
if (this.handlesDid(postDid)) {
1432
1432
-
return this.queryPostView(uri);
1484
1484
+
return await this.queryPostView(uri);
1433
1485
} else {
1434
1486
return this.constructPostViewRef(uri);
1435
1487
}
···
1437
1489
return undefined;
1438
1490
}
1439
1491
}
1440
1440
-
queryPostThreadPartial(
1492
1492
+
async queryPostThreadPartial(
1441
1493
uri: string
1442
1442
-
): IndexServerTypes.PartyWheyAppBskyFeedGetPostThreadPartial.OutputSchema | undefined {
1443
1443
-
1444
1444
-
const post = this._getPostViewUnion(uri);
1494
1494
+
): Promise<
1495
1495
+
| IndexServerTypes.PartyWheyAppBskyFeedGetPostThreadPartial.OutputSchema
1496
1496
+
| undefined
1497
1497
+
> {
1498
1498
+
const post = await this._getPostViewUnion(uri);
1445
1499
1446
1500
if (!post) {
1447
1501
return {
···
1455
1509
1456
1510
const thread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef = {
1457
1511
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1458
1458
-
post: post as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView> | IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1512
1512
+
post: post as
1513
1513
+
| ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>
1514
1514
+
| IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1459
1515
replies: [],
1460
1516
};
1461
1517
1462
1518
let current = thread;
1463
1519
// we can only climb the parent tree if we have the full post record.
1464
1520
// which is not implemented yet (sad i know)
1465
1465
-
if (isPostView(current.post) && isFeedPostRecord(current.post.record) && current.post.record?.reply?.parent?.uri) {
1521
1521
+
if (
1522
1522
+
isPostView(current.post) &&
1523
1523
+
isFeedPostRecord(current.post.record) &&
1524
1524
+
current.post.record?.reply?.parent?.uri
1525
1525
+
) {
1466
1526
let parentUri: string | undefined = current.post.record.reply.parent.uri;
1467
1527
1468
1528
// keep climbing as long as we find a valid parent post.
1469
1529
while (parentUri) {
1470
1470
-
const parentPost = this._getPostViewUnion(parentUri);
1530
1530
+
const parentPost = await this._getPostViewUnion(parentUri);
1471
1531
if (!parentPost) break; // stop if a parent in the chain is not found.
1472
1532
1473
1473
-
const parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef = {
1474
1474
-
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1475
1475
-
post: parentPost as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>,
1476
1476
-
replies: [current as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>],
1477
1477
-
};
1478
1478
-
current.parent = parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>;
1533
1533
+
const parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef =
1534
1534
+
{
1535
1535
+
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1536
1536
+
post: parentPost as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>,
1537
1537
+
replies: [
1538
1538
+
current as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1539
1539
+
],
1540
1540
+
};
1541
1541
+
current.parent =
1542
1542
+
parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>;
1479
1543
current = parentThread;
1480
1544
1481
1545
// check if the new current post has a parent to continue the loop
1482
1482
-
parentUri = (isPostView(current.post) && isFeedPostRecord(current.post.record)) ? current.post.record?.reply?.parent?.uri : undefined;
1546
1546
+
parentUri =
1547
1547
+
isPostView(current.post) && isFeedPostRecord(current.post.record)
1548
1548
+
? current.post.record?.reply?.parent?.uri
1549
1549
+
: undefined;
1483
1550
}
1484
1551
}
1485
1485
-
1486
1486
-
1487
1552
1488
1553
const seenUris = new Set<string>();
1489
1489
-
const fetchReplies = (parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef) => {
1490
1490
-
if (!parentThread.post || !('uri' in parentThread.post)) {
1554
1554
+
const fetchReplies = async (
1555
1555
+
parentThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef
1556
1556
+
) => {
1557
1557
+
if (!parentThread.post || !("uri" in parentThread.post)) {
1491
1558
return;
1492
1559
}
1493
1560
if (seenUris.has(parentThread.post.uri)) return;
···
1498
1565
1499
1566
// replies can only be discovered for local posts where we have the backlink data
1500
1567
if (!this.handlesDid(parentAuthorDid)) return;
1501
1501
-
1568
1568
+
1502
1569
const db = this.userManager.getDbForDid(parentAuthorDid);
1503
1570
if (!db) return;
1504
1571
···
1509
1576
`);
1510
1577
const replyRows = stmt.all(parentThread.post.uri) as { srcuri: string }[];
1511
1578
1512
1512
-
const replies = replyRows
1513
1513
-
.map((row) => this._getPostViewUnion(row.srcuri))
1514
1514
-
.filter((p): p is ATPAPI.AppBskyFeedDefs.PostView | IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef => !!p);
1579
1579
+
const replies = await Promise.all(
1580
1580
+
replyRows
1581
1581
+
.map(async (row) => await this._getPostViewUnion(row.srcuri))
1582
1582
+
.filter(
1583
1583
+
(
1584
1584
+
p
1585
1585
+
): p is Promise<
1586
1586
+
| ATPAPI.AppBskyFeedDefs.PostView
1587
1587
+
| IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef
1588
1588
+
> => !!p
1589
1589
+
)
1590
1590
+
);
1515
1591
1516
1592
for (const replyPost of replies) {
1517
1517
-
const replyThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef = {
1518
1518
-
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1519
1519
-
post: replyPost as ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView> | IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1520
1520
-
parent: parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1521
1521
-
replies: [],
1522
1522
-
};
1523
1523
-
parentThread.replies?.push(replyThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>);
1593
1593
+
const replyThread: IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef =
1594
1594
+
{
1595
1595
+
$type: "party.whey.app.bsky.feed.defs#threadViewPostRef",
1596
1596
+
post: replyPost as
1597
1597
+
| ATPAPI.$Typed<ATPAPI.AppBskyFeedDefs.PostView>
1598
1598
+
| IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.PostViewRef>,
1599
1599
+
parent:
1600
1600
+
parentThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1601
1601
+
replies: [],
1602
1602
+
};
1603
1603
+
parentThread.replies?.push(
1604
1604
+
replyThread as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>
1605
1605
+
);
1524
1606
fetchReplies(replyThread); // recurse
1525
1607
}
1526
1608
};
1527
1609
1528
1610
fetchReplies(thread);
1529
1611
1530
1530
-
const returned = current as unknown as IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef;
1612
1612
+
const returned =
1613
1613
+
current as unknown as IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef;
1531
1614
1532
1532
-
return { thread: returned as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef> };
1615
1615
+
return {
1616
1616
+
thread:
1617
1617
+
returned as IndexServerUtils.$Typed<IndexServerAPI.PartyWheyAppBskyFeedDefs.ThreadViewPostRef>,
1618
1618
+
};
1533
1619
}
1534
1534
-
1535
1620
1536
1621
/**
1537
1622
* please do not use this, use openDbForDid() instead
+62
-56
main-index.ts
···
2
2
import { setupSystemDb } from "./utils/dbsystem.ts";
3
3
import { didDocument } from "./utils/diddoc.ts";
4
4
import { cachedFetch, searchParamsToJson, withCors } from "./utils/server.ts";
5
5
-
import { IndexServer, IndexServerConfig } from "./indexserver.ts"
5
5
+
import { IndexServer, IndexServerConfig } from "./indexserver.ts";
6
6
import { extractDid } from "./utils/identity.ts";
7
7
import { config } from "./config.ts";
8
8
···
11
11
// ------------------------------------------
12
12
13
13
const indexServerConfig: IndexServerConfig = {
14
14
-
baseDbPath: './dbs/registered-users', // The directory for user databases
15
15
-
systemDbPath: './dbs/registered-users/system.db', // The path for the main system database
16
16
-
jetstreamUrl: config.jetstream
14
14
+
baseDbPath: "./dbs/index/registered-users", // The directory for user databases
15
15
+
systemDbPath: "./dbs/index/registered-users/system.db", // The path for the main system database
17
16
};
18
17
export const genericIndexServer = new IndexServer(indexServerConfig);
19
18
setupSystemDb(genericIndexServer.systemDB);
···
35
34
datetime('now'),
36
35
'ready'
37
36
);
38
38
-
`)
37
37
+
`);
39
38
40
39
genericIndexServer.start();
41
40
···
61
60
// app.bsky.graph.getLists // doesnt need to because theres no items[], and its self ProfileViewBasic
62
61
// app.bsky.graph.getList // needs to be Partial-ed (items[] union with ProfileViewRef)
63
62
// app.bsky.graph.getActorStarterPacks // maybe doesnt need to be Partial-ed because its self ProfileViewBasic
64
64
-
63
63
+
65
64
// app.bsky.feed.getListFeed // uhh actually already exists its getListFeedPartial
66
65
// */
67
66
// "/xrpc/party.whey.app.bsky.feed.getListFeedPartial",
68
67
// ]);
69
68
70
70
-
Deno.serve(
71
71
-
{ port: config.indexServer.port },
72
72
-
(req: Request): Response => {
73
73
-
const url = new URL(req.url);
74
74
-
const pathname = url.pathname;
75
75
-
const searchParams = searchParamsToJson(url.searchParams);
69
69
+
Deno.serve({ port: config.indexServer.port }, async (req: Request): Promise<Response> => {
70
70
+
const url = new URL(req.url);
71
71
+
const pathname = url.pathname;
72
72
+
const searchParams = searchParamsToJson(url.searchParams);
76
73
77
77
-
if (pathname === "/.well-known/did.json") {
78
78
-
return new Response(JSON.stringify(didDocument("index",config.indexServer.did,config.indexServer.host,"whatever")), {
74
74
+
if (pathname === "/.well-known/did.json") {
75
75
+
return new Response(
76
76
+
JSON.stringify(
77
77
+
didDocument(
78
78
+
"index",
79
79
+
config.indexServer.did,
80
80
+
config.indexServer.host,
81
81
+
"whatever"
82
82
+
)
83
83
+
),
84
84
+
{
79
85
headers: withCors({ "Content-Type": "application/json" }),
80
80
-
});
81
81
-
}
82
82
-
if (pathname === "/health") {
83
83
-
return new Response("OK", {
84
84
-
status: 200,
85
85
-
headers: withCors({
86
86
-
"Content-Type": "text/plain",
87
87
-
}),
88
88
-
});
89
89
-
}
90
90
-
if (req.method === "OPTIONS") {
91
91
-
return new Response(null, {
92
92
-
status: 204,
93
93
-
headers: {
94
94
-
"Access-Control-Allow-Origin": "*",
95
95
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
96
96
-
"Access-Control-Allow-Headers": "*",
97
97
-
},
98
98
-
});
99
99
-
}
100
100
-
console.log(`request for "${pathname}"`)
101
101
-
const constellation = pathname.startsWith("/links")
102
102
-
103
103
-
if (constellation) {
104
104
-
const target = searchParams?.target as string
105
105
-
const safeDid = extractDid(target);
106
106
-
const targetserver = genericIndexServer.handlesDid(safeDid)
107
107
-
if (targetserver) {
108
108
-
return genericIndexServer.constellationAPIHandler(req);
109
109
-
} else {
110
110
-
return new Response(
111
111
-
JSON.stringify({
112
112
-
error: "User not found",
113
113
-
}),
114
114
-
{
115
115
-
status: 404,
116
116
-
headers: withCors({ "Content-Type": "application/json" }),
117
117
-
}
118
118
-
);
119
86
}
87
87
+
);
88
88
+
}
89
89
+
if (pathname === "/health") {
90
90
+
return new Response("OK", {
91
91
+
status: 200,
92
92
+
headers: withCors({
93
93
+
"Content-Type": "text/plain",
94
94
+
}),
95
95
+
});
96
96
+
}
97
97
+
if (req.method === "OPTIONS") {
98
98
+
return new Response(null, {
99
99
+
status: 204,
100
100
+
headers: {
101
101
+
"Access-Control-Allow-Origin": "*",
102
102
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
103
103
+
"Access-Control-Allow-Headers": "*",
104
104
+
},
105
105
+
});
106
106
+
}
107
107
+
console.log(`request for "${pathname}"`);
108
108
+
const constellation = pathname.startsWith("/links");
109
109
+
110
110
+
if (constellation) {
111
111
+
const target = searchParams?.target as string;
112
112
+
const safeDid = extractDid(target);
113
113
+
const targetserver = genericIndexServer.handlesDid(safeDid);
114
114
+
if (targetserver) {
115
115
+
return genericIndexServer.constellationAPIHandler(req);
120
116
} else {
121
121
-
// indexServerRoutes.has(pathname)
122
122
-
return genericIndexServer.indexServerHandler(req);
117
117
+
return new Response(
118
118
+
JSON.stringify({
119
119
+
error: "User not found",
120
120
+
}),
121
121
+
{
122
122
+
status: 404,
123
123
+
headers: withCors({ "Content-Type": "application/json" }),
124
124
+
}
125
125
+
);
123
126
}
127
127
+
} else {
128
128
+
// indexServerRoutes.has(pathname)
129
129
+
return await genericIndexServer.indexServerHandler(req);
124
130
}
125
125
-
);
131
131
+
});
+58
-10
main-view.ts
···
2
2
import { setupSystemDb } from "./utils/dbsystem.ts";
3
3
import { didDocument } from "./utils/diddoc.ts";
4
4
import { cachedFetch, searchParamsToJson, withCors } from "./utils/server.ts";
5
5
-
import { IndexServer, IndexServerConfig } from "./indexserver.ts"
5
5
+
import { ViewServer, ViewServerConfig } from "./viewserver.ts";
6
6
import { extractDid } from "./utils/identity.ts";
7
7
import { config } from "./config.ts";
8
8
-
import { viewServerHandler } from "./viewserver.ts";
9
8
10
9
// ------------------------------------------
11
10
// AppView Setup
···
17
16
//keyCacheTTL: 10 * 60 * 1000,
18
17
});
19
18
19
19
+
const viewServerConfig: ViewServerConfig = {
20
20
+
baseDbPath: "./dbs/view/registered-users", // The directory for user databases
21
21
+
systemDbPath: "./dbs/view/registered-users/system.db", // The path for the main system database
22
22
+
};
23
23
+
export const genericViewServer = new ViewServer(viewServerConfig);
24
24
+
setupSystemDb(genericViewServer.systemDB);
25
25
+
26
26
+
// add me lol
27
27
+
genericViewServer.systemDB.exec(`
28
28
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
29
29
+
VALUES (
30
30
+
'did:plc:mn45tewwnse5btfftvd3powc',
31
31
+
'admin',
32
32
+
datetime('now'),
33
33
+
'ready'
34
34
+
);
35
35
+
36
36
+
INSERT OR IGNORE INTO users (did, role, registrationdate, onboardingstatus)
37
37
+
VALUES (
38
38
+
'did:web:did12.whey.party',
39
39
+
'admin',
40
40
+
datetime('now'),
41
41
+
'ready'
42
42
+
);
43
43
+
`);
44
44
+
45
45
+
genericViewServer.start();
46
46
+
20
47
// ------------------------------------------
21
48
// XRPC Method Implementations
22
49
// ------------------------------------------
23
23
-
24
50
25
51
Deno.serve(
26
52
{ port: config.viewServer.port },
···
30
56
const searchParams = searchParamsToJson(url.searchParams);
31
57
32
58
if (pathname === "/.well-known/did.json") {
33
33
-
return new Response(JSON.stringify(didDocument), {
34
34
-
headers: withCors({ "Content-Type": "application/json" }),
35
35
-
});
59
59
+
return new Response(
60
60
+
JSON.stringify(
61
61
+
didDocument(
62
62
+
"view",
63
63
+
config.viewServer.did,
64
64
+
config.viewServer.host,
65
65
+
"whatever"
66
66
+
)
67
67
+
),
68
68
+
{
69
69
+
headers: withCors({ "Content-Type": "application/json" }),
70
70
+
}
71
71
+
);
36
72
}
37
73
if (pathname === "/health") {
38
74
return new Response("OK", {
···
52
88
},
53
89
});
54
90
}
55
55
-
console.log(`request for "${pathname}"`)
56
56
-
57
57
-
return await viewServerHandler(req)
91
91
+
console.log(`request for "${pathname}"`);
92
92
+
93
93
+
let authdid: string | undefined = undefined;
94
94
+
try {
95
95
+
authdid = (await getAuthenticatedDid(req)) ?? undefined;
96
96
+
} catch (_e) {
97
97
+
// nothing lol
98
98
+
}
99
99
+
const auth = authdid
100
100
+
? genericViewServer.handlesDid(authdid)
101
101
+
? authdid
102
102
+
: undefined
103
103
+
: undefined;
104
104
+
console.log("authed:", auth);
105
105
+
return await genericViewServer.viewServerHandler(req);
58
106
}
59
59
-
);
107
107
+
);
+2
-2
utils/auth.borrowed.ts
···
22
22
return { DidPlcResolver: resolve }
23
23
}
24
24
const myResolver = getResolver()
25
25
-
const web = getWebResolver()
25
25
+
const webResolver = getWebResolver()
26
26
const resolver: ResolverRegistry = {
27
27
'plc': myResolver.DidPlcResolver as unknown as DIDResolver,
28
28
-
'web': web as unknown as DIDResolver,
28
28
+
...webResolver
29
29
}
30
30
export const resolverInstance = new Resolver(resolver)
31
31
export type Service = {
+5
-1
utils/auth.ts
···
61
61
return null;
62
62
}
63
63
}
64
64
-
64
64
+
/**
65
65
+
* @deprecated dont use this use getAuthenticatedDid() instead
66
66
+
* @param param0
67
67
+
* @returns
68
68
+
*/
65
69
export const authVerifier: MethodAuthVerifier<AuthResult> = async ({ req }) => {
66
70
//console.log("help us all fuck you",req)
67
71
console.log("you are doing well")
+744
-433
viewserver.ts
···
13
13
} from "./utils/server.ts";
14
14
import { validateRecord } from "./utils/records.ts";
15
15
import { indexHandlerContext } from "./index/types.ts";
16
16
+
import { Database } from "jsr:@db/sqlite@0.11";
17
17
+
import { JetstreamManager, SpacedustManager } from "./utils/sharders.ts";
18
18
+
import { SpacedustLinkMessage } from "./index/spacedust.ts";
19
19
+
import { setupUserDb } from "./utils/dbuser.ts";
16
20
21
21
+
export interface ViewServerConfig {
22
22
+
baseDbPath: string;
23
23
+
systemDbPath: string;
24
24
+
}
17
25
18
18
-
export async function viewServerHandler(req: Request): Promise<Response> {
19
19
-
const url = new URL(req.url);
20
20
-
const pathname = url.pathname;
21
21
-
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
22
22
-
const hasAuth = req.headers.has("authorization");
23
23
-
const xrpcMethod = pathname.startsWith("/xrpc/")
24
24
-
? pathname.slice("/xrpc/".length)
25
25
-
: null;
26
26
-
const searchParams = searchParamsToJson(url.searchParams);
27
27
-
const jsonUntyped = searchParams;
26
26
+
interface BaseRow {
27
27
+
uri: string;
28
28
+
did: string;
29
29
+
cid: string | null;
30
30
+
rev: string | null;
31
31
+
createdat: number | null;
32
32
+
indexedat: number;
33
33
+
json: string | null;
34
34
+
}
35
35
+
interface GeneratorRow extends BaseRow {
36
36
+
displayname: string | null;
37
37
+
description: string | null;
38
38
+
avatarcid: string | null;
39
39
+
}
40
40
+
interface LikeRow extends BaseRow {
41
41
+
subject: string;
42
42
+
}
43
43
+
interface RepostRow extends BaseRow {
44
44
+
subject: string;
45
45
+
}
46
46
+
interface BacklinkRow {
47
47
+
srcuri: string;
48
48
+
srcdid: string;
49
49
+
}
28
50
29
29
-
if (xrpcMethod === "app.bsky.unspecced.getTrendingTopics") {
30
30
-
// const jsonTyped =
31
31
-
// jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
51
51
+
const FEED_LIMIT = 50;
32
52
33
33
-
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
34
34
-
{
35
35
-
$type: "app.bsky.unspecced.defs#trendingTopic",
36
36
-
topic: "Git Repo",
37
37
-
displayName: "Git Repo",
38
38
-
description: "Git Repo",
39
39
-
link: "https://tangled.sh/@whey.party/skylite",
40
40
-
},
41
41
-
{
42
42
-
$type: "app.bsky.unspecced.defs#trendingTopic",
43
43
-
topic: "Red Dwarf Lite",
44
44
-
displayName: "Red Dwarf Lite",
45
45
-
description: "Red Dwarf Lite",
46
46
-
link: "https://reddwarflite.whey.party/",
47
47
-
},
48
48
-
{
49
49
-
$type: "app.bsky.unspecced.defs#trendingTopic",
50
50
-
topic: "whey dot party",
51
51
-
displayName: "whey dot party",
52
52
-
description: "whey dot party",
53
53
-
link: "https://whey.party/",
54
54
-
},
55
55
-
];
53
53
+
export class ViewServer {
54
54
+
private config: ViewServerConfig;
55
55
+
public userManager: ViewServerUserManager;
56
56
+
public systemDB: Database;
56
57
57
57
-
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
58
58
-
{
59
59
-
topics: faketopics,
60
60
-
suggested: faketopics,
61
61
-
};
58
58
+
constructor(config: ViewServerConfig) {
59
59
+
this.config = config;
60
60
+
61
61
+
// We will initialize the system DB and user manager here
62
62
+
this.systemDB = new Database(this.config.systemDbPath);
63
63
+
// TODO: We need to setup the system DB schema if it's new
62
64
63
63
-
return new Response(JSON.stringify(response), {
64
64
-
headers: withCors({ "Content-Type": "application/json" }),
65
65
-
});
65
65
+
this.userManager = new ViewServerUserManager(this); // Pass the server instance
66
66
}
67
67
68
68
-
//if (xrpcMethod !== 'app.bsky.actor.getPreferences' && xrpcMethod !== 'app.bsky.notification.listNotifications') {
69
69
-
if (
70
70
-
!hasAuth
71
71
-
// (!hasAuth ||
72
72
-
// xrpcMethod === "app.bsky.labeler.getServices" ||
73
73
-
// xrpcMethod === "app.bsky.unspecced.getConfig") &&
74
74
-
// xrpcMethod !== "app.bsky.notification.putPreferences"
75
75
-
) {
76
76
-
return new Response(
77
77
-
JSON.stringify({
78
78
-
error: "XRPCNotSupported",
79
79
-
message:
80
80
-
"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",
81
81
-
}),
82
82
-
{
83
83
-
status: 404,
84
84
-
headers: withCors({ "Content-Type": "application/json" }),
85
85
-
}
86
86
-
);
87
87
-
//return await sendItToApiBskyApp(req);
68
68
+
public start() {
69
69
+
// This is where we'll kick things off, like the cold start
70
70
+
this.userManager.coldStart(this.systemDB);
71
71
+
console.log("viewServer started.");
88
72
}
89
89
-
if (
90
90
-
// !hasAuth ||
91
91
-
xrpcMethod === "app.bsky.labeler.getServices" ||
92
92
-
xrpcMethod === "app.bsky.unspecced.getConfig" //&&
93
93
-
//xrpcMethod !== "app.bsky.notification.putPreferences"
94
94
-
) {
95
95
-
return new Response(
96
96
-
JSON.stringify({
97
97
-
error: "XRPCNotSupported",
98
98
-
message:
99
99
-
"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",
100
100
-
}),
101
101
-
{
102
102
-
status: 404,
73
73
+
74
74
+
async viewServerHandler(req: Request): Promise<Response> {
75
75
+
const url = new URL(req.url);
76
76
+
const pathname = url.pathname;
77
77
+
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
78
78
+
const hasAuth = req.headers.has("authorization");
79
79
+
const xrpcMethod = pathname.startsWith("/xrpc/")
80
80
+
? pathname.slice("/xrpc/".length)
81
81
+
: null;
82
82
+
const searchParams = searchParamsToJson(url.searchParams);
83
83
+
const jsonUntyped = searchParams;
84
84
+
85
85
+
if (xrpcMethod === "app.bsky.unspecced.getTrendingTopics") {
86
86
+
// const jsonTyped =
87
87
+
// jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
88
88
+
89
89
+
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
90
90
+
{
91
91
+
$type: "app.bsky.unspecced.defs#trendingTopic",
92
92
+
topic: "Git Repo",
93
93
+
displayName: "Git Repo",
94
94
+
description: "Git Repo",
95
95
+
link: "https://tangled.sh/@whey.party/skylite",
96
96
+
},
97
97
+
{
98
98
+
$type: "app.bsky.unspecced.defs#trendingTopic",
99
99
+
topic: "Red Dwarf Lite",
100
100
+
displayName: "Red Dwarf Lite",
101
101
+
description: "Red Dwarf Lite",
102
102
+
link: "https://reddwarflite.whey.party/",
103
103
+
},
104
104
+
{
105
105
+
$type: "app.bsky.unspecced.defs#trendingTopic",
106
106
+
topic: "whey dot party",
107
107
+
displayName: "whey dot party",
108
108
+
description: "whey dot party",
109
109
+
link: "https://whey.party/",
110
110
+
},
111
111
+
];
112
112
+
113
113
+
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
114
114
+
{
115
115
+
topics: faketopics,
116
116
+
suggested: faketopics,
117
117
+
};
118
118
+
119
119
+
return new Response(JSON.stringify(response), {
103
120
headers: withCors({ "Content-Type": "application/json" }),
104
104
-
}
105
105
-
);
106
106
-
//return await sendItToApiBskyApp(req);
107
107
-
}
121
121
+
});
122
122
+
}
108
123
109
109
-
const authDID = "did:plc:mn45tewwnse5btfftvd3powc"; //getAuthenticatedDid(req);
124
124
+
//if (xrpcMethod !== 'app.bsky.actor.getPreferences' && xrpcMethod !== 'app.bsky.notification.listNotifications') {
125
125
+
if (
126
126
+
!hasAuth
127
127
+
// (!hasAuth ||
128
128
+
// xrpcMethod === "app.bsky.labeler.getServices" ||
129
129
+
// xrpcMethod === "app.bsky.unspecced.getConfig") &&
130
130
+
// xrpcMethod !== "app.bsky.notification.putPreferences"
131
131
+
) {
132
132
+
return new Response(
133
133
+
JSON.stringify({
134
134
+
error: "XRPCNotSupported",
135
135
+
message:
136
136
+
"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",
137
137
+
}),
138
138
+
{
139
139
+
status: 404,
140
140
+
headers: withCors({ "Content-Type": "application/json" }),
141
141
+
}
142
142
+
);
143
143
+
//return await sendItToApiBskyApp(req);
144
144
+
}
145
145
+
if (
146
146
+
// !hasAuth ||
147
147
+
xrpcMethod === "app.bsky.labeler.getServices" ||
148
148
+
xrpcMethod === "app.bsky.unspecced.getConfig" //&&
149
149
+
//xrpcMethod !== "app.bsky.notification.putPreferences"
150
150
+
) {
151
151
+
return new Response(
152
152
+
JSON.stringify({
153
153
+
error: "XRPCNotSupported",
154
154
+
message:
155
155
+
"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",
156
156
+
}),
157
157
+
{
158
158
+
status: 404,
159
159
+
headers: withCors({ "Content-Type": "application/json" }),
160
160
+
}
161
161
+
);
162
162
+
//return await sendItToApiBskyApp(req);
163
163
+
}
164
164
+
165
165
+
const authDID = "did:plc:mn45tewwnse5btfftvd3powc"; //getAuthenticatedDid(req);
110
166
111
111
-
switch (xrpcMethod) {
112
112
-
case "app.bsky.feed.getFeedGenerators": {
113
113
-
const jsonTyped =
114
114
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
167
167
+
switch (xrpcMethod) {
168
168
+
case "app.bsky.feed.getFeedGenerators": {
169
169
+
const jsonTyped =
170
170
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeedGenerators.QueryParams;
115
171
116
116
-
const feeds: ATPAPI.AppBskyFeedDefs.GeneratorView[] = (
117
117
-
await Promise.all(
118
118
-
jsonTyped.feeds.map(async (feed) => {
119
119
-
try {
120
120
-
const did = new ATPAPI.AtUri(feed).hostname;
121
121
-
const rkey = new ATPAPI.AtUri(feed).rkey;
122
122
-
const identity = await resolveIdentity(did);
123
123
-
const feedgetRecord = await getSlingshotRecord(
124
124
-
identity.did,
125
125
-
"app.bsky.feed.generator",
126
126
-
rkey
127
127
-
);
128
128
-
const profile = (
129
129
-
await getSlingshotRecord(
172
172
+
const feeds: ATPAPI.AppBskyFeedDefs.GeneratorView[] = (
173
173
+
await Promise.all(
174
174
+
jsonTyped.feeds.map(async (feed) => {
175
175
+
try {
176
176
+
const did = new ATPAPI.AtUri(feed).hostname;
177
177
+
const rkey = new ATPAPI.AtUri(feed).rkey;
178
178
+
const identity = await resolveIdentity(did);
179
179
+
const feedgetRecord = await getSlingshotRecord(
130
180
identity.did,
131
131
-
"app.bsky.actor.profile",
132
132
-
"self"
133
133
-
)
134
134
-
).value as ATPAPI.AppBskyActorProfile.Record;
135
135
-
const anyprofile = profile as any;
136
136
-
const value =
137
137
-
feedgetRecord.value as ATPAPI.AppBskyFeedGenerator.Record;
181
181
+
"app.bsky.feed.generator",
182
182
+
rkey
183
183
+
);
184
184
+
const profile = (
185
185
+
await getSlingshotRecord(
186
186
+
identity.did,
187
187
+
"app.bsky.actor.profile",
188
188
+
"self"
189
189
+
)
190
190
+
).value as ATPAPI.AppBskyActorProfile.Record;
191
191
+
const anyprofile = profile as any;
192
192
+
const value =
193
193
+
feedgetRecord.value as ATPAPI.AppBskyFeedGenerator.Record;
138
194
139
139
-
return {
140
140
-
$type: "app.bsky.feed.defs#generatorView",
141
141
-
uri: feed,
142
142
-
cid: feedgetRecord.cid,
143
143
-
did: identity.did,
144
144
-
creator: /*AppBskyActorDefs.ProfileView*/ {
145
145
-
$type: "app.bsky.actor.defs#profileView",
195
195
+
return {
196
196
+
$type: "app.bsky.feed.defs#generatorView",
197
197
+
uri: feed,
198
198
+
cid: feedgetRecord.cid,
146
199
did: identity.did,
147
147
-
handle: identity.handle,
148
148
-
displayName: profile.displayName,
149
149
-
description: profile.description,
200
200
+
creator: /*AppBskyActorDefs.ProfileView*/ {
201
201
+
$type: "app.bsky.actor.defs#profileView",
202
202
+
did: identity.did,
203
203
+
handle: identity.handle,
204
204
+
displayName: profile.displayName,
205
205
+
description: profile.description,
206
206
+
avatar: buildBlobUrl(
207
207
+
identity.pds,
208
208
+
identity.did,
209
209
+
anyprofile.avatar.ref["$link"]
210
210
+
),
211
211
+
//associated?: ProfileAssociated
212
212
+
//indexedAt?: string
213
213
+
//createdAt?: string
214
214
+
//viewer?: ViewerState
215
215
+
//labels?: ComAtprotoLabelDefs.Label[]
216
216
+
//verification?: VerificationState
217
217
+
//status?: StatusView
218
218
+
},
219
219
+
displayName: value.displayName,
220
220
+
description: value.description,
221
221
+
//descriptionFacets?: AppBskyRichtextFacet.Main[]
150
222
avatar: buildBlobUrl(
151
223
identity.pds,
152
224
identity.did,
153
153
-
anyprofile.avatar.ref["$link"]
225
225
+
(value as any).avatar.ref["$link"]
154
226
),
155
155
-
//associated?: ProfileAssociated
156
156
-
//indexedAt?: string
157
157
-
//createdAt?: string
158
158
-
//viewer?: ViewerState
227
227
+
//likeCount?: number
228
228
+
//acceptsInteractions?: boolean
159
229
//labels?: ComAtprotoLabelDefs.Label[]
160
160
-
//verification?: VerificationState
161
161
-
//status?: StatusView
162
162
-
},
163
163
-
displayName: value.displayName,
164
164
-
description: value.description,
165
165
-
//descriptionFacets?: AppBskyRichtextFacet.Main[]
166
166
-
avatar: buildBlobUrl(
167
167
-
identity.pds,
168
168
-
identity.did,
169
169
-
(value as any).avatar.ref["$link"]
170
170
-
),
171
171
-
//likeCount?: number
172
172
-
//acceptsInteractions?: boolean
173
173
-
//labels?: ComAtprotoLabelDefs.Label[]
174
174
-
//viewer?: GeneratorViewerState
175
175
-
contentMode: value.contentMode,
176
176
-
indexedAt: new Date().toISOString(),
177
177
-
};
178
178
-
} catch (err) {
179
179
-
return undefined;
180
180
-
}
181
181
-
})
182
182
-
)
183
183
-
).filter(isGeneratorView);
230
230
+
//viewer?: GeneratorViewerState
231
231
+
contentMode: value.contentMode,
232
232
+
indexedAt: new Date().toISOString(),
233
233
+
};
234
234
+
} catch (err) {
235
235
+
return undefined;
236
236
+
}
237
237
+
})
238
238
+
)
239
239
+
).filter(isGeneratorView);
184
240
185
185
-
const response: ViewServerTypes.AppBskyFeedGetFeedGenerators.OutputSchema =
186
186
-
{
187
187
-
feeds: feeds ? feeds : [],
188
188
-
};
241
241
+
const response: ViewServerTypes.AppBskyFeedGetFeedGenerators.OutputSchema =
242
242
+
{
243
243
+
feeds: feeds ? feeds : [],
244
244
+
};
189
245
190
190
-
return new Response(JSON.stringify(response), {
191
191
-
headers: withCors({ "Content-Type": "application/json" }),
192
192
-
});
193
193
-
}
194
194
-
case "app.bsky.feed.getFeed": {
195
195
-
const jsonTyped =
196
196
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeed.QueryParams;
197
197
-
const cursor = jsonTyped.cursor;
198
198
-
const feed = jsonTyped.feed;
199
199
-
const limit = jsonTyped.limit;
200
200
-
const proxyauth = req.headers.get("authorization") || "";
246
246
+
return new Response(JSON.stringify(response), {
247
247
+
headers: withCors({ "Content-Type": "application/json" }),
248
248
+
});
249
249
+
}
250
250
+
case "app.bsky.feed.getFeed": {
251
251
+
const jsonTyped =
252
252
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetFeed.QueryParams;
253
253
+
const cursor = jsonTyped.cursor;
254
254
+
const feed = jsonTyped.feed;
255
255
+
const limit = jsonTyped.limit;
256
256
+
const proxyauth = req.headers.get("authorization") || "";
201
257
202
202
-
const did = new ATPAPI.AtUri(feed).hostname;
203
203
-
const rkey = new ATPAPI.AtUri(feed).rkey;
204
204
-
const identity = await resolveIdentity(did);
205
205
-
const feedgetRecord = (
206
206
-
await getSlingshotRecord(identity.did, "app.bsky.feed.generator", rkey)
207
207
-
).value as ATPAPI.AppBskyFeedGenerator.Record;
258
258
+
const did = new ATPAPI.AtUri(feed).hostname;
259
259
+
const rkey = new ATPAPI.AtUri(feed).rkey;
260
260
+
const identity = await resolveIdentity(did);
261
261
+
const feedgetRecord = (
262
262
+
await getSlingshotRecord(
263
263
+
identity.did,
264
264
+
"app.bsky.feed.generator",
265
265
+
rkey
266
266
+
)
267
267
+
).value as ATPAPI.AppBskyFeedGenerator.Record;
208
268
209
209
-
const skeleton = (await cachedFetch(
210
210
-
`${didWebToHttps(
211
211
-
feedgetRecord.did
212
212
-
)}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${jsonTyped.feed}${
213
213
-
cursor ? `&cursor=${cursor}` : ""
214
214
-
}${limit ? `&limit=${limit}` : ""}`,
215
215
-
proxyauth
216
216
-
)) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
269
269
+
const skeleton = (await cachedFetch(
270
270
+
`${didWebToHttps(
271
271
+
feedgetRecord.did
272
272
+
)}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${jsonTyped.feed}${
273
273
+
cursor ? `&cursor=${cursor}` : ""
274
274
+
}${limit ? `&limit=${limit}` : ""}`,
275
275
+
proxyauth
276
276
+
)) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
217
277
218
218
-
const nextcursor = skeleton.cursor;
219
219
-
const dbgrqstid = skeleton.reqId;
220
220
-
const uriarray = skeleton.feed;
278
278
+
const nextcursor = skeleton.cursor;
279
279
+
const dbgrqstid = skeleton.reqId;
280
280
+
const uriarray = skeleton.feed;
221
281
222
222
-
// Step 1: Chunk into 25 max
223
223
-
const chunks = [];
224
224
-
for (let i = 0; i < uriarray.length; i += 25) {
225
225
-
chunks.push(uriarray.slice(i, i + 25));
226
226
-
}
282
282
+
// Step 1: Chunk into 25 max
283
283
+
const chunks = [];
284
284
+
for (let i = 0; i < uriarray.length; i += 25) {
285
285
+
chunks.push(uriarray.slice(i, i + 25));
286
286
+
}
227
287
228
228
-
// Step 2: Hydrate via getPosts
229
229
-
const hydratedPosts: ATPAPI.AppBskyFeedDefs.FeedViewPost[] = [];
288
288
+
// Step 2: Hydrate via getPosts
289
289
+
const hydratedPosts: ATPAPI.AppBskyFeedDefs.FeedViewPost[] = [];
230
290
231
231
-
for (const chunk of chunks) {
232
232
-
const searchParams = new URLSearchParams();
233
233
-
for (const uri of chunk.map((item) => item.post)) {
234
234
-
searchParams.append("uris", uri);
235
235
-
}
291
291
+
for (const chunk of chunks) {
292
292
+
const searchParams = new URLSearchParams();
293
293
+
for (const uri of chunk.map((item) => item.post)) {
294
294
+
searchParams.append("uris", uri);
295
295
+
}
236
296
237
237
-
const postResp = await ky
238
238
-
.get(`https://api.bsky.app/xrpc/app.bsky.feed.getPosts`, {
239
239
-
// headers: {
240
240
-
// Authorization: proxyauth,
241
241
-
// },
242
242
-
searchParams,
243
243
-
})
244
244
-
.json<ATPAPI.AppBskyFeedGetPosts.OutputSchema>();
297
297
+
const postResp = await ky
298
298
+
.get(`https://api.bsky.app/xrpc/app.bsky.feed.getPosts`, {
299
299
+
// headers: {
300
300
+
// Authorization: proxyauth,
301
301
+
// },
302
302
+
searchParams,
303
303
+
})
304
304
+
.json<ATPAPI.AppBskyFeedGetPosts.OutputSchema>();
245
305
246
246
-
for (const post of postResp.posts) {
247
247
-
const matchingSkeleton = uriarray.find(
248
248
-
(item) => item.post === post.uri
249
249
-
);
250
250
-
if (matchingSkeleton) {
251
251
-
//post.author.handle = post.author.handle + ".percent40.api.bsky.app"; // or any logic to modify it
252
252
-
hydratedPosts.push({
253
253
-
post,
254
254
-
reason: matchingSkeleton.reason,
255
255
-
//reply: matchingSkeleton,
256
256
-
});
306
306
+
for (const post of postResp.posts) {
307
307
+
const matchingSkeleton = uriarray.find(
308
308
+
(item) => item.post === post.uri
309
309
+
);
310
310
+
if (matchingSkeleton) {
311
311
+
//post.author.handle = post.author.handle + ".percent40.api.bsky.app"; // or any logic to modify it
312
312
+
hydratedPosts.push({
313
313
+
post,
314
314
+
reason: matchingSkeleton.reason,
315
315
+
//reply: matchingSkeleton,
316
316
+
});
317
317
+
}
257
318
}
258
319
}
320
320
+
321
321
+
// Step 3: Compose final response
322
322
+
const response: ViewServerTypes.AppBskyFeedGetFeed.OutputSchema = {
323
323
+
feed: hydratedPosts,
324
324
+
cursor: nextcursor,
325
325
+
};
326
326
+
327
327
+
return new Response(JSON.stringify(response), {
328
328
+
headers: withCors({ "Content-Type": "application/json" }),
329
329
+
});
259
330
}
331
331
+
case "app.bsky.actor.getProfile": {
332
332
+
const jsonTyped =
333
333
+
jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
260
334
261
261
-
// Step 3: Compose final response
262
262
-
const response: ViewServerTypes.AppBskyFeedGetFeed.OutputSchema = {
263
263
-
feed: hydratedPosts,
264
264
-
cursor: nextcursor,
265
265
-
};
335
335
+
const userindexservice = "";
336
336
+
const isbskyfallback = true;
337
337
+
if (isbskyfallback) {
338
338
+
return this.sendItToApiBskyApp(req);
339
339
+
}
266
340
267
267
-
return new Response(JSON.stringify(response), {
268
268
-
headers: withCors({ "Content-Type": "application/json" }),
269
269
-
});
270
270
-
}
271
271
-
case "app.bsky.actor.getProfile": {
272
272
-
const jsonTyped =
273
273
-
jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
341
341
+
const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema =
342
342
+
{};
274
343
275
275
-
const userindexservice = "";
276
276
-
const isbskyfallback = true;
277
277
-
if (isbskyfallback) {
278
278
-
return sendItToApiBskyApp(req);
344
344
+
return new Response(JSON.stringify(response), {
345
345
+
headers: withCors({ "Content-Type": "application/json" }),
346
346
+
});
279
347
}
280
348
281
281
-
const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema = {};
349
349
+
case "app.bsky.actor.getProfiles": {
350
350
+
const jsonTyped =
351
351
+
jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
282
352
283
283
-
return new Response(JSON.stringify(response), {
284
284
-
headers: withCors({ "Content-Type": "application/json" }),
285
285
-
});
286
286
-
}
353
353
+
const userindexservice = "";
354
354
+
const isbskyfallback = true;
355
355
+
if (isbskyfallback) {
356
356
+
return this.sendItToApiBskyApp(req);
357
357
+
}
287
358
288
288
-
case "app.bsky.actor.getProfiles": {
289
289
-
const jsonTyped =
290
290
-
jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
359
359
+
const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema =
360
360
+
{};
291
361
292
292
-
const userindexservice = "";
293
293
-
const isbskyfallback = true;
294
294
-
if (isbskyfallback) {
295
295
-
return sendItToApiBskyApp(req);
362
362
+
return new Response(JSON.stringify(response), {
363
363
+
headers: withCors({ "Content-Type": "application/json" }),
364
364
+
});
296
365
}
366
366
+
case "app.bsky.feed.getAuthorFeed": {
367
367
+
const jsonTyped =
368
368
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
297
369
298
298
-
const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
370
370
+
const userindexservice = "";
371
371
+
const isbskyfallback = true;
372
372
+
if (isbskyfallback) {
373
373
+
return this.sendItToApiBskyApp(req);
374
374
+
}
299
375
300
300
-
return new Response(JSON.stringify(response), {
301
301
-
headers: withCors({ "Content-Type": "application/json" }),
302
302
-
});
303
303
-
}
304
304
-
case "app.bsky.feed.getAuthorFeed": {
305
305
-
const jsonTyped =
306
306
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
376
376
+
const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema =
377
377
+
{};
307
378
308
308
-
const userindexservice = "";
309
309
-
const isbskyfallback = true;
310
310
-
if (isbskyfallback) {
311
311
-
return sendItToApiBskyApp(req);
379
379
+
return new Response(JSON.stringify(response), {
380
380
+
headers: withCors({ "Content-Type": "application/json" }),
381
381
+
});
312
382
}
383
383
+
case "app.bsky.feed.getPostThread": {
384
384
+
const jsonTyped =
385
385
+
jsonUntyped as ViewServerTypes.AppBskyFeedGetPostThread.QueryParams;
313
386
314
314
-
const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema =
315
315
-
{};
387
387
+
const userindexservice = "";
388
388
+
const isbskyfallback = true;
389
389
+
if (isbskyfallback) {
390
390
+
return this.sendItToApiBskyApp(req);
391
391
+
}
316
392
317
317
-
return new Response(JSON.stringify(response), {
318
318
-
headers: withCors({ "Content-Type": "application/json" }),
319
319
-
});
320
320
-
}
321
321
-
case "app.bsky.feed.getPostThread": {
322
322
-
const jsonTyped =
323
323
-
jsonUntyped as ViewServerTypes.AppBskyFeedGetPostThread.QueryParams;
393
393
+
const response: ViewServerTypes.AppBskyFeedGetPostThread.OutputSchema =
394
394
+
{};
324
395
325
325
-
const userindexservice = "";
326
326
-
const isbskyfallback = true;
327
327
-
if (isbskyfallback) {
328
328
-
return sendItToApiBskyApp(req);
396
396
+
return new Response(JSON.stringify(response), {
397
397
+
headers: withCors({ "Content-Type": "application/json" }),
398
398
+
});
329
399
}
400
400
+
case "app.bsky.unspecced.getPostThreadV2": {
401
401
+
const jsonTyped =
402
402
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.QueryParams;
330
403
331
331
-
const response: ViewServerTypes.AppBskyFeedGetPostThread.OutputSchema =
332
332
-
{};
404
404
+
const userindexservice = "";
405
405
+
const isbskyfallback = true;
406
406
+
if (isbskyfallback) {
407
407
+
return this.sendItToApiBskyApp(req);
408
408
+
}
333
409
334
334
-
return new Response(JSON.stringify(response), {
335
335
-
headers: withCors({ "Content-Type": "application/json" }),
336
336
-
});
337
337
-
}
338
338
-
case "app.bsky.unspecced.getPostThreadV2": {
339
339
-
const jsonTyped =
340
340
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.QueryParams;
410
410
+
const response: ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.OutputSchema =
411
411
+
{};
341
412
342
342
-
const userindexservice = "";
343
343
-
const isbskyfallback = true;
344
344
-
if (isbskyfallback) {
345
345
-
return sendItToApiBskyApp(req);
413
413
+
return new Response(JSON.stringify(response), {
414
414
+
headers: withCors({ "Content-Type": "application/json" }),
415
415
+
});
346
416
}
347
417
348
348
-
const response: ViewServerTypes.AppBskyUnspeccedGetPostThreadV2.OutputSchema =
349
349
-
{};
418
418
+
// case "app.bsky.actor.getProfile": {
419
419
+
// const jsonTyped =
420
420
+
// jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
421
421
+
422
422
+
// const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema= {};
423
423
+
424
424
+
// return new Response(JSON.stringify(response), {
425
425
+
// headers: withCors({ "Content-Type": "application/json" }),
426
426
+
// });
427
427
+
// }
428
428
+
// case "app.bsky.actor.getProfiles": {
429
429
+
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
350
430
351
351
-
return new Response(JSON.stringify(response), {
352
352
-
headers: withCors({ "Content-Type": "application/json" }),
353
353
-
});
354
354
-
}
431
431
+
// const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
355
432
356
356
-
// case "app.bsky.actor.getProfile": {
357
357
-
// const jsonTyped =
358
358
-
// jsonUntyped as ViewServerTypes.AppBskyActorGetProfile.QueryParams;
433
433
+
// return new Response(JSON.stringify(response), {
434
434
+
// headers: withCors({ "Content-Type": "application/json" }),
435
435
+
// });
436
436
+
// }
437
437
+
// case "whatever": {
438
438
+
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
359
439
360
360
-
// const response: ViewServerTypes.AppBskyActorGetProfile.OutputSchema= {};
440
440
+
// const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema = {}
361
441
362
362
-
// return new Response(JSON.stringify(response), {
363
363
-
// headers: withCors({ "Content-Type": "application/json" }),
364
364
-
// });
365
365
-
// }
366
366
-
// case "app.bsky.actor.getProfiles": {
367
367
-
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyActorGetProfiles.QueryParams;
442
442
+
// return new Response(JSON.stringify(response), {
443
443
+
// headers: withCors({ "Content-Type": "application/json" }),
444
444
+
// });
445
445
+
// }
446
446
+
// case "app.bsky.notification.listNotifications": {
447
447
+
// const jsonTyped =
448
448
+
// jsonUntyped as ViewServerTypes.AppBskyNotificationListNotifications.QueryParams;
368
449
369
369
-
// const response: ViewServerTypes.AppBskyActorGetProfiles.OutputSchema = {};
450
450
+
// const response: ViewServerTypes.AppBskyNotificationListNotifications.OutputSchema = {};
370
451
371
371
-
// return new Response(JSON.stringify(response), {
372
372
-
// headers: withCors({ "Content-Type": "application/json" }),
373
373
-
// });
374
374
-
// }
375
375
-
// case "whatever": {
376
376
-
// const jsonTyped = jsonUntyped as ViewServerTypes.AppBskyFeedGetAuthorFeed.QueryParams;
452
452
+
// return new Response(JSON.stringify(response), {
453
453
+
// headers: withCors({ "Content-Type": "application/json" }),
454
454
+
// });
455
455
+
// }
377
456
378
378
-
// const response: ViewServerTypes.AppBskyFeedGetAuthorFeed.OutputSchema = {}
457
457
+
case "app.bsky.unspecced.getConfig": {
458
458
+
const jsonTyped =
459
459
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetConfig.QueryParams;
379
460
380
380
-
// return new Response(JSON.stringify(response), {
381
381
-
// headers: withCors({ "Content-Type": "application/json" }),
382
382
-
// });
383
383
-
// }
384
384
-
// case "app.bsky.notification.listNotifications": {
385
385
-
// const jsonTyped =
386
386
-
// jsonUntyped as ViewServerTypes.AppBskyNotificationListNotifications.QueryParams;
461
461
+
const response: ViewServerTypes.AppBskyUnspeccedGetConfig.OutputSchema =
462
462
+
{
463
463
+
checkEmailConfirmed: true,
464
464
+
liveNow: [
465
465
+
{
466
466
+
$type: "app.bsky.unspecced.getConfig#liveNowConfig",
467
467
+
did: "did:plc:mn45tewwnse5btfftvd3powc",
468
468
+
domains: ["local3768forumtest.whey.party"],
469
469
+
},
470
470
+
],
471
471
+
};
387
472
388
388
-
// const response: ViewServerTypes.AppBskyNotificationListNotifications.OutputSchema = {};
473
473
+
return new Response(JSON.stringify(response), {
474
474
+
headers: withCors({ "Content-Type": "application/json" }),
475
475
+
});
476
476
+
}
477
477
+
case "app.bsky.graph.getLists": {
478
478
+
const jsonTyped =
479
479
+
jsonUntyped as ViewServerTypes.AppBskyGraphGetLists.QueryParams;
389
480
390
390
-
// return new Response(JSON.stringify(response), {
391
391
-
// headers: withCors({ "Content-Type": "application/json" }),
392
392
-
// });
393
393
-
// }
481
481
+
const response: ViewServerTypes.AppBskyGraphGetLists.OutputSchema = {
482
482
+
lists: [],
483
483
+
};
394
484
395
395
-
case "app.bsky.unspecced.getConfig": {
396
396
-
const jsonTyped =
397
397
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetConfig.QueryParams;
485
485
+
return new Response(JSON.stringify(response), {
486
486
+
headers: withCors({ "Content-Type": "application/json" }),
487
487
+
});
488
488
+
}
489
489
+
//https://shimeji.us-east.host.bsky.network/xrpc/app.bsky.unspecced.getTrendingTopics?limit=14
490
490
+
case "app.bsky.unspecced.getTrendingTopics": {
491
491
+
const jsonTyped =
492
492
+
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
398
493
399
399
-
const response: ViewServerTypes.AppBskyUnspeccedGetConfig.OutputSchema = {
400
400
-
checkEmailConfirmed: true,
401
401
-
liveNow: [
494
494
+
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
402
495
{
403
403
-
$type: "app.bsky.unspecced.getConfig#liveNowConfig",
404
404
-
did: "did:plc:mn45tewwnse5btfftvd3powc",
405
405
-
domains: ["local3768forumtest.whey.party"],
496
496
+
$type: "app.bsky.unspecced.defs#trendingTopic",
497
497
+
topic: "Git Repo",
498
498
+
displayName: "Git Repo",
499
499
+
description: "Git Repo",
500
500
+
link: "https://tangled.sh/@whey.party/skylite",
406
501
},
407
407
-
],
408
408
-
};
502
502
+
{
503
503
+
$type: "app.bsky.unspecced.defs#trendingTopic",
504
504
+
topic: "Red Dwarf Lite",
505
505
+
displayName: "Red Dwarf Lite",
506
506
+
description: "Red Dwarf Lite",
507
507
+
link: "https://reddwarf.whey.party/",
508
508
+
},
509
509
+
{
510
510
+
$type: "app.bsky.unspecced.defs#trendingTopic",
511
511
+
topic: "whey dot party",
512
512
+
displayName: "whey dot party",
513
513
+
description: "whey dot party",
514
514
+
link: "https://whey.party/",
515
515
+
},
516
516
+
];
517
517
+
518
518
+
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
519
519
+
{
520
520
+
topics: faketopics,
521
521
+
suggested: faketopics,
522
522
+
};
409
523
410
410
-
return new Response(JSON.stringify(response), {
411
411
-
headers: withCors({ "Content-Type": "application/json" }),
412
412
-
});
524
524
+
return new Response(JSON.stringify(response), {
525
525
+
headers: withCors({ "Content-Type": "application/json" }),
526
526
+
});
527
527
+
}
528
528
+
default: {
529
529
+
return new Response(
530
530
+
JSON.stringify({
531
531
+
error: "XRPCNotSupported",
532
532
+
message:
533
533
+
"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",
534
534
+
}),
535
535
+
{
536
536
+
status: 404,
537
537
+
headers: withCors({ "Content-Type": "application/json" }),
538
538
+
}
539
539
+
);
540
540
+
}
413
541
}
414
414
-
case "app.bsky.graph.getLists": {
415
415
-
const jsonTyped =
416
416
-
jsonUntyped as ViewServerTypes.AppBskyGraphGetLists.QueryParams;
417
542
418
418
-
const response: ViewServerTypes.AppBskyGraphGetLists.OutputSchema = {
419
419
-
lists: [],
420
420
-
};
543
543
+
// return new Response("Not Found", { status: 404 });
544
544
+
}
421
545
422
422
-
return new Response(JSON.stringify(response), {
423
423
-
headers: withCors({ "Content-Type": "application/json" }),
424
424
-
});
546
546
+
async sendItToApiBskyApp(req: Request): Promise<Response> {
547
547
+
const url = new URL(req.url);
548
548
+
const pathname = url.pathname;
549
549
+
const searchParams = searchParamsToJson(url.searchParams);
550
550
+
let reqBody: undefined | string;
551
551
+
let jsonbody: undefined | Record<string, unknown>;
552
552
+
if (req.body) {
553
553
+
const body = await req.json();
554
554
+
jsonbody = body;
555
555
+
// console.log(
556
556
+
// `called at euh reqreqreqreq: ${pathname}\n\n${JSON.stringify(body)}`
557
557
+
// );
558
558
+
reqBody = JSON.stringify(body, null, 2);
425
559
}
426
426
-
//https://shimeji.us-east.host.bsky.network/xrpc/app.bsky.unspecced.getTrendingTopics?limit=14
427
427
-
case "app.bsky.unspecced.getTrendingTopics": {
428
428
-
const jsonTyped =
429
429
-
jsonUntyped as ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.QueryParams;
560
560
+
const bskyUrl = `https://public.api.bsky.app${pathname}${url.search}`;
561
561
+
console.log("request", searchParams);
562
562
+
const proxyHeaders = new Headers(req.headers);
563
563
+
564
564
+
// Remove Authorization and set browser-like User-Agent
565
565
+
proxyHeaders.delete("authorization");
566
566
+
proxyHeaders.delete("Access-Control-Allow-Origin"),
567
567
+
proxyHeaders.set(
568
568
+
"user-agent",
569
569
+
"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"
570
570
+
);
571
571
+
proxyHeaders.set("Access-Control-Allow-Origin", "*");
572
572
+
573
573
+
const proxyRes = await fetch(bskyUrl, {
574
574
+
method: req.method,
575
575
+
headers: proxyHeaders,
576
576
+
body: ["GET", "HEAD"].includes(req.method.toUpperCase())
577
577
+
? undefined
578
578
+
: reqBody,
579
579
+
});
580
580
+
581
581
+
const resBody = await proxyRes.text();
430
582
431
431
-
const faketopics: ATPAPI.AppBskyUnspeccedDefs.TrendingTopic[] = [
432
432
-
{
433
433
-
$type: "app.bsky.unspecced.defs#trendingTopic",
434
434
-
topic: "Git Repo",
435
435
-
displayName: "Git Repo",
436
436
-
description: "Git Repo",
437
437
-
link: "https://tangled.sh/@whey.party/skylite",
438
438
-
},
439
439
-
{
440
440
-
$type: "app.bsky.unspecced.defs#trendingTopic",
441
441
-
topic: "Red Dwarf Lite",
442
442
-
displayName: "Red Dwarf Lite",
443
443
-
description: "Red Dwarf Lite",
444
444
-
link: "https://reddwarf.whey.party/",
445
445
-
},
446
446
-
{
447
447
-
$type: "app.bsky.unspecced.defs#trendingTopic",
448
448
-
topic: "whey dot party",
449
449
-
displayName: "whey dot party",
450
450
-
description: "whey dot party",
451
451
-
link: "https://whey.party/",
452
452
-
},
453
453
-
];
583
583
+
// console.log(
584
584
+
// "← Response:",
585
585
+
// JSON.stringify(await JSON.parse(resBody), null, 2)
586
586
+
// );
454
587
455
455
-
const response: ViewServerTypes.AppBskyUnspeccedGetTrendingTopics.OutputSchema =
456
456
-
{
457
457
-
topics: faketopics,
458
458
-
suggested: faketopics,
459
459
-
};
588
588
+
return new Response(resBody, {
589
589
+
status: proxyRes.status,
590
590
+
headers: proxyRes.headers,
591
591
+
});
592
592
+
}
460
593
461
461
-
return new Response(JSON.stringify(response), {
462
462
-
headers: withCors({ "Content-Type": "application/json" }),
463
463
-
});
464
464
-
}
465
465
-
default: {
466
466
-
return new Response(
467
467
-
JSON.stringify({
468
468
-
error: "XRPCNotSupported",
469
469
-
message:
470
470
-
"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",
471
471
-
}),
472
472
-
{
473
473
-
status: 404,
474
474
-
headers: withCors({ "Content-Type": "application/json" }),
475
475
-
}
476
476
-
);
594
594
+
viewServerIndexer(ctx: indexHandlerContext) {
595
595
+
const record = validateRecord(ctx.value);
596
596
+
switch (record?.$type) {
597
597
+
case "app.bsky.feed.like": {
598
598
+
return;
599
599
+
}
600
600
+
default: {
601
601
+
// what the hell
602
602
+
return;
603
603
+
}
477
604
}
478
605
}
479
606
480
480
-
// return new Response("Not Found", { status: 404 });
607
607
+
/**
608
608
+
* please do not use this, use openDbForDid() instead
609
609
+
* @param did
610
610
+
* @returns
611
611
+
*/
612
612
+
internalCreateDbForDid(did: string): Database {
613
613
+
const path = `${this.config.baseDbPath}/${did}.sqlite`;
614
614
+
const db = new Database(path);
615
615
+
// TODO maybe split the user db schema between view server and index server
616
616
+
setupUserDb(db);
617
617
+
//await db.exec(/* CREATE IF NOT EXISTS statements */);
618
618
+
return db;
619
619
+
}
620
620
+
public handlesDid(did: string): boolean {
621
621
+
return this.userManager.handlesDid(did);
622
622
+
}
481
623
}
482
624
483
483
-
async function sendItToApiBskyApp(req: Request): Promise<Response> {
484
484
-
const url = new URL(req.url);
485
485
-
const pathname = url.pathname;
486
486
-
const searchParams = searchParamsToJson(url.searchParams);
487
487
-
let reqBody: undefined | string;
488
488
-
let jsonbody: undefined | Record<string, unknown>;
489
489
-
if (req.body) {
490
490
-
const body = await req.json();
491
491
-
jsonbody = body;
492
492
-
// console.log(
493
493
-
// `called at euh reqreqreqreq: ${pathname}\n\n${JSON.stringify(body)}`
494
494
-
// );
495
495
-
reqBody = JSON.stringify(body, null, 2);
625
625
+
export class ViewServerUserManager {
626
626
+
public viewServer: ViewServer;
627
627
+
628
628
+
constructor(viewServer: ViewServer) {
629
629
+
this.viewServer = viewServer;
630
630
+
}
631
631
+
632
632
+
public users = new Map<string, UserViewServer>();
633
633
+
public handlesDid(did: string): boolean {
634
634
+
return this.users.has(did);
496
635
}
497
497
-
const bskyUrl = `https://api.bsky.app${pathname}${url.search}`;
498
498
-
const proxyHeaders = new Headers(req.headers);
499
636
500
500
-
// Remove Authorization and set browser-like User-Agent
501
501
-
proxyHeaders.delete("authorization");
502
502
-
proxyHeaders.delete("Access-Control-Allow-Origin"),
503
503
-
proxyHeaders.set(
504
504
-
"user-agent",
505
505
-
"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"
506
506
-
);
507
507
-
proxyHeaders.set("Access-Control-Allow-Origin", "*");
637
637
+
/*async*/ addUser(did: string) {
638
638
+
if (this.users.has(did)) return;
639
639
+
const instance = new UserViewServer(this, did);
640
640
+
//await instance.initialize();
641
641
+
this.users.set(did, instance);
642
642
+
}
508
643
509
509
-
const proxyRes = await fetch(bskyUrl, {
510
510
-
method: req.method,
511
511
-
headers: proxyHeaders,
512
512
-
body: ["GET", "HEAD"].includes(req.method.toUpperCase())
513
513
-
? undefined
514
514
-
: reqBody,
515
515
-
});
644
644
+
// async handleRequest({
645
645
+
// did,
646
646
+
// route,
647
647
+
// req,
648
648
+
// }: {
649
649
+
// did: string;
650
650
+
// route: string;
651
651
+
// req: Request;
652
652
+
// }) {
653
653
+
// if (!this.users.has(did)) await this.addUser(did);
654
654
+
// const user = this.users.get(did)!;
655
655
+
// return await user.handleHttpRequest(route, req);
656
656
+
// }
516
657
517
517
-
const resBody = await proxyRes.text();
658
658
+
removeUser(did: string) {
659
659
+
const instance = this.users.get(did);
660
660
+
if (!instance) return;
661
661
+
/*await*/ instance.shutdown();
662
662
+
this.users.delete(did);
663
663
+
}
518
664
519
519
-
// console.log(
520
520
-
// "← Response:",
521
521
-
// JSON.stringify(await JSON.parse(resBody), null, 2)
522
522
-
// );
665
665
+
getDbForDid(did: string): Database | null {
666
666
+
if (!this.users.has(did)) {
667
667
+
return null;
668
668
+
}
669
669
+
return this.users.get(did)?.db ?? null;
670
670
+
}
523
671
524
524
-
return new Response(resBody, {
525
525
-
status: proxyRes.status,
526
526
-
headers: proxyRes.headers,
527
527
-
});
672
672
+
coldStart(db: Database) {
673
673
+
const rows = db.prepare("SELECT did FROM users").all();
674
674
+
for (const row of rows) {
675
675
+
this.addUser(row.did);
676
676
+
}
677
677
+
}
528
678
}
529
679
530
530
-
export function viewServerIndexer(ctx: indexHandlerContext) {
531
531
-
const record = validateRecord(ctx.value);
532
532
-
switch (record?.$type) {
533
533
-
case "app.bsky.feed.like": {
534
534
-
return;
535
535
-
}
536
536
-
default: {
537
537
-
// what the hell
538
538
-
return;
539
539
-
}
680
680
+
class UserViewServer {
681
681
+
public viewServerUserManager: ViewServerUserManager;
682
682
+
did: string;
683
683
+
db: Database; // | undefined;
684
684
+
jetstream: JetstreamManager; // | undefined;
685
685
+
spacedust: SpacedustManager; // | undefined;
686
686
+
687
687
+
constructor(viewServerUserManager: ViewServerUserManager, did: string) {
688
688
+
this.did = did;
689
689
+
this.viewServerUserManager = viewServerUserManager;
690
690
+
this.db = this.viewServerUserManager.viewServer.internalCreateDbForDid(
691
691
+
this.did
692
692
+
);
693
693
+
// should probably put the params of exactly what were listening to here
694
694
+
this.jetstream = new JetstreamManager((msg) => {
695
695
+
console.log("Received Jetstream message: ", msg);
696
696
+
697
697
+
const op = msg.commit.operation;
698
698
+
const doer = msg.did;
699
699
+
const rev = msg.commit.rev;
700
700
+
const aturi = `${msg.did}/${msg.commit.collection}/${msg.commit.rkey}`;
701
701
+
const value = msg.commit.record;
702
702
+
703
703
+
if (!doer || !value) return;
704
704
+
this.viewServerUserManager.viewServer.viewServerIndexer({
705
705
+
op,
706
706
+
doer,
707
707
+
cid: msg.commit.cid,
708
708
+
rev,
709
709
+
aturi,
710
710
+
value,
711
711
+
indexsrc: `jetstream-${op}`,
712
712
+
db: this.db,
713
713
+
});
714
714
+
});
715
715
+
this.jetstream.start({
716
716
+
// for realsies pls get from db or something instead of this shit
717
717
+
wantedDids: [
718
718
+
this.did,
719
719
+
// "did:plc:mn45tewwnse5btfftvd3powc",
720
720
+
// "did:plc:yy6kbriyxtimkjqonqatv2rb",
721
721
+
// "did:plc:zzhzjga3ab5fcs2vnsv2ist3",
722
722
+
// "did:plc:jz4ibztn56hygfld6j6zjszg",
723
723
+
],
724
724
+
wantedCollections: [
725
725
+
// View server only needs some of the things related to user views mutes, not all of them
726
726
+
//"app.bsky.actor.profile",
727
727
+
//"app.bsky.feed.generator",
728
728
+
//"app.bsky.feed.like",
729
729
+
//"app.bsky.feed.post",
730
730
+
//"app.bsky.feed.repost",
731
731
+
"app.bsky.feed.threadgate", // mod
732
732
+
"app.bsky.graph.block", // mod
733
733
+
"app.bsky.graph.follow", // graphing
734
734
+
//"app.bsky.graph.list",
735
735
+
"app.bsky.graph.listblock", // mod
736
736
+
//"app.bsky.graph.listitem",
737
737
+
"app.bsky.notification.declaration", // mod
738
738
+
],
739
739
+
});
740
740
+
//await connectToJetstream(this.did, this.db);
741
741
+
this.spacedust = new SpacedustManager((msg: SpacedustLinkMessage) => {
742
742
+
console.log("Received Spacedust message: ", msg);
743
743
+
const operation = msg.link.operation;
744
744
+
745
745
+
const sourceURI = new ATPAPI.AtUri(msg.link.source_record);
746
746
+
const srcUri = msg.link.source_record;
747
747
+
const srcDid = sourceURI.host;
748
748
+
const srcField = msg.link.source;
749
749
+
const srcCol = sourceURI.collection;
750
750
+
const subjectURI = new ATPAPI.AtUri(msg.link.subject);
751
751
+
const subUri = msg.link.subject;
752
752
+
const subDid = subjectURI.host;
753
753
+
const subCol = subjectURI.collection;
754
754
+
755
755
+
if (operation === "delete") {
756
756
+
this.db.run(
757
757
+
`DELETE FROM backlink_skeleton
758
758
+
WHERE srcuri = ? AND srcfield = ? AND suburi = ?`,
759
759
+
[srcUri, srcField, subUri]
760
760
+
);
761
761
+
} else if (operation === "create") {
762
762
+
this.db.run(
763
763
+
`INSERT OR REPLACE INTO backlink_skeleton (
764
764
+
srcuri,
765
765
+
srcdid,
766
766
+
srcfield,
767
767
+
srccol,
768
768
+
suburi,
769
769
+
subdid,
770
770
+
subcol
771
771
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
772
772
+
[
773
773
+
srcUri, // full AT URI of the source record
774
774
+
srcDid, // did: of the source
775
775
+
srcField, // e.g., "reply.parent.uri" or "facets.features.did"
776
776
+
srcCol, // e.g., "app.bsky.feed.post"
777
777
+
subUri, // full AT URI of the subject (linked record)
778
778
+
subDid, // did: of the subject
779
779
+
subCol, // subject collection (can be inferred or passed)
780
780
+
]
781
781
+
);
782
782
+
}
783
783
+
});
784
784
+
this.spacedust.start({
785
785
+
wantedSources: [
786
786
+
// view server keeps all of this because notifications are a thing
787
787
+
"app.bsky.feed.like:subject.uri", // like
788
788
+
"app.bsky.feed.like:via.uri", // liked repost
789
789
+
"app.bsky.feed.repost:subject.uri", // repost
790
790
+
"app.bsky.feed.repost:via.uri", // reposted repost
791
791
+
"app.bsky.feed.post:reply.root.uri", // thread OP
792
792
+
"app.bsky.feed.post:reply.parent.uri", // direct parent
793
793
+
"app.bsky.feed.post:embed.media.record.record.uri", // quote with media
794
794
+
"app.bsky.feed.post:embed.record.uri", // quote without media
795
795
+
"app.bsky.feed.threadgate:post", // threadgate subject
796
796
+
"app.bsky.feed.threadgate:hiddenReplies", // threadgate items (array)
797
797
+
"app.bsky.feed.post:facets.features.did", // facet item (array): mention
798
798
+
"app.bsky.graph.block:subject", // blocks
799
799
+
"app.bsky.graph.follow:subject", // follow
800
800
+
"app.bsky.graph.listblock:subject", // list item (blocks)
801
801
+
"app.bsky.graph.listblock:list", // blocklist mention (might not exist)
802
802
+
"app.bsky.graph.listitem:subject", // list item (blocks)
803
803
+
"app.bsky.graph.listitem:list", // list mention
804
804
+
],
805
805
+
// should be getting from DB but whatever right
806
806
+
wantedSubjects: [
807
807
+
// as noted i dont need to write down each post, just the user to listen to !
808
808
+
// hell yeah
809
809
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybv7b6ic2h",
810
810
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvybws4avc2h",
811
811
+
// "at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.post/3lvvkcxcscs2h",
812
812
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3l63ogxocq42f",
813
813
+
// "at://did:plc:yy6kbriyxtimkjqonqatv2rb/app.bsky.feed.post/3lw3wamvflu23",
814
814
+
],
815
815
+
wantedSubjectDids: [
816
816
+
this.did,
817
817
+
//"did:plc:mn45tewwnse5btfftvd3powc",
818
818
+
//"did:plc:yy6kbriyxtimkjqonqatv2rb",
819
819
+
//"did:plc:zzhzjga3ab5fcs2vnsv2ist3",
820
820
+
//"did:plc:jz4ibztn56hygfld6j6zjszg",
821
821
+
],
822
822
+
});
823
823
+
//await connectToConstellation(this.did, this.db);
824
824
+
}
825
825
+
826
826
+
// initialize() {
827
827
+
828
828
+
// }
829
829
+
830
830
+
// async handleHttpRequest(route: string, req: Request): Promise<Response> {
831
831
+
// if (route === "posts") {
832
832
+
// const posts = await this.queryPosts();
833
833
+
// return new Response(JSON.stringify(posts), {
834
834
+
// headers: { "content-type": "application/json" },
835
835
+
// });
836
836
+
// }
837
837
+
838
838
+
// return new Response("Unknown route", { status: 404 });
839
839
+
// }
840
840
+
841
841
+
// private async queryPosts() {
842
842
+
// return this.db.run(
843
843
+
// "SELECT * FROM posts ORDER BY created_at DESC LIMIT 100"
844
844
+
// );
845
845
+
// }
846
846
+
847
847
+
shutdown() {
848
848
+
this.jetstream.stop();
849
849
+
this.spacedust.stop();
850
850
+
this.db.close?.();
540
851
}
541
852
}