From 50f764ba8609080cebbae21590801f8f3b81a98e Mon Sep 17 00:00:00 2001 From: devin ivy Date: Wed, 10 May 2023 23:59:50 -0400 Subject: [PATCH 01/38] Config by environment vars (#5) * Support .env file * Fix default sub endpoint --- .gitignore | 3 ++- package.json | 1 + src/index.ts | 21 +++++++++++++++++++-- src/server.ts | 3 +-- yarn.lock | 5 +++++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b512c09d4..37d7e7348 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.env diff --git a/package.json b/package.json index cf61c1685..72a4e974b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@atproto/uri": "^0.0.2", "@atproto/xrpc-server": "^0.1.0", "better-sqlite3": "^8.3.0", + "dotenv": "^16.0.3", "express": "^4.18.2", "kysely": "^0.22.0" }, diff --git a/src/index.ts b/src/index.ts index b8bbe8194..8770d69b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,27 @@ +import dotenv from 'dotenv' import FeedGenerator from './server' const run = async () => { - // we'll add .env soon - const server = FeedGenerator.create() + dotenv.config() + const server = FeedGenerator.create({ + port: maybeInt(process.env.FEEDGEN_PORT), + sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION), + subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT), + }) await server.start() console.log(`🤖 running feed generator at localhost${server.cfg.port}`) } +const maybeStr = (val?: string) => { + if (!val) return undefined + return val +} + +const maybeInt = (val?: string) => { + if (!val) return undefined + const int = parseInt(val, 10) + if (isNaN(int)) return undefined + return int +} + run() diff --git a/src/server.ts b/src/server.ts index f86fd5576..9b59d7575 100644 --- a/src/server.ts +++ b/src/server.ts @@ -35,8 +35,7 @@ export class FeedGenerator { const cfg = { port: config?.port ?? 3000, sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', - subscriptionEndpoint: - config?.subscriptionEndpoint ?? 'https://bsky.social', + subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', } const app = express() const db = createDb(cfg.sqliteLocation) diff --git a/yarn.lock b/yarn.lock index b2e8e281b..781bace4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -517,6 +517,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" From 71c2ee061e9de900125a59625351986ebdc1dc08 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Thu, 11 May 2023 00:00:44 -0400 Subject: [PATCH 02/38] Setup simple migration system (#7) * Setup migrations, misc tidy/fixes * Simplify migration provider --- src/db/index.ts | 26 ++++++++------------------ src/db/migrations.ts | 31 +++++++++++++++++++++++++++++++ src/db/schema.ts | 17 +++++++++++++++++ src/feed-generation.ts | 8 ++++---- src/index.ts | 2 +- src/server.ts | 15 +++++++-------- src/subscription.ts | 4 ++-- src/util/subscription.ts | 2 +- 8 files changed, 71 insertions(+), 34 deletions(-) create mode 100644 src/db/migrations.ts create mode 100644 src/db/schema.ts diff --git a/src/db/index.ts b/src/db/index.ts index 3c269ad62..9275d4685 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,5 +1,7 @@ -import { Kysely, SqliteDialect } from 'kysely' import SqliteDb from 'better-sqlite3' +import { Kysely, Migrator, SqliteDialect } from 'kysely' +import { DatabaseSchema } from './schema' +import { migrationProvider } from './migrations' export const createDb = (location: string): Database => { return new Kysely({ @@ -9,22 +11,10 @@ export const createDb = (location: string): Database => { }) } -export type Database = Kysely - -export type PostTable = { - uri: string - cid: string - replyParent: string | null - replyRoot: string | null - indexedAt: string -} - -export type SubStateTable = { - service: string - cursor: number +export const migrateToLatest = async (db: Database) => { + const migrator = new Migrator({ db, provider: migrationProvider }) + const { error } = await migrator.migrateToLatest() + if (error) throw error } -export type DatabaseSchema = { - posts: PostTable - sub_state: SubStateTable -} +export type Database = Kysely diff --git a/src/db/migrations.ts b/src/db/migrations.ts new file mode 100644 index 000000000..966007801 --- /dev/null +++ b/src/db/migrations.ts @@ -0,0 +1,31 @@ +import { Kysely, Migration, MigrationProvider } from 'kysely' + +const migrations: Record = {} + +export const migrationProvider: MigrationProvider = { + async getMigrations() { + return migrations + }, +} + +migrations['001'] = { + async up(db: Kysely) { + await db.schema + .createTable('post') + .addColumn('uri', 'varchar', (col) => col.primaryKey()) + .addColumn('cid', 'varchar', (col) => col.notNull()) + .addColumn('replyParent', 'varchar') + .addColumn('replyRoot', 'varchar') + .addColumn('indexedAt', 'varchar', (col) => col.notNull()) + .execute() + await db.schema + .createTable('sub_state') + .addColumn('service', 'varchar', (col) => col.primaryKey()) + .addColumn('cursor', 'integer', (col) => col.notNull()) + .execute() + }, + async down(db: Kysely) { + await db.schema.dropTable('post').execute() + await db.schema.dropTable('sub_state').execute() + }, +} diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 000000000..8b2455d74 --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,17 @@ +export type DatabaseSchema = { + post: Post + sub_state: SubState +} + +export type Post = { + uri: string + cid: string + replyParent: string | null + replyRoot: string | null + indexedAt: string +} + +export type SubState = { + service: string + cursor: number +} diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 30b9fd837..4a7aa6467 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -8,7 +8,7 @@ export default function (server: Server, db: Database) { throw new InvalidRequestError('algorithm unsupported') } let builder = db - .selectFrom('posts') + .selectFrom('post') .selectAll() .orderBy('indexedAt', 'desc') .orderBy('cid', 'desc') @@ -20,9 +20,9 @@ export default function (server: Server, db: Database) { } const timeStr = new Date(parseInt(indexedAt, 10)).toISOString() builder = builder - .where('posts.indexedAt', '<', timeStr) - .orWhere((qb) => qb.where('posts.indexedAt', '=', timeStr)) - .where('posts.cid', '<', cid) + .where('post.indexedAt', '<', timeStr) + .orWhere((qb) => qb.where('post.indexedAt', '=', timeStr)) + .where('post.cid', '<', cid) } const res = await builder.execute() diff --git a/src/index.ts b/src/index.ts index 8770d69b9..36a578bf9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ const run = async () => { subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT), }) await server.start() - console.log(`🤖 running feed generator at localhost${server.cfg.port}`) + console.log(`🤖 running feed generator at localhost:${server.cfg.port}`) } const maybeStr = (val?: string) => { diff --git a/src/server.ts b/src/server.ts index 9b59d7575..1a5c36bed 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,7 +3,7 @@ import events from 'events' import express from 'express' import { createServer } from './lexicon' import feedGeneration from './feed-generation' -import { createDb, Database } from './db' +import { createDb, Database, migrateToLatest } from './db' import { FirehoseSubscription } from './subscription' export type Config = { @@ -32,7 +32,7 @@ export class FeedGenerator { } static create(config?: Partial) { - const cfg = { + const cfg: Config = { port: config?.port ?? 3000, sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', @@ -56,12 +56,11 @@ export class FeedGenerator { } async start(): Promise { - await this.firehose.run() - const server = this.app.listen(this.cfg.port) - server.keepAliveTimeout = 90000 - this.server = server - await events.once(server, 'listening') - return server + await migrateToLatest(this.db) + this.firehose.run() + this.server = this.app.listen(this.cfg.port) + await events.once(this.server, 'listening') + return this.server } } diff --git a/src/subscription.ts b/src/subscription.ts index 943b8d1cd..02b03bfb5 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -36,13 +36,13 @@ export class FirehoseSubscription extends FirehoseSubscriptionBase { if (postsToDelete.length > 0) { await this.db - .deleteFrom('posts') + .deleteFrom('post') .where('uri', 'in', postsToDelete) .execute() } if (postsToCreate.length > 0) { await this.db - .insertInto('posts') + .insertInto('post') .values(postsToCreate) .onConflict((oc) => oc.doNothing()) .execute() diff --git a/src/util/subscription.ts b/src/util/subscription.ts index f3a892cb3..711b642c5 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -78,7 +78,7 @@ export abstract class FirehoseSubscriptionBase { export const getPostOperations = async (evt: Commit): Promise => { const ops: Operations = { creates: [], deletes: [] } const postOps = evt.ops.filter( - (op) => op.path.split('/')[1] === ids.AppBskyFeedPost, + (op) => op.path.split('/')[0] === ids.AppBskyFeedPost, ) if (postOps.length < 1) return ops From 9eb71863b3ecc6d6647c51cae58c21c1b9b07fb8 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Thu, 11 May 2023 00:02:18 -0400 Subject: [PATCH 03/38] More convenient access to ops by record type (#6) --- src/subscription.ts | 32 +++--------- src/util/subscription.ts | 110 ++++++++++++++++++++++++++++----------- 2 files changed, 88 insertions(+), 54 deletions(-) diff --git a/src/subscription.ts b/src/subscription.ts index 02b03bfb5..649f7ed88 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -1,35 +1,26 @@ -import { ids, lexicons } from './lexicon/lexicons' -import { Record as PostRecord } from './lexicon/types/app/bsky/feed/post' import { OutputSchema as RepoEvent, isCommit, } from './lexicon/types/com/atproto/sync/subscribeRepos' -import { - FirehoseSubscriptionBase, - getPostOperations, -} from './util/subscription' +import { FirehoseSubscriptionBase, getOpsByType } from './util/subscription' export class FirehoseSubscription extends FirehoseSubscriptionBase { async handleEvent(evt: RepoEvent) { if (!isCommit(evt)) return - const postOps = await getPostOperations(evt) - const postsToDelete = postOps.deletes.map((del) => del.uri) - const postsToCreate = postOps.creates + const ops = await getOpsByType(evt) + const postsToDelete = ops.posts.deletes.map((del) => del.uri) + const postsToCreate = ops.posts.creates .filter((create) => { // only alf-related posts - return ( - isPost(create.record) && - create.record.text.toLowerCase().includes('alf') - ) + return create.record.text.toLowerCase().includes('alf') }) .map((create) => { // map alf-related posts to a db row - const record = isPost(create.record) ? create.record : null return { uri: create.uri, cid: create.cid, - replyParent: record?.reply?.parent.uri ?? null, - replyRoot: record?.reply?.root.uri ?? null, + replyParent: create.record?.reply?.parent.uri ?? null, + replyRoot: create.record?.reply?.root.uri ?? null, indexedAt: new Date().toISOString(), } }) @@ -49,12 +40,3 @@ export class FirehoseSubscription extends FirehoseSubscriptionBase { } } } - -export const isPost = (obj: unknown): obj is PostRecord => { - try { - lexicons.assertValidRecord(ids.AppBskyFeedPost, obj) - return true - } catch (err) { - return false - } -} diff --git a/src/util/subscription.ts b/src/util/subscription.ts index 711b642c5..3eee79e30 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -1,12 +1,16 @@ import { Subscription } from '@atproto/xrpc-server' +import { cborToLexRecord, readCar } from '@atproto/repo' import { ids, lexicons } from '../lexicon/lexicons' +import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' +import { Record as RepostRecord } from '../lexicon/types/app/bsky/feed/repost' +import { Record as LikeRecord } from '../lexicon/types/app/bsky/feed/like' +import { Record as FollowRecord } from '../lexicon/types/app/bsky/graph/follow' import { Commit, OutputSchema as RepoEvent, isCommit, } from '../lexicon/types/com/atproto/sync/subscribeRepos' import { Database } from '../db' -import { cborToLexRecord, readCar } from '@atproto/repo' export abstract class FirehoseSubscriptionBase { public sub: Subscription @@ -75,50 +79,98 @@ export abstract class FirehoseSubscriptionBase { } } -export const getPostOperations = async (evt: Commit): Promise => { - const ops: Operations = { creates: [], deletes: [] } - const postOps = evt.ops.filter( - (op) => op.path.split('/')[0] === ids.AppBskyFeedPost, - ) - - if (postOps.length < 1) return ops - +export const getOpsByType = async (evt: Commit): Promise => { const car = await readCar(evt.blocks) + const opsByType: OperationsByType = { + posts: { creates: [], deletes: [] }, + reposts: { creates: [], deletes: [] }, + likes: { creates: [], deletes: [] }, + follows: { creates: [], deletes: [] }, + } - for (const op of postOps) { - // updates not supported yet - if (op.action === 'update') continue + for (const op of evt.ops) { const uri = `at://${evt.repo}/${op.path}` - if (op.action === 'delete') { - ops.deletes.push({ uri }) - } else if (op.action === 'create') { + const [collection] = op.path.split('/') + + if (op.action === 'update') continue // updates not supported yet + + if (op.action === 'create') { if (!op.cid) continue - const postBytes = await car.blocks.get(op.cid) - if (!postBytes) continue - ops.creates.push({ - uri, - cid: op.cid.toString(), - author: evt.repo, - record: cborToLexRecord(postBytes), - }) + const recordBytes = car.blocks.get(op.cid) + if (!recordBytes) continue + const record = cborToLexRecord(recordBytes) + const create = { uri, cid: op.cid.toString(), author: evt.repo } + if (collection === ids.AppBskyFeedPost && isPost(record)) { + opsByType.posts.creates.push({ record, ...create }) + } else if (collection === ids.AppBskyFeedRepost && isRepost(record)) { + opsByType.reposts.creates.push({ record, ...create }) + } else if (collection === ids.AppBskyFeedLike && isLike(record)) { + opsByType.likes.creates.push({ record, ...create }) + } else if (collection === ids.AppBskyGraphFollow && isFollow(record)) { + opsByType.follows.creates.push({ record, ...create }) + } + } + + if (op.action === 'delete') { + if (collection === ids.AppBskyFeedPost) { + opsByType.posts.deletes.push({ uri }) + } else if (collection === ids.AppBskyFeedRepost) { + opsByType.reposts.deletes.push({ uri }) + } else if (collection === ids.AppBskyFeedLike) { + opsByType.likes.deletes.push({ uri }) + } else if (collection === ids.AppBskyGraphFollow) { + opsByType.follows.deletes.push({ uri }) + } } } - return ops + return opsByType } -type CreateOp = { +type OperationsByType = { + posts: Operations + reposts: Operations + likes: Operations + follows: Operations +} + +type Operations> = { + creates: CreateOp[] + deletes: DeleteOp[] +} + +type CreateOp = { uri: string cid: string author: string - record: Record + record: T } type DeleteOp = { uri: string } -type Operations = { - creates: CreateOp[] - deletes: DeleteOp[] +export const isPost = (obj: unknown): obj is PostRecord => { + return isType(obj, ids.AppBskyFeedPost) +} + +export const isRepost = (obj: unknown): obj is RepostRecord => { + return isType(obj, ids.AppBskyFeedRepost) +} + +export const isLike = (obj: unknown): obj is LikeRecord => { + return isType(obj, ids.AppBskyFeedLike) +} + +export const isFollow = (obj: unknown): obj is FollowRecord => { + return isType(obj, ids.AppBskyGraphFollow) +} + +const isType = (obj: unknown, nsid: string) => { + try { + lexicons.assertValidRecord(nsid, obj) + return true + } catch (err) { + return false + } } From 2a13e9e97cfa56525087e8e2f5eab2d6bc3946cd Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 10 May 2023 23:03:54 -0500 Subject: [PATCH 04/38] tidy --- .gitignore | 1 + src/index.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 37d7e7348..5999717b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .env +test.sqlite diff --git a/src/index.ts b/src/index.ts index 36a578bf9..5974b4094 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,9 @@ const run = async () => { subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT), }) await server.start() - console.log(`🤖 running feed generator at localhost:${server.cfg.port}`) + console.log( + `🤖 running feed generator at http://localhost:${server.cfg.port}`, + ) } const maybeStr = (val?: string) => { From 84420cc7aa4d514d4d1282f21ddaf083096298d1 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 10 May 2023 23:20:44 -0500 Subject: [PATCH 05/38] add auth --- .gitignore | 1 - package.json | 3 ++- src/auth.ts | 18 +++++++++++++ src/config.ts | 15 +++++++++++ src/feed-generation.ts | 19 ++++++++++---- src/server.ts | 22 ++++++++++------ test.sqlite | 0 yarn.lock | 57 +++++++++++++++++++++++++++++++++++++----- 8 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 src/auth.ts create mode 100644 src/config.ts delete mode 100644 test.sqlite diff --git a/.gitignore b/.gitignore index 5999717b3..37d7e7348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules .env -test.sqlite diff --git a/package.json b/package.json index 72a4e974b..516cf93af 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,10 @@ "start": "ts-node src/index.ts" }, "dependencies": { + "@atproto/did-resolver": "^0.1.0", "@atproto/repo": "^0.1.0", "@atproto/uri": "^0.0.2", - "@atproto/xrpc-server": "^0.1.0", + "@atproto/xrpc-server": "^0.2.0", "better-sqlite3": "^8.3.0", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 000000000..9ccaa99c3 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,18 @@ +import express from 'express' +import { verifyJwt, AuthRequiredError } from '@atproto/xrpc-server' +import { DidResolver } from '@atproto/did-resolver' + +export const validateAuth = async ( + req: express.Request, + serviceDid: string, + didResolver: DidResolver, +): Promise => { + const { authorization = '' } = req.headers + if (!authorization.startsWith('Bearer ')) { + throw new AuthRequiredError() + } + const jwt = authorization.replace('Bearer ', '').trim() + return verifyJwt(jwt, serviceDid, async (did: string) => { + return didResolver.resolveAtprotoKey(did) + }) +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 000000000..e3576eb60 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,15 @@ +import { Database } from './db' +import { DidResolver } from '@atproto/did-resolver' + +export type AppContext = { + db: Database + didResolver: DidResolver + cfg: Config +} + +export type Config = { + port: number + sqliteLocation: string + subscriptionEndpoint: string + serviceDid: string +} diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 4a7aa6467..c6d064a8e 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -1,13 +1,22 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { Database } from './db' import { Server } from './lexicon' +import { AppContext } from './config' +import { validateAuth } from './auth' -export default function (server: Server, db: Database) { - server.app.bsky.feed.getFeedSkeleton(async ({ params, auth }) => { - if (params.feed !== 'alf.bsky.social') { +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { + if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') { throw new InvalidRequestError('algorithm unsupported') } - let builder = db + // example of how to check auth + // feel free to remove if requesterDid is not used + const requesterDid = await validateAuth( + req, + ctx.cfg.serviceDid, + ctx.didResolver, + ) + + let builder = ctx.db .selectFrom('post') .selectAll() .orderBy('indexedAt', 'desc') diff --git a/src/server.ts b/src/server.ts index 1a5c36bed..059b805be 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,16 +1,12 @@ import http from 'http' import events from 'events' import express from 'express' +import { DidResolver, MemoryCache } from '@atproto/did-resolver' import { createServer } from './lexicon' import feedGeneration from './feed-generation' import { createDb, Database, migrateToLatest } from './db' import { FirehoseSubscription } from './subscription' - -export type Config = { - port: number - sqliteLocation: string - subscriptionEndpoint: string -} +import { AppContext, Config } from './config' export class FeedGenerator { public app: express.Application @@ -36,11 +32,18 @@ export class FeedGenerator { port: config?.port ?? 3000, sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', + serviceDid: config?.serviceDid ?? 'did:example:test', } const app = express() const db = createDb(cfg.sqliteLocation) const firehose = new FirehoseSubscription(db, cfg.subscriptionEndpoint) + const didCache = new MemoryCache() + const didResolver = new DidResolver( + { plcUrl: 'https://plc.directory' }, + didCache, + ) + const server = createServer({ validateResponse: true, payload: { @@ -49,7 +52,12 @@ export class FeedGenerator { blobLimit: 5 * 1024 * 1024, // 5mb }, }) - feedGeneration(server, db) + const ctx: AppContext = { + db, + didResolver, + cfg, + } + feedGeneration(server, ctx) app.use(server.xrpc.router) return new FeedGenerator(app, db, firehose, cfg) diff --git a/test.sqlite b/test.sqlite deleted file mode 100644 index e69de29bb..000000000 diff --git a/yarn.lock b/yarn.lock index 781bace4a..4cab0fa88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,6 +43,16 @@ axios "^0.24.0" did-resolver "^4.0.0" +"@atproto/did-resolver@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/did-resolver/-/did-resolver-0.1.0.tgz#58f42447700aaad61bad2f0d70b721966268aa02" + integrity sha512-ztljyMMCqXvJSi/Qqa2zEQFvOm1AUUR7Bybr3cM1BCddbhW46gk6/g8BgdZeDt2sMBdye37qTctR9O/FjhigvQ== + dependencies: + "@atproto/common-web" "*" + "@atproto/crypto" "*" + axios "^0.27.2" + zod "^3.14.2" + "@atproto/identifier@*": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75" @@ -92,12 +102,13 @@ "@atproto/identifier" "*" "@atproto/nsid" "*" -"@atproto/xrpc-server@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.1.0.tgz#2dd3172bb35fbfefb98c3d727d29be8eca5c3d9b" - integrity sha512-I7EjhnLUrlqQKTe2jDEnyAaOTvj26pg9NRjTXflbIOqCOkh+K9+5ztGSI0djF7TSQ7pegXroj3qRnmpVVCBr7Q== +"@atproto/xrpc-server@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.2.0.tgz#a36616c2ac70339cd79cda83ede0a0b305c74f9b" + integrity sha512-sCJuVUIb1tDIlKCFwHPRHbAgEy0HYGlQ7XhpNqMRKXECh8Z+DRICEne3gLDVaXhyNaC/N7OjHcsyuofDDbuGFQ== dependencies: "@atproto/common" "*" + "@atproto/crypto" "*" "@atproto/lexicon" "*" cbor-x "^1.5.1" express "^4.17.2" @@ -322,6 +333,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + atomic-sleep@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" @@ -334,6 +350,14 @@ axios@^0.24.0: dependencies: follow-redirects "^1.14.4" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -446,6 +470,13 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -492,6 +523,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -624,11 +660,20 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -follow-redirects@^1.14.4: +follow-redirects@^1.14.4, follow-redirects@^1.14.9: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -755,7 +800,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== From 42eebc3cfe23d9aa2ac9c338c6fee187b2dd4b5f Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 10 May 2023 23:24:41 -0500 Subject: [PATCH 06/38] readme tweaks --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f71c2ae68..c4fe5d681 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ type SkeletonItem = { type Reason = ReasonRepost type ReasonRepost = { - $type: @TODO + $type: 'app.bsky.feed.defs#skeletonReasonRepost' by: string // the did of the reposting user indexedAt: string // the time that the repost took place } @@ -111,7 +111,7 @@ const payload = { } ``` -We provide utilities for verifying user JWTs in the `@atproto/xrpc-server` package. +We provide utilities for verifying user JWTs in the `@atproto/xrpc-server` package, and you can see them in action in `src/auth.ts`. ### Pagination You'll notice that the `getFeedSkeleton` method returns a `cursor` in its response & takes a `cursor` param as input. From b6a588c4682aad3e679a9cc0265e6fd0177fcd1d Mon Sep 17 00:00:00 2001 From: Emily Liu Date: Wed, 10 May 2023 22:39:47 -0700 Subject: [PATCH 07/38] update app.bsky.feed.getFeedSkeleton link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4fe5d681..feefdb271 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ In the meantime, we've put together this starter kit for devs. It doesn't do eve Feed Generators are services that provide custom algorithms to users through the AT protocol. -They work very simply: the server receives a request from a user's server and returns a list of [post URIs](https://atproto.com/specs/at-uri-scheme) with some optional metadata attached. Those posts are then hydrated into full views by the requesting server and sent back to the client. This route is described in the [`com.atproto.feed.getFeedSkeleton` lexicon](https://github.com/bluesky-social/atproto/blob/custom-feeds/lexicons/app/bsky/feed/getFeedSkeleton.json). +They work very simply: the server receives a request from a user's server and returns a list of [post URIs](https://atproto.com/specs/at-uri-scheme) with some optional metadata attached. Those posts are then hydrated into full views by the requesting server and sent back to the client. This route is described in the [`app.bsky.feed.getFeedSkeleton` lexicon](https://atproto.com/lexicons/app-bsky-feed#appbskyfeedgetfeedskeleton). A Feed Generator service can host one or more algorithms. The service itself is identified by DID, however each algorithm that it hosts is declared by a record in the repo of the account that created it. For instance feeds offered by Bluesky will likely be declared in `@bsky.app`'s repo. Therefore, a given algorithm is identified by the at-uri of the declaration record. This declaration record includes a pointer to the service's DID along with some profile information for the feed. From 5bc3a5fcb753cd9a2edbd504e258abaa0bae0e62 Mon Sep 17 00:00:00 2001 From: Emily Liu Date: Wed, 10 May 2023 22:45:08 -0700 Subject: [PATCH 08/38] fix small typos --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index feefdb271..5cc7a8331 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ In the meantime, we've put together this starter kit for devs. It doesn't do eve ## Overview -Feed Generators are services that provide custom algorithms to users through the AT protocol. +Feed Generators are services that provide custom algorithms to users through the AT Protocol. They work very simply: the server receives a request from a user's server and returns a list of [post URIs](https://atproto.com/specs/at-uri-scheme) with some optional metadata attached. Those posts are then hydrated into full views by the requesting server and sent back to the client. This route is described in the [`app.bsky.feed.getFeedSkeleton` lexicon](https://atproto.com/lexicons/app-bsky-feed#appbskyfeedgetfeedskeleton). -A Feed Generator service can host one or more algorithms. The service itself is identified by DID, however each algorithm that it hosts is declared by a record in the repo of the account that created it. For instance feeds offered by Bluesky will likely be declared in `@bsky.app`'s repo. Therefore, a given algorithm is identified by the at-uri of the declaration record. This declaration record includes a pointer to the service's DID along with some profile information for the feed. +A Feed Generator service can host one or more algorithms. The service itself is identified by DID, while each algorithm that it hosts is declared by a record in the repo of the account that created it. For instance, feeds offered by Bluesky will likely be declared in `@bsky.app`'s repo. Therefore, a given algorithm is identified by the at-uri of the declaration record. This declaration record includes a pointer to the service's DID along with some profile information for the feed. The general flow of providing a custom algorithm to a user is as follows: - A user requests a feed from their server (PDS) using the at-uri of the declared feed @@ -24,21 +24,21 @@ The general flow of providing a custom algorithm to a user is as follows: - In the future, the PDS will hydrate the feed with the help of an App View, but for now the PDS handles hydration itself - The PDS returns the hydrated feed to the user -To the user this should feel like visiting a page in the app. Once they subscribe, it will appear in their home interface as one of their available feeds. +For users, this should feel like visiting a page in the app. Once they subscribe to a custom algorithm, it will appear in their home interface as one of their available feeds. ## Getting Started -We've setup this simple server with sqlite to store & query data. Feel free to switch this out for whichever database you prefer. +We've set up this simple server with SQLite to store & query data. Feel free to switch this out for whichever database you prefer. Next you will need to do two things: -- Implement indexing logic in `src/subscription.ts`. +1. Implement indexing logic in `src/subscription.ts`. + + This will subscribe to the repo subscription stream on startup, parse events & index them according to your provided logic. -This will subscribe to the repo subscription stream on startup, parse events & index them according to your provided logic. - -- Implement feed generation logic in `src/feed-generation.ts` - -The types are in place and you will just need to return something that satisfies the `SkeletonFeedPost[]` type +2. Implement feed generation logic in `src/feed-generation.ts` + + The types are in place and you will just need to return something that satisfies the `SkeletonFeedPost[]` type. For inspiration, we've provided a very simple feed algorithm ("whats alf") that returns all posts related to the titular character of the TV show ALF. @@ -50,7 +50,7 @@ Once the custom algorithms feature launches, you'll be able to publish your feed ### Skeleton Metadata -The skeleton that a Feed Generator puts together is, in its simplest form, a list of post uris. +The skeleton that a Feed Generator puts together is, in its simplest form, a list of post URIs. ```ts [ @@ -116,7 +116,7 @@ We provide utilities for verifying user JWTs in the `@atproto/xrpc-server` packa ### Pagination You'll notice that the `getFeedSkeleton` method returns a `cursor` in its response & takes a `cursor` param as input. -This cursor is treated as an opaque value & fully at the Feed Generator's discretion. It is simply pased through he PDS directly to & from the client. +This cursor is treated as an opaque value & fully at the Feed Generator's discretion. It is simply pased through the PDS directly to & from the client. We strongly encourage that the cursor be _unique per feed item_ to prevent unexpected behavior in pagination. @@ -127,7 +127,7 @@ We recommend, for instance, a compound cursor with a timestamp + a CID: How a feed generator fulfills the `getFeedSkeleton` request is completely at their discretion. At the simplest end, a Feed Generator could supply a "feed" that only contains some hardcoded posts. -For most usecases, we recommend subscribing to the firehose at `com.atproto.sync.subscribeRepos`. This websocket will send you every record that is published on the network. Since Feed Generators do not need to provide hydrated posts, you can index as much or as little of the firehose as necessary. +For most use cases, we recommend subscribing to the firehose at `com.atproto.sync.subscribeRepos`. This websocket will send you every record that is published on the network. Since Feed Generators do not need to provide hydrated posts, you can index as much or as little of the firehose as necessary. Depending on your algorithm, you likely do not need to keep posts around for long. Unless your algorithm is intended to provide "posts you missed" or something similar, you can likely garbage collect any data that is older than 48 hours. @@ -140,4 +140,4 @@ To reimplement "What's Hot", you may subscribe to the firehose & filter for all You might create a feed for a given community by compiling a list of DIDs within that community & filtering the firehose for all posts from users within that list. ### A Topical Feed -To implement a topical feed, you might filter the algorithm for posts and pass the post text through some filtering mechanism (an LLM, a keyword matcher, etc) that filters for the topic of your choice. +To implement a topical feed, you might filter the algorithm for posts and pass the post text through some filtering mechanism (an LLM, a keyword matcher, etc.) that filters for the topic of your choice. From bca915f6ae750f5cb51c6fc67231ea171bcbce9a Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 12:06:34 -0500 Subject: [PATCH 09/38] live tail subscription --- src/server.ts | 2 +- src/subscription.ts | 5 +++++ src/util/subscription.ts | 16 ++-------------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/server.ts b/src/server.ts index 059b805be..bbcf69f0a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -30,7 +30,7 @@ export class FeedGenerator { static create(config?: Partial) { const cfg: Config = { port: config?.port ?? 3000, - sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', + sqliteLocation: config?.sqliteLocation ?? ':memory:', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', serviceDid: config?.serviceDid ?? 'did:example:test', } diff --git a/src/subscription.ts b/src/subscription.ts index 649f7ed88..0aaf94e47 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -8,6 +8,11 @@ export class FirehoseSubscription extends FirehoseSubscriptionBase { async handleEvent(evt: RepoEvent) { if (!isCommit(evt)) return const ops = await getOpsByType(evt) + if (ops.posts.creates.length > 0) { + for (const op of ops.posts.creates) { + console.log(op.record.text) + } + } const postsToDelete = ops.posts.deletes.map((del) => del.uri) const postsToCreate = ops.posts.creates .filter((create) => { diff --git a/src/util/subscription.ts b/src/util/subscription.ts index 3eee79e30..e3c0111d4 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -36,7 +36,6 @@ export abstract class FirehoseSubscriptionBase { abstract handleEvent(evt: RepoEvent): Promise async run() { - await this.ensureCursor() for await (const evt of this.sub) { try { await this.handleEvent(evt) @@ -50,17 +49,6 @@ export abstract class FirehoseSubscriptionBase { } } - async ensureCursor() { - await this.db - .insertInto('sub_state') - .values({ - service: this.service, - cursor: 0, - }) - .onConflict((oc) => oc.doNothing()) - .execute() - } - async updateCursor(cursor: number) { await this.db .updateTable('sub_state') @@ -69,13 +57,13 @@ export abstract class FirehoseSubscriptionBase { .execute() } - async getCursor(): Promise<{ cursor: number }> { + async getCursor(): Promise<{ cursor?: number }> { const res = await this.db .selectFrom('sub_state') .selectAll() .where('service', '=', this.service) .executeTakeFirst() - return res ? { cursor: res.cursor } : { cursor: 0 } + return res ? { cursor: res.cursor } : {} } } From eeeb0325fc5c0fb141166125278658f2d53b318f Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 12:14:50 -0500 Subject: [PATCH 10/38] rm log --- src/subscription.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/subscription.ts b/src/subscription.ts index 0aaf94e47..649f7ed88 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -8,11 +8,6 @@ export class FirehoseSubscription extends FirehoseSubscriptionBase { async handleEvent(evt: RepoEvent) { if (!isCommit(evt)) return const ops = await getOpsByType(evt) - if (ops.posts.creates.length > 0) { - for (const op of ops.posts.creates) { - console.log(op.record.text) - } - } const postsToDelete = ops.posts.deletes.map((del) => del.uri) const postsToCreate = ops.posts.creates .filter((create) => { From cacc0469863ca180598a234d2390d45e94235667 Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 14:33:20 -0500 Subject: [PATCH 11/38] comment out auth check & log firehose output --- src/feed-generation.ts | 16 +++++++++------- src/subscription.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/feed-generation.ts b/src/feed-generation.ts index c6d064a8e..96490464e 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -8,13 +8,15 @@ export default function (server: Server, ctx: AppContext) { if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') { throw new InvalidRequestError('algorithm unsupported') } - // example of how to check auth - // feel free to remove if requesterDid is not used - const requesterDid = await validateAuth( - req, - ctx.cfg.serviceDid, - ctx.didResolver, - ) + /** + * Example of how to check auth if giving user-specific results: + * + * const requesterDid = await validateAuth( + * req, + * ctx.cfg.serviceDid, + * ctx.didResolver, + * ) + */ let builder = ctx.db .selectFrom('post') diff --git a/src/subscription.ts b/src/subscription.ts index 649f7ed88..b3a141e8d 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -8,6 +8,14 @@ export class FirehoseSubscription extends FirehoseSubscriptionBase { async handleEvent(evt: RepoEvent) { if (!isCommit(evt)) return const ops = await getOpsByType(evt) + + // This logs the text of every post off the firehose. + // Just for fun :) + // Delete before actually using + for (const post of ops.posts.creates) { + console.log(post.record.text) + } + const postsToDelete = ops.posts.deletes.map((del) => del.uri) const postsToCreate = ops.posts.creates .filter((create) => { From fac68c32502655e635224ba7d92d36bdd5e72baa Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 14:35:04 -0500 Subject: [PATCH 12/38] tweak error type --- src/feed-generation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 96490464e..f1304a0db 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -6,7 +6,10 @@ import { validateAuth } from './auth' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') { - throw new InvalidRequestError('algorithm unsupported') + throw new InvalidRequestError( + 'Unsupported algorithm', + 'UnsupportedAlgorithm', + ) } /** * Example of how to check auth if giving user-specific results: From f9ccf112e113f5e8ed5c3cff6b7115da34a95bfa Mon Sep 17 00:00:00 2001 From: Alice <81575558+aliceisjustplaying@users.noreply.github.com> Date: Thu, 11 May 2023 20:49:34 +0100 Subject: [PATCH 13/38] fix dependencies (#9) --- package.json | 6 +- yarn.lock | 397 ++++++++++++++++++++++++--------------------------- 2 files changed, 189 insertions(+), 214 deletions(-) diff --git a/package.json b/package.json index 516cf93af..f4e8b3fa9 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,20 @@ }, "dependencies": { "@atproto/did-resolver": "^0.1.0", + "@atproto/lexicon": "^0.1.0", "@atproto/repo": "^0.1.0", "@atproto/uri": "^0.0.2", "@atproto/xrpc-server": "^0.2.0", "better-sqlite3": "^8.3.0", "dotenv": "^16.0.3", "express": "^4.18.2", - "kysely": "^0.22.0" + "kysely": "^0.22.0", + "multiformats": "^9.9.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.4", "@types/express": "^4.17.17", - "@types/node": "^20.1.1", + "@types/node": "^20.1.2", "ts-node": "^10.9.1", "typescript": "^5.0.4" } diff --git a/yarn.lock b/yarn.lock index 4cab0fa88..ef4f11f02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,7 +4,7 @@ "@atproto/common-web@*": version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.1.0.tgz#5529fa66f9533aa00cfd13f0a25757df7b26bd3d" + resolved "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.1.0.tgz" integrity sha512-qD6xF60hvH+cP++fk/mt+0S9cxs94KsK+rNWypNlgnlp7r9By4ltXwtDSR/DNTA8mwDeularUno4VbTd2IWIzA== dependencies: multiformats "^9.6.4" @@ -13,7 +13,7 @@ "@atproto/common@*": version "0.2.0" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.2.0.tgz#e74502edf636f30e332f516dcb96f7342b71ff1b" + resolved "https://registry.npmjs.org/@atproto/common/-/common-0.2.0.tgz" integrity sha512-PVYSC30pyonz2MOxuBLk27uGdwyZQ42gJfCA/NE9jLeuenVDmZnVrK5WqJ7eGg+F88rZj7NcGfRsZdP0GMykEQ== dependencies: "@atproto/common-web" "*" @@ -24,7 +24,7 @@ "@atproto/crypto@*": version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.1.tgz#54afad2124c3867091e4d9b271f22d375fcfdf9e" + resolved "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.1.1.tgz" integrity sha512-/7Ntn55dRZPtCnOd6dVo1IvZzpVut6YTAkZ8iFry9JW29l7ZeNkJd+NTnmWRz3aGQody10jngb4SNxQNi/f3+A== dependencies: "@noble/secp256k1" "^1.7.0" @@ -33,19 +33,9 @@ one-webcrypto "^1.0.3" uint8arrays "3.0.0" -"@atproto/did-resolver@*": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@atproto/did-resolver/-/did-resolver-0.0.1.tgz#e54c1b7fddff2cd6adf87c044b4a3b6f00d5eff7" - integrity sha512-sdva3+nydMaWXwHJED558UZdVZuajfC2CHcsIZz0pQybicm3VI+khkf42ClZeOhf4Bwa4V4SOaaAqwyf86bDew== - dependencies: - "@atproto/common" "*" - "@atproto/crypto" "*" - axios "^0.24.0" - did-resolver "^4.0.0" - -"@atproto/did-resolver@^0.1.0": +"@atproto/did-resolver@*", "@atproto/did-resolver@^0.1.0": version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/did-resolver/-/did-resolver-0.1.0.tgz#58f42447700aaad61bad2f0d70b721966268aa02" + resolved "https://registry.npmjs.org/@atproto/did-resolver/-/did-resolver-0.1.0.tgz" integrity sha512-ztljyMMCqXvJSi/Qqa2zEQFvOm1AUUR7Bybr3cM1BCddbhW46gk6/g8BgdZeDt2sMBdye37qTctR9O/FjhigvQ== dependencies: "@atproto/common-web" "*" @@ -55,14 +45,14 @@ "@atproto/identifier@*": version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75" + resolved "https://registry.npmjs.org/@atproto/identifier/-/identifier-0.1.0.tgz" integrity sha512-3LV7+4E6S0k8Rru7NBkyDF6Zf6NHVUXVS9d4l9fiXWMC49ghZMjq0vPmz80xjG1rRuFdJFbpRf4ApFciGxLIyQ== dependencies: "@atproto/common-web" "*" -"@atproto/lexicon@*": +"@atproto/lexicon@*", "@atproto/lexicon@^0.1.0": version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.1.0.tgz#e7784cc868c734314d5bf9af83487aba7ccae0b3" + resolved "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.1.0.tgz" integrity sha512-Iy+gV9w42xLhrZrmcbZh7VFoHjXuzWvecGHIfz44owNjjv7aE/d2P5BbOX/XicSkmQ8Qkpg0BqwYDD1XBVS+DQ== dependencies: "@atproto/common-web" "*" @@ -75,12 +65,12 @@ "@atproto/nsid@*": version "0.0.1" - resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4" + resolved "https://registry.npmjs.org/@atproto/nsid/-/nsid-0.0.1.tgz" integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw== "@atproto/repo@^0.1.0": version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.1.0.tgz#8c546af16c30fe5ba4c883ac73b68be9d7eca273" + resolved "https://registry.npmjs.org/@atproto/repo/-/repo-0.1.0.tgz" integrity sha512-O4qs5WfSjEFvUtpOTB4n9cLcK6YP/w/ly6Qxc3S8IFevLGYX58NPPr5zlg3dxs64uLKbWWjzhQM7JAqO44MEKw== dependencies: "@atproto/common" "*" @@ -96,7 +86,7 @@ "@atproto/uri@*", "@atproto/uri@^0.0.2": version "0.0.2" - resolved "https://registry.yarnpkg.com/@atproto/uri/-/uri-0.0.2.tgz#c6d3788e6f12d66ba72690d2d70fe6c291b4acfb" + resolved "https://registry.npmjs.org/@atproto/uri/-/uri-0.0.2.tgz" integrity sha512-/6otLZF7BLpT9suSdHuXLbL12nINcWPsLmcOI+dctqovWUjH+XIRVNXDQgBYSrPVetxMiknuEwWelmnA33AEXg== dependencies: "@atproto/identifier" "*" @@ -104,7 +94,7 @@ "@atproto/xrpc-server@^0.2.0": version "0.2.0" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.2.0.tgz#a36616c2ac70339cd79cda83ede0a0b305c74f9b" + resolved "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.2.0.tgz" integrity sha512-sCJuVUIb1tDIlKCFwHPRHbAgEy0HYGlQ7XhpNqMRKXECh8Z+DRICEne3gLDVaXhyNaC/N7OjHcsyuofDDbuGFQ== dependencies: "@atproto/common" "*" @@ -120,7 +110,7 @@ "@cbor-extract/cbor-extract-darwin-arm64@2.1.1": version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz#5721f6dd3feae0b96d23122853ce977e0671b7a6" + resolved "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz" integrity sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA== "@cbor-extract/cbor-extract-darwin-x64@2.1.1": @@ -150,14 +140,14 @@ "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" "@ipld/car@^3.2.3": version "3.2.4" - resolved "https://registry.yarnpkg.com/@ipld/car/-/car-3.2.4.tgz#115951ba2255ec51d865773a074e422c169fb01c" + resolved "https://registry.npmjs.org/@ipld/car/-/car-3.2.4.tgz" integrity sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw== dependencies: "@ipld/dag-cbor" "^7.0.0" @@ -166,7 +156,7 @@ "@ipld/dag-cbor@^7.0.0", "@ipld/dag-cbor@^7.0.3": version "7.0.3" - resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" + resolved "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz" integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== dependencies: cborg "^1.6.0" @@ -174,17 +164,17 @@ "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -192,39 +182,39 @@ "@noble/secp256k1@^1.7.0": version "1.7.1" - resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" + resolved "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz" integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== "@tsconfig/node10@^1.0.7": version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/better-sqlite3@^7.6.4": version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/better-sqlite3/-/better-sqlite3-7.6.4.tgz#102462611e67aadf950d3ccca10292de91e6f35b" + resolved "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.4.tgz" integrity sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg== dependencies: "@types/node" "*" "@types/body-parser@*": version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: "@types/connect" "*" @@ -232,14 +222,14 @@ "@types/connect@*": version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" "@types/express-serve-static-core@^4.17.33": version "4.17.34" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz#c119e85b75215178bc127de588e93100698ab4cc" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz" integrity sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w== dependencies: "@types/node" "*" @@ -249,7 +239,7 @@ "@types/express@^4.17.17": version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz" integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== dependencies: "@types/body-parser" "*" @@ -257,34 +247,29 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/mime@^1": +"@types/mime@*", "@types/mime@^1": version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@*", "@types/node@^20.1.1": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.1.tgz#afc492e8dbe7f672dd3a13674823522b467a45ad" - integrity sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A== +"@types/node@*", "@types/node@^20.1.2": + version "20.1.2" + resolved "https://registry.npmjs.org/@types/node/-/node-20.1.2.tgz" + integrity sha512-CTO/wa8x+rZU626cL2BlbCDzydgnFNgc19h4YvizpTO88MFQxab8wqisxaofQJ/9bLGugRdWIuX/TbIs6VVF6g== "@types/qs@*": version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/send@*": version "0.17.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz" integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== dependencies: "@types/mime" "^1" @@ -292,7 +277,7 @@ "@types/serve-static@*": version "1.15.1" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz" integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== dependencies: "@types/mime" "*" @@ -300,14 +285,14 @@ abort-controller@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: event-target-shim "^5.0.0" accepts@~1.3.8: version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: mime-types "~2.1.34" @@ -315,44 +300,37 @@ accepts@~1.3.8: acorn-walk@^8.1.1: version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^8.4.1: version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== arg@^4.1.0: version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== array-flatten@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== atomic-sleep@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -axios@^0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" - integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== - dependencies: - follow-redirects "^1.14.4" - axios@^0.27.2: version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== dependencies: follow-redirects "^1.14.9" @@ -360,12 +338,12 @@ axios@^0.27.2: base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== better-sqlite3@^8.3.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.3.0.tgz#3873ddfd9f2af90b628657e8ff221786c93d1984" + resolved "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.3.0.tgz" integrity sha512-JTmvBZL/JLTc+3Msbvq6gK6elbU9/wVMqiudplHrVJpr7sVMR9KJrNhZAbW+RhXKlpMcuEhYkdcHa3TXKNXQ1w== dependencies: bindings "^1.5.0" @@ -373,19 +351,19 @@ better-sqlite3@^8.3.0: big-integer@^1.6.51: version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== bindings@^1.5.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" bl@^4.0.3: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -394,7 +372,7 @@ bl@^4.0.3: body-parser@1.20.1: version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz" integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" @@ -412,7 +390,7 @@ body-parser@1.20.1: buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -420,7 +398,7 @@ buffer@^5.5.0: buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" @@ -428,12 +406,12 @@ buffer@^6.0.3: bytes@3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== call-bind@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -441,7 +419,7 @@ call-bind@^1.0.0: cbor-extract@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.1.1.tgz#f154b31529fdb6b7c70fb3ca448f44eda96a1b42" + resolved "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.1.1.tgz" integrity sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA== dependencies: node-gyp-build-optional-packages "5.0.3" @@ -455,154 +433,149 @@ cbor-extract@^2.1.1: cbor-x@^1.5.1: version "1.5.2" - resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.5.2.tgz#ceabc48bda06185de1f3a078bb4a793e6e222de5" + resolved "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.2.tgz" integrity sha512-JArE6xcgj3eo13fpnShO42QFBUuXP2uG12RLeF2Nb+dJcETFYxkUa27gXQrRYp67Ahtaxyfbg+ihc62XTyQqsQ== optionalDependencies: cbor-extract "^2.1.1" cborg@^1.6.0: version "1.10.1" - resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.1.tgz#24cfe52c69ec0f66f95e23dc57f2086954c8d718" + resolved "https://registry.npmjs.org/cborg/-/cborg-1.10.1.tgz" integrity sha512-et6Qm8MOUY2kCWa5GKk2MlBVoPjHv0hQBmlzI/Z7+5V3VJCeIkGehIB3vWknNsm2kOkAIs6wEKJFJo8luWQQ/w== chownr@^1.1.1: version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" content-disposition@0.5.4: version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" content-type@~1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== cookie-signature@1.0.6: version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== cookie@0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== debug@2.6.9: version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" decompress-response@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: mimic-response "^3.1.0" deep-extend@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== depd@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== destroy@1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detect-libc@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -did-resolver@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.1.0.tgz#740852083c4fd5bf9729d528eca5d105aff45eb6" - integrity sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA== - diff@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dotenv@^16.0.3: version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== ee-first@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== encodeurl@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" escape-html@~1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== etag@~1.8.1: version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-target-shim@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== events@^3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== expand-template@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== express@^4.17.2, express@^4.18.2: version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" @@ -639,17 +612,17 @@ express@^4.17.2, express@^4.18.2: fast-redact@^3.1.1: version "3.1.2" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" + resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz" integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== file-uri-to-path@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== finalhandler@1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== dependencies: debug "2.6.9" @@ -660,14 +633,14 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -follow-redirects@^1.14.4, follow-redirects@^1.14.9: +follow-redirects@^1.14.9: version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== form-data@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" @@ -676,27 +649,27 @@ form-data@^4.0.0: forwarded@0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fresh@0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== get-intrinsic@^1.0.2: version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== dependencies: function-bind "^1.1.1" @@ -705,24 +678,24 @@ get-intrinsic@^1.0.2: github-from-package@0.0.0: version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" http-errors@2.0.0, http-errors@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== dependencies: depd "2.0.0" @@ -733,179 +706,179 @@ http-errors@2.0.0, http-errors@^2.0.0: iconv-lite@0.4.24: version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~1.3.0: version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ipaddr.js@1.9.1: version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== iso-datestring-validator@^2.2.2: version "2.2.2" - resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" + resolved "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz" integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== kysely@^0.22.0: version "0.22.0" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.22.0.tgz#8aac53942da3cadc604d7d154a746d983fe8f7b9" + resolved "https://registry.npmjs.org/kysely/-/kysely-0.22.0.tgz" integrity sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ== lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" make-error@^1.1.1: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== media-typer@0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== merge-descriptors@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== methods@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" mime@1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mimic-response@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimist@^1.2.0, minimist@^1.2.3: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== ms@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4: +multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4, multiformats@^9.9.0: version "9.9.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz" integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== napi-build-utils@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== negotiator@0.6.3: version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== node-abi@^3.3.0: version "3.40.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.40.0.tgz#51d8ed44534f70ff1357dfbc3a89717b1ceac1b4" + resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz" integrity sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA== dependencies: semver "^7.3.5" node-gyp-build-optional-packages@5.0.3: version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" + resolved "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz" integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== object-inspect@^1.9.0: version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== on-exit-leak-free@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" + resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz" integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== on-finished@2.4.1: version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" one-webcrypto@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/one-webcrypto/-/one-webcrypto-1.0.3.tgz#f951243cde29b79b6745ad14966fc598a609997c" + resolved "https://registry.npmjs.org/one-webcrypto/-/one-webcrypto-1.0.3.tgz" integrity sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q== parseurl@~1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== path-to-regexp@0.1.7: version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== pino-abstract-transport@v1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" + resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz" integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== dependencies: readable-stream "^4.0.0" @@ -913,12 +886,12 @@ pino-abstract-transport@v1.0.0: pino-std-serializers@^6.0.0: version "6.2.1" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz#369f4ae2a19eb6d769ddf2c88a2164b76879a284" + resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz" integrity sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ== pino@^8.6.1: version "8.14.1" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.14.1.tgz#bb38dcda8b500dd90c1193b6c9171eb777a47ac8" + resolved "https://registry.npmjs.org/pino/-/pino-8.14.1.tgz" integrity sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw== dependencies: atomic-sleep "^1.0.0" @@ -935,7 +908,7 @@ pino@^8.6.1: prebuild-install@^7.1.0: version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== dependencies: detect-libc "^2.0.0" @@ -953,17 +926,17 @@ prebuild-install@^7.1.0: process-warning@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626" + resolved "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz" integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg== process@^0.11.10: version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== proxy-addr@~2.0.7: version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: forwarded "0.2.0" @@ -971,7 +944,7 @@ proxy-addr@~2.0.7: pump@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" @@ -979,24 +952,24 @@ pump@^3.0.0: qs@6.11.0: version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" quick-format-unescaped@^4.0.3: version "4.0.4" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== range-parser@~1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== raw-body@2.5.1: version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: bytes "3.1.2" @@ -1006,7 +979,7 @@ raw-body@2.5.1: rc@^1.2.7: version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: deep-extend "^0.6.0" @@ -1016,7 +989,7 @@ rc@^1.2.7: readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -1025,7 +998,7 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: readable-stream@^4.0.0: version "4.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz" integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== dependencies: abort-controller "^3.0.0" @@ -1035,34 +1008,34 @@ readable-stream@^4.0.0: real-require@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-stable-stringify@^2.3.1: version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== "safer-buffer@>= 2.1.2 < 3": version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^7.3.5: version "7.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz" integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" send@0.18.0: version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" @@ -1081,7 +1054,7 @@ send@0.18.0: serve-static@1.15.0: version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" @@ -1091,12 +1064,12 @@ serve-static@1.15.0: setprototypeof@1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" @@ -1105,12 +1078,12 @@ side-channel@^1.0.4: simple-concat@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== simple-get@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== dependencies: decompress-response "^6.0.0" @@ -1119,36 +1092,36 @@ simple-get@^4.0.0: sonic-boom@^3.1.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c" + resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz" integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g== dependencies: atomic-sleep "^1.0.0" split2@^4.0.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== statuses@2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" strip-json-comments@~2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== tar-fs@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: chownr "^1.1.1" @@ -1158,7 +1131,7 @@ tar-fs@^2.0.0: tar-stream@^2.1.4: version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -1169,19 +1142,19 @@ tar-stream@^2.1.4: thread-stream@^2.0.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33" + resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz" integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA== dependencies: real-require "^0.2.0" toidentifier@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== ts-node@^10.9.1: version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -1200,14 +1173,14 @@ ts-node@^10.9.1: tunnel-agent@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: safe-buffer "^5.0.1" type-is@~1.6.18: version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" @@ -1215,67 +1188,67 @@ type-is@~1.6.18: typescript@^5.0.4: version "5.0.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz" integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== uint8arrays@3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" + resolved "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz" integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== dependencies: multiformats "^9.4.2" unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== utils-merge@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== varint@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" + resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== vary@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^8.12.0: version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yn@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== zod@^3.14.2: version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + resolved "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz" integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== From 050047fcd07eb47c0ad99e01820e42174bc0a557 Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 18:43:47 -0500 Subject: [PATCH 14/38] add did-web example --- src/config.ts | 1 + src/server.ts | 3 +++ src/well-known.ts | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/well-known.ts diff --git a/src/config.ts b/src/config.ts index e3576eb60..0ad853576 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,6 +9,7 @@ export type AppContext = { export type Config = { port: number + hostname: string sqliteLocation: string subscriptionEndpoint: string serviceDid: string diff --git a/src/server.ts b/src/server.ts index bbcf69f0a..07d23b3b1 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,6 +7,7 @@ import feedGeneration from './feed-generation' import { createDb, Database, migrateToLatest } from './db' import { FirehoseSubscription } from './subscription' import { AppContext, Config } from './config' +import wellKnown from './well-known' export class FeedGenerator { public app: express.Application @@ -30,6 +31,7 @@ export class FeedGenerator { static create(config?: Partial) { const cfg: Config = { port: config?.port ?? 3000, + hostname: config?.hostname ?? 'feed-generator.test', sqliteLocation: config?.sqliteLocation ?? ':memory:', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', serviceDid: config?.serviceDid ?? 'did:example:test', @@ -59,6 +61,7 @@ export class FeedGenerator { } feedGeneration(server, ctx) app.use(server.xrpc.router) + app.use(wellKnown(cfg.hostname)) return new FeedGenerator(app, db, firehose, cfg) } diff --git a/src/well-known.ts b/src/well-known.ts new file mode 100644 index 000000000..b869b6ca9 --- /dev/null +++ b/src/well-known.ts @@ -0,0 +1,22 @@ +import express from 'express' + +const makeRouter = (serverHostname: string) => { + const router = express.Router() + + router.get('/.well-known/did.json', (_req, res) => { + res.json({ + '@context': ['https://www.w3.org/ns/did/v1'], + id: `did:web:${serverHostname}`, + service: [ + { + id: '#bsky_fg', + type: 'BskyFeedGenerator', + serviceEndpoint: `https://${serverHostname}`, + }, + ], + }) + }) + + return router +} +export default makeRouter From 17ad1e9d799b5551b7711430c7009ad5c1477a4c Mon Sep 17 00:00:00 2001 From: Alice <81575558+aliceisjustplaying@users.noreply.github.com> Date: Fri, 12 May 2023 04:26:10 +0100 Subject: [PATCH 15/38] add .env.example, additional env var (#10) --- .env.example | 4 ++++ src/index.ts | 1 + 2 files changed, 5 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..ebd0e2b1a --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +FEEDGEN_PORT=3000 +FEEDGEN_SQLITE_LOCATION=":memory:" +FEEDGEN_SUBSCRIPTION_ENDPOINT="wss://bsky.social" +FEEDGEN_SERVICE_DID="did:example:test" diff --git a/src/index.ts b/src/index.ts index 5974b4094..a8486d79d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ const run = async () => { port: maybeInt(process.env.FEEDGEN_PORT), sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION), subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT), + serviceDid: maybeStr(process.env.FEEDGEN_SERVICE_DID), }) await server.start() console.log( From ceae744601ac9e49f7c2e7b1d9280bb511c09386 Mon Sep 17 00:00:00 2001 From: Alice <81575558+aliceisjustplaying@users.noreply.github.com> Date: Fri, 12 May 2023 04:28:27 +0100 Subject: [PATCH 16/38] Update README with instructions for the default URL (#11) * update README with instructions for the default URL * Update README.md --------- Co-authored-by: Daniel Holmgren --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5cc7a8331..cf707a67e 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ We've taken care of setting this server up with a did:web. However, you're free Once the custom algorithms feature launches, you'll be able to publish your feed in-app by providing the DID of your service. +## Running the Server + +Install dependencies with `yarn` and then run the server with `yarn start`. This will start the server on port 3000, or what's defined in `.env`. You can then watch the firehose output in the console and access the output of the default custom ALF feed at [http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=did:example:alice/app.bsky.feed.generator/whats-alf](http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=did:example:alice/app.bsky.feed.generator/whats-alf). + ## Some Details ### Skeleton Metadata From 15ed3bec9e57666b736fdcdb653d8ea550d1bbb1 Mon Sep 17 00:00:00 2001 From: V <78769380+codegod100@users.noreply.github.com> Date: Thu, 11 May 2023 20:29:09 -0700 Subject: [PATCH 17/38] Update .gitignore (#12) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 37d7e7348..fc453fb42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .env +.yarn From c936d5ae9e1dd497b6872dda5b75601ce3892f00 Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 11 May 2023 22:29:47 -0500 Subject: [PATCH 18/38] moar gitignore --- .gitignore | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fc453fb42..6a7d6d8ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,130 @@ -node_modules +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files .env -.yarn +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file From bc753388c3c02cebeee961ec456cca07ec8bc19d Mon Sep 17 00:00:00 2001 From: dholms Date: Fri, 12 May 2023 17:22:13 -0500 Subject: [PATCH 19/38] update lexicons & docs --- README.md | 13 +- src/feed-generation.ts | 7 - src/lexicon/index.ts | 83 +- src/lexicon/lexicons.ts | 706 ++++++++++++++++-- src/lexicon/types/app/bsky/actor/defs.ts | 33 +- src/lexicon/types/app/bsky/feed/defs.ts | 58 +- src/lexicon/types/app/bsky/feed/generator.ts | 31 + .../types/app/bsky/feed/getActorFeeds.ts | 45 ++ .../types/app/bsky/feed/getBookmarkedFeeds.ts | 4 +- src/lexicon/types/app/bsky/graph/defs.ts | 95 +++ src/lexicon/types/app/bsky/graph/getList.ts | 46 ++ .../types/app/bsky/graph/getListMutes.ts | 44 ++ src/lexicon/types/app/bsky/graph/getLists.ts | 45 ++ src/lexicon/types/app/bsky/graph/list.ts | 32 + src/lexicon/types/app/bsky/graph/listitem.ts | 27 + .../types/app/bsky/graph/muteActorList.ts | 35 + .../types/app/bsky/graph/unmuteActorList.ts | 35 + src/lexicon/types/com/atproto/admin/defs.ts | 2 + .../atproto/admin/disableAccountInvites.ts | 35 + .../com/atproto/admin/enableAccountInvites.ts | 35 + .../types/com/atproto/repo/rebaseRepo.ts | 39 + 21 files changed, 1311 insertions(+), 139 deletions(-) create mode 100644 src/lexicon/types/app/bsky/feed/generator.ts create mode 100644 src/lexicon/types/app/bsky/feed/getActorFeeds.ts create mode 100644 src/lexicon/types/app/bsky/graph/defs.ts create mode 100644 src/lexicon/types/app/bsky/graph/getList.ts create mode 100644 src/lexicon/types/app/bsky/graph/getListMutes.ts create mode 100644 src/lexicon/types/app/bsky/graph/getLists.ts create mode 100644 src/lexicon/types/app/bsky/graph/list.ts create mode 100644 src/lexicon/types/app/bsky/graph/listitem.ts create mode 100644 src/lexicon/types/app/bsky/graph/muteActorList.ts create mode 100644 src/lexicon/types/app/bsky/graph/unmuteActorList.ts create mode 100644 src/lexicon/types/com/atproto/admin/disableAccountInvites.ts create mode 100644 src/lexicon/types/com/atproto/admin/enableAccountInvites.ts create mode 100644 src/lexicon/types/com/atproto/repo/rebaseRepo.ts diff --git a/README.md b/README.md index cf707a67e..aff81a61e 100644 --- a/README.md +++ b/README.md @@ -64,18 +64,12 @@ The skeleton that a Feed Generator puts together is, in its simplest form, a lis ] ``` -However, we include two locations to attach some additional context. Here is the full schema: +However, we include an additionl location to attach some context. Here is the full schema: ```ts type SkeletonItem = { post: string // post URI - - // optional metadata about the thread that this post is in reply to - replyTo?: { - root: string, // reply root URI - parent: string, // reply parent URI - } - + // optional reason for inclusion in the feed // (generally to be displayed in client) reason?: Reason @@ -86,8 +80,7 @@ type Reason = ReasonRepost type ReasonRepost = { $type: 'app.bsky.feed.defs#skeletonReasonRepost' - by: string // the did of the reposting user - indexedAt: string // the time that the repost took place + repost: string // repost URI } ``` diff --git a/src/feed-generation.ts b/src/feed-generation.ts index f1304a0db..5eab87b4b 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -42,13 +42,6 @@ export default function (server: Server, ctx: AppContext) { const feed = res.map((row) => ({ post: row.uri, - replyTo: - row.replyParent && row.replyRoot - ? { - root: row.replyRoot, - parent: row.replyParent, - } - : undefined, })) let cursor: string | undefined diff --git a/src/lexicon/index.ts b/src/lexicon/index.ts index 0fe1132bb..7759d837d 100644 --- a/src/lexicon/index.ts +++ b/src/lexicon/index.ts @@ -9,7 +9,9 @@ import { StreamAuthVerifier, } from '@atproto/xrpc-server' import { schemas } from './lexicons' +import * as ComAtprotoAdminDisableAccountInvites from './types/com/atproto/admin/disableAccountInvites' import * as ComAtprotoAdminDisableInviteCodes from './types/com/atproto/admin/disableInviteCodes' +import * as ComAtprotoAdminEnableAccountInvites from './types/com/atproto/admin/enableAccountInvites' import * as ComAtprotoAdminGetInviteCodes from './types/com/atproto/admin/getInviteCodes' import * as ComAtprotoAdminGetModerationAction from './types/com/atproto/admin/getModerationAction' import * as ComAtprotoAdminGetModerationActions from './types/com/atproto/admin/getModerationActions' @@ -35,6 +37,7 @@ import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRe import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' +import * as ComAtprotoRepoRebaseRepo from './types/com/atproto/repo/rebaseRepo' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' @@ -70,6 +73,7 @@ import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestio import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors' import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead' import * as AppBskyFeedBookmarkFeed from './types/app/bsky/feed/bookmarkFeed' +import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' import * as AppBskyFeedGetBookmarkedFeeds from './types/app/bsky/feed/getBookmarkedFeeds' import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' @@ -83,9 +87,14 @@ import * as AppBskyFeedUnbookmarkFeed from './types/app/bsky/feed/unbookmarkFeed import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' +import * as AppBskyGraphGetList from './types/app/bsky/graph/getList' +import * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes' +import * as AppBskyGraphGetLists from './types/app/bsky/graph/getLists' import * as AppBskyGraphGetMutes from './types/app/bsky/graph/getMutes' import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' +import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' +import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount' import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications' import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen' @@ -105,9 +114,8 @@ export const COM_ATPROTO_MODERATION = { DefsReasonRude: 'com.atproto.moderation.defs#reasonRude', DefsReasonOther: 'com.atproto.moderation.defs#reasonOther', } -export const APP_BSKY_ACTOR = { - DefsUser: 'app.bsky.actor.defs#user', - DefsFeedGenerator: 'app.bsky.actor.defs#feedGenerator', +export const APP_BSKY_GRAPH = { + DefsModlist: 'app.bsky.graph.defs#modlist', } export function createServer(options?: XrpcOptions): Server { @@ -165,6 +173,16 @@ export class AdminNS { this._server = server } + disableAccountInvites( + cfg: ConfigOf< + AV, + ComAtprotoAdminDisableAccountInvites.Handler> + >, + ) { + const nsid = 'com.atproto.admin.disableAccountInvites' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + disableInviteCodes( cfg: ConfigOf< AV, @@ -175,6 +193,16 @@ export class AdminNS { return this._server.xrpc.method(nsid, cfg) } + enableAccountInvites( + cfg: ConfigOf< + AV, + ComAtprotoAdminEnableAccountInvites.Handler> + >, + ) { + const nsid = 'com.atproto.admin.enableAccountInvites' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getInviteCodes( cfg: ConfigOf>>, ) { @@ -412,6 +440,13 @@ export class RepoNS { return this._server.xrpc.method(nsid, cfg) } + rebaseRepo( + cfg: ConfigOf>>, + ) { + const nsid = 'com.atproto.repo.rebaseRepo' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + uploadBlob( cfg: ConfigOf>>, ) { @@ -756,6 +791,13 @@ export class FeedNS { return this._server.xrpc.method(nsid, cfg) } + getActorFeeds( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.feed.getActorFeeds' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getAuthorFeed( cfg: ConfigOf>>, ) { @@ -855,6 +897,27 @@ export class GraphNS { return this._server.xrpc.method(nsid, cfg) } + getList( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.graph.getList' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + getListMutes( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.graph.getListMutes' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + getLists( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.graph.getLists' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getMutes( cfg: ConfigOf>>, ) { @@ -869,12 +932,26 @@ export class GraphNS { return this._server.xrpc.method(nsid, cfg) } + muteActorList( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.graph.muteActorList' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + unmuteActor( cfg: ConfigOf>>, ) { const nsid = 'app.bsky.graph.unmuteActor' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + unmuteActorList( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.graph.unmuteActorList' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class NotificationNS { diff --git a/src/lexicon/lexicons.ts b/src/lexicon/lexicons.ts index 5e07faeb0..d242054aa 100644 --- a/src/lexicon/lexicons.ts +++ b/src/lexicon/lexicons.ts @@ -333,6 +333,9 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.server.defs#inviteCode', }, + invitesDisabled: { + type: 'boolean', + }, }, }, repoViewDetail: { @@ -388,6 +391,9 @@ export const schemaDict = { ref: 'lex:com.atproto.server.defs#inviteCode', }, }, + invitesDisabled: { + type: 'boolean', + }, }, }, repoRef: { @@ -589,6 +595,30 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminDisableAccountInvites: { + lexicon: 1, + id: 'com.atproto.admin.disableAccountInvites', + defs: { + main: { + type: 'procedure', + description: + 'Disable an account from receiving new invite codes, but does not invalidate existing codes', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['account'], + properties: { + account: { + type: 'string', + format: 'did', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminDisableInviteCodes: { lexicon: 1, id: 'com.atproto.admin.disableInviteCodes', @@ -620,6 +650,29 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminEnableAccountInvites: { + lexicon: 1, + id: 'com.atproto.admin.enableAccountInvites', + defs: { + main: { + type: 'procedure', + description: 'Re-enable an accounts ability to receive invite codes', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['account'], + properties: { + account: { + type: 'string', + format: 'did', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminGetInviteCodes: { lexicon: 1, id: 'com.atproto.admin.getInviteCodes', @@ -1959,6 +2012,41 @@ export const schemaDict = { }, }, }, + ComAtprotoRepoRebaseRepo: { + lexicon: 1, + id: 'com.atproto.repo.rebaseRepo', + defs: { + main: { + type: 'procedure', + description: 'Simple rebase of repo that deletes history', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['repo'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: 'The handle or DID of the repo.', + }, + swapCommit: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous commit by cid.', + }, + }, + }, + }, + errors: [ + { + name: 'InvalidSwap', + }, + ], + }, + }, + }, ComAtprotoRepoStrongRef: { lexicon: 1, id: 'com.atproto.repo.strongRef', @@ -3285,10 +3373,6 @@ export const schemaDict = { avatar: { type: 'string', }, - actorType: { - type: 'ref', - ref: 'lex:app.bsky.actor.defs#actorType', - }, viewer: { type: 'ref', ref: 'lex:app.bsky.actor.defs#viewerState', @@ -3327,10 +3411,6 @@ export const schemaDict = { avatar: { type: 'string', }, - actorType: { - type: 'ref', - ref: 'lex:app.bsky.actor.defs#actorType', - }, indexedAt: { type: 'string', format: 'datetime', @@ -3373,14 +3453,6 @@ export const schemaDict = { avatar: { type: 'string', }, - actorType: { - type: 'ref', - ref: 'lex:app.bsky.actor.defs#actorType', - }, - actorInfo: { - type: 'union', - refs: ['lex:app.bsky.actor.defs#infoFeedGenerator'], - }, banner: { type: 'string', }, @@ -3416,6 +3488,10 @@ export const schemaDict = { muted: { type: 'boolean', }, + mutedByList: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listViewBasic', + }, blockedBy: { type: 'boolean', }, @@ -3433,32 +3509,6 @@ export const schemaDict = { }, }, }, - infoFeedGenerator: { - type: 'object', - required: ['likes'], - properties: { - likes: { - type: 'integer', - }, - }, - }, - actorType: { - type: 'string', - knownValues: [ - 'app.bsky.actor.defs#user', - 'app.bsky.actor.defs#feedGenerator', - ], - }, - user: { - type: 'token', - description: - 'Actor type: User. This is the default option and an actor is assumed to be a user unless suggested otherwise.', - }, - feedGenerator: { - type: 'token', - description: - 'Actor type: Feed Generator. A service that provides a custom feed.', - }, }, }, AppBskyActorGetProfile: { @@ -3973,7 +4023,7 @@ export const schemaDict = { properties: { feed: { type: 'string', - format: 'at-identifier', + format: 'at-uri', }, }, }, @@ -4163,49 +4213,178 @@ export const schemaDict = { }, }, }, - skeletonFeedPost: { + generatorView: { type: 'object', - required: ['post'], + required: ['uri', 'creator', 'indexedAt'], properties: { - post: { + uri: { type: 'string', format: 'at-uri', }, - replyTo: { + did: { + type: 'string', + format: 'did', + }, + creator: { type: 'ref', - ref: 'lex:app.bsky.feed.defs#skeletonReplyRef', + ref: 'lex:app.bsky.actor.defs#profileView', }, - reason: { - type: 'union', - refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], + displayName: { + type: 'string', + }, + description: { + type: 'string', + maxGraphemes: 300, + maxLength: 3000, + }, + descriptionFacets: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.richtext.facet', + }, + }, + avatar: { + type: 'string', + }, + viewer: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorViewerState', + }, + indexedAt: { + type: 'string', + format: 'datetime', }, }, }, - skeletonReplyRef: { + generatorViewerState: { type: 'object', - required: ['root', 'parent'], properties: { - root: { + subscribed: { + type: 'boolean', + }, + like: { type: 'string', - ref: 'at-uri', + format: 'at-uri', }, - parent: { + }, + }, + skeletonFeedPost: { + type: 'object', + required: ['post'], + properties: { + post: { type: 'string', - ref: 'at-uri', + format: 'at-uri', + }, + reason: { + type: 'union', + refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], }, }, }, skeletonReasonRepost: { type: 'object', - required: ['by', 'indexedAt'], + required: ['repost'], properties: { - by: { + repost: { type: 'string', - format: 'did', + ref: 'at-uri', }, - indexedAt: { - type: 'string', - format: 'datetime', + }, + }, + }, + }, + AppBskyFeedGenerator: { + lexicon: 1, + id: 'app.bsky.feed.generator', + defs: { + main: { + type: 'record', + description: 'A declaration of the existence of a feed generator', + key: 'any', + record: { + type: 'object', + required: ['did', 'createdAt'], + properties: { + did: { + type: 'string', + format: 'did', + }, + displayName: { + type: 'string', + maxGraphemes: 64, + maxLength: 640, + }, + description: { + type: 'string', + maxGraphemes: 300, + maxLength: 3000, + }, + descriptionFacets: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.richtext.facet', + }, + }, + avatar: { + type: 'blob', + accept: ['image/png', 'image/jpeg'], + maxSize: 1000000, + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + AppBskyFeedGetActorFeeds: { + lexicon: 1, + id: 'app.bsky.feed.getActorFeeds', + defs: { + main: { + type: 'query', + description: 'Retrieve a list of feeds created by a given actor', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feeds'], + properties: { + cursor: { + type: 'string', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', + }, + }, + }, }, }, }, @@ -4302,7 +4481,7 @@ export const schemaDict = { type: 'array', items: { type: 'ref', - ref: 'lex:app.bsky.actor.defs#profileView', + ref: 'lex:app.bsky.feed.defs#generatorView', }, }, }, @@ -4325,7 +4504,7 @@ export const schemaDict = { properties: { feed: { type: 'string', - format: 'at-identifier', + format: 'at-uri', }, limit: { type: 'integer', @@ -4373,6 +4552,7 @@ export const schemaDict = { properties: { feed: { type: 'string', + format: 'at-uri', }, limit: { type: 'integer', @@ -4837,7 +5017,7 @@ export const schemaDict = { properties: { feed: { type: 'string', - format: 'at-identifier', + format: 'at-uri', }, }, }, @@ -4870,11 +5050,120 @@ export const schemaDict = { }, }, }, - AppBskyGraphFollow: { + AppBskyGraphDefs: { lexicon: 1, - id: 'app.bsky.graph.follow', + id: 'app.bsky.graph.defs', defs: { - main: { + listViewBasic: { + type: 'object', + required: ['uri', 'creator', 'name', 'purpose'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + name: { + type: 'string', + maxLength: 64, + minLength: 1, + }, + purpose: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listPurpose', + }, + avatar: { + type: 'string', + }, + viewer: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listViewerState', + }, + indexedAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + listView: { + type: 'object', + required: ['uri', 'creator', 'name', 'purpose', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + creator: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#profileView', + }, + name: { + type: 'string', + maxLength: 64, + minLength: 1, + }, + purpose: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listPurpose', + }, + description: { + type: 'string', + maxGraphemes: 300, + maxLength: 3000, + }, + descriptionFacets: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.richtext.facet', + }, + }, + avatar: { + type: 'string', + }, + viewer: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listViewerState', + }, + indexedAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + listItemView: { + type: 'object', + required: ['subject'], + properties: { + subject: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#profileView', + }, + }, + }, + listPurpose: { + type: 'string', + knownValues: ['app.bsky.graph.defs#modlist'], + }, + modlist: { + type: 'token', + description: + 'A list of actors to apply an aggregate moderation action (mute/block) on', + }, + listViewerState: { + type: 'object', + properties: { + muted: { + type: 'boolean', + }, + }, + }, + }, + }, + AppBskyGraphFollow: { + lexicon: 1, + id: 'app.bsky.graph.follow', + defs: { + main: { type: 'record', description: 'A social follow.', key: 'tid', @@ -5042,6 +5331,149 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetList: { + lexicon: 1, + id: 'app.bsky.graph.getList', + defs: { + main: { + type: 'query', + description: 'Fetch a list of actors', + parameters: { + type: 'params', + required: ['list'], + properties: { + list: { + type: 'string', + format: 'at-uri', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['list', 'items'], + properties: { + cursor: { + type: 'string', + }, + list: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + items: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listItemView', + }, + }, + }, + }, + }, + }, + }, + }, + AppBskyGraphGetListMutes: { + lexicon: 1, + id: 'app.bsky.graph.getListMutes', + defs: { + main: { + type: 'query', + description: "Which lists is the requester's account muting?", + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['lists'], + properties: { + cursor: { + type: 'string', + }, + lists: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + }, + }, + }, + }, + }, + }, + }, + AppBskyGraphGetLists: { + lexicon: 1, + id: 'app.bsky.graph.getLists', + defs: { + main: { + type: 'query', + description: 'Fetch a list of lists that belong to an actor', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['lists'], + properties: { + cursor: { + type: 'string', + }, + lists: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphGetMutes: { lexicon: 1, id: 'app.bsky.graph.getMutes', @@ -5085,6 +5517,82 @@ export const schemaDict = { }, }, }, + AppBskyGraphList: { + lexicon: 1, + id: 'app.bsky.graph.list', + defs: { + main: { + type: 'record', + description: 'A declaration of a list of actors.', + key: 'tid', + record: { + type: 'object', + required: ['name', 'purpose', 'createdAt'], + properties: { + purpose: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listPurpose', + }, + name: { + type: 'string', + maxLength: 64, + minLength: 1, + }, + description: { + type: 'string', + maxGraphemes: 300, + maxLength: 3000, + }, + descriptionFacets: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.richtext.facet', + }, + }, + avatar: { + type: 'blob', + accept: ['image/png', 'image/jpeg'], + maxSize: 1000000, + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + AppBskyGraphListitem: { + lexicon: 1, + id: 'app.bsky.graph.listitem', + defs: { + main: { + type: 'record', + description: 'An item under a declared list of actors', + key: 'tid', + record: { + type: 'object', + required: ['subject', 'list', 'createdAt'], + properties: { + subject: { + type: 'string', + format: 'did', + }, + list: { + type: 'string', + format: 'at-uri', + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, AppBskyGraphMuteActor: { lexicon: 1, id: 'app.bsky.graph.muteActor', @@ -5108,6 +5616,29 @@ export const schemaDict = { }, }, }, + AppBskyGraphMuteActorList: { + lexicon: 1, + id: 'app.bsky.graph.muteActorList', + defs: { + main: { + type: 'procedure', + description: 'Mute a list of actors.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['list'], + properties: { + list: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, + }, + }, AppBskyGraphUnmuteActor: { lexicon: 1, id: 'app.bsky.graph.unmuteActor', @@ -5131,6 +5662,29 @@ export const schemaDict = { }, }, }, + AppBskyGraphUnmuteActorList: { + lexicon: 1, + id: 'app.bsky.graph.unmuteActorList', + defs: { + main: { + type: 'procedure', + description: 'Unmute a list of actors.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['list'], + properties: { + list: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, + }, + }, AppBskyNotificationGetUnreadCount: { lexicon: 1, id: 'app.bsky.notification.getUnreadCount', @@ -5406,7 +5960,10 @@ export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) export const ids = { ComAtprotoAdminDefs: 'com.atproto.admin.defs', + ComAtprotoAdminDisableAccountInvites: + 'com.atproto.admin.disableAccountInvites', ComAtprotoAdminDisableInviteCodes: 'com.atproto.admin.disableInviteCodes', + ComAtprotoAdminEnableAccountInvites: 'com.atproto.admin.enableAccountInvites', ComAtprotoAdminGetInviteCodes: 'com.atproto.admin.getInviteCodes', ComAtprotoAdminGetModerationAction: 'com.atproto.admin.getModerationAction', ComAtprotoAdminGetModerationActions: 'com.atproto.admin.getModerationActions', @@ -5436,6 +5993,7 @@ export const ids = { ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', + ComAtprotoRepoRebaseRepo: 'com.atproto.repo.rebaseRepo', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', @@ -5483,6 +6041,8 @@ export const ids = { AppBskyEmbedRecordWithMedia: 'app.bsky.embed.recordWithMedia', AppBskyFeedBookmarkFeed: 'app.bsky.feed.bookmarkFeed', AppBskyFeedDefs: 'app.bsky.feed.defs', + AppBskyFeedGenerator: 'app.bsky.feed.generator', + AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds', AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed', AppBskyFeedGetBookmarkedFeeds: 'app.bsky.feed.getBookmarkedFeeds', AppBskyFeedGetFeed: 'app.bsky.feed.getFeed', @@ -5497,13 +6057,21 @@ export const ids = { AppBskyFeedRepost: 'app.bsky.feed.repost', AppBskyFeedUnbookmarkFeed: 'app.bsky.feed.unbookmarkFeed', AppBskyGraphBlock: 'app.bsky.graph.block', + AppBskyGraphDefs: 'app.bsky.graph.defs', AppBskyGraphFollow: 'app.bsky.graph.follow', AppBskyGraphGetBlocks: 'app.bsky.graph.getBlocks', AppBskyGraphGetFollowers: 'app.bsky.graph.getFollowers', AppBskyGraphGetFollows: 'app.bsky.graph.getFollows', + AppBskyGraphGetList: 'app.bsky.graph.getList', + AppBskyGraphGetListMutes: 'app.bsky.graph.getListMutes', + AppBskyGraphGetLists: 'app.bsky.graph.getLists', AppBskyGraphGetMutes: 'app.bsky.graph.getMutes', + AppBskyGraphList: 'app.bsky.graph.list', + AppBskyGraphListitem: 'app.bsky.graph.listitem', AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', + AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor', + AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList', AppBskyNotificationGetUnreadCount: 'app.bsky.notification.getUnreadCount', AppBskyNotificationListNotifications: 'app.bsky.notification.listNotifications', diff --git a/src/lexicon/types/app/bsky/actor/defs.ts b/src/lexicon/types/app/bsky/actor/defs.ts index 3d826c466..162e45ada 100644 --- a/src/lexicon/types/app/bsky/actor/defs.ts +++ b/src/lexicon/types/app/bsky/actor/defs.ts @@ -6,13 +6,13 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import * as AppBskyGraphDefs from '../graph/defs' export interface ProfileViewBasic { did: string handle: string displayName?: string avatar?: string - actorType?: ActorType viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] [k: string]: unknown @@ -36,7 +36,6 @@ export interface ProfileView { displayName?: string description?: string avatar?: string - actorType?: ActorType indexedAt?: string viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] @@ -61,8 +60,6 @@ export interface ProfileViewDetailed { displayName?: string description?: string avatar?: string - actorType?: ActorType - actorInfo?: InfoFeedGenerator | { $type: string; [k: string]: unknown } banner?: string followersCount?: number followsCount?: number @@ -87,6 +84,7 @@ export function validateProfileViewDetailed(v: unknown): ValidationResult { export interface ViewerState { muted?: boolean + mutedByList?: AppBskyGraphDefs.ListViewBasic blockedBy?: boolean blocking?: string following?: string @@ -105,30 +103,3 @@ export function isViewerState(v: unknown): v is ViewerState { export function validateViewerState(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#viewerState', v) } - -export interface InfoFeedGenerator { - likes: number - [k: string]: unknown -} - -export function isInfoFeedGenerator(v: unknown): v is InfoFeedGenerator { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#infoFeedGenerator' - ) -} - -export function validateInfoFeedGenerator(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#infoFeedGenerator', v) -} - -export type ActorType = - | 'app.bsky.actor.defs#user' - | 'app.bsky.actor.defs#feedGenerator' - | (string & {}) - -/** Actor type: User. This is the default option and an actor is assumed to be a user unless suggested otherwise. */ -export const USER = 'app.bsky.actor.defs#user' -/** Actor type: Feed Generator. A service that provides a custom feed. */ -export const FEEDGENERATOR = 'app.bsky.actor.defs#feedGenerator' diff --git a/src/lexicon/types/app/bsky/feed/defs.ts b/src/lexicon/types/app/bsky/feed/defs.ts index cda2c4b46..45a152038 100644 --- a/src/lexicon/types/app/bsky/feed/defs.ts +++ b/src/lexicon/types/app/bsky/feed/defs.ts @@ -11,6 +11,7 @@ import * as AppBskyEmbedExternal from '../embed/external' import * as AppBskyEmbedRecord from '../embed/record' import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import * as AppBskyRichtextFacet from '../richtext/facet' export interface PostView { uri: string @@ -185,46 +186,69 @@ export function validateBlockedPost(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#blockedPost', v) } -export interface SkeletonFeedPost { - post: string - replyTo?: SkeletonReplyRef - reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } +export interface GeneratorView { + uri: string + did?: string + creator: AppBskyActorDefs.ProfileView + displayName?: string + description?: string + descriptionFacets?: AppBskyRichtextFacet.Main[] + avatar?: string + viewer?: GeneratorViewerState + indexedAt: string [k: string]: unknown } -export function isSkeletonFeedPost(v: unknown): v is SkeletonFeedPost { +export function isGeneratorView(v: unknown): v is GeneratorView { return ( isObj(v) && hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#skeletonFeedPost' + v.$type === 'app.bsky.feed.defs#generatorView' ) } -export function validateSkeletonFeedPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#skeletonFeedPost', v) +export function validateGeneratorView(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#generatorView', v) } -export interface SkeletonReplyRef { - root: string - parent: string +export interface GeneratorViewerState { + subscribed?: boolean + like?: string [k: string]: unknown } -export function isSkeletonReplyRef(v: unknown): v is SkeletonReplyRef { +export function isGeneratorViewerState(v: unknown): v is GeneratorViewerState { return ( isObj(v) && hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#skeletonReplyRef' + v.$type === 'app.bsky.feed.defs#generatorViewerState' ) } -export function validateSkeletonReplyRef(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#skeletonReplyRef', v) +export function validateGeneratorViewerState(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#generatorViewerState', v) +} + +export interface SkeletonFeedPost { + post: string + reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } + [k: string]: unknown +} + +export function isSkeletonFeedPost(v: unknown): v is SkeletonFeedPost { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#skeletonFeedPost' + ) +} + +export function validateSkeletonFeedPost(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#skeletonFeedPost', v) } export interface SkeletonReasonRepost { - by: string - indexedAt: string + repost: string [k: string]: unknown } diff --git a/src/lexicon/types/app/bsky/feed/generator.ts b/src/lexicon/types/app/bsky/feed/generator.ts new file mode 100644 index 000000000..c3082ffa4 --- /dev/null +++ b/src/lexicon/types/app/bsky/feed/generator.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import * as AppBskyRichtextFacet from '../richtext/facet' + +export interface Record { + did: string + displayName?: string + description?: string + descriptionFacets?: AppBskyRichtextFacet.Main[] + avatar?: BlobRef + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.feed.generator#main' || + v.$type === 'app.bsky.feed.generator') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.generator#main', v) +} diff --git a/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/src/lexicon/types/app/bsky/feed/getActorFeeds.ts new file mode 100644 index 000000000..e868dd39d --- /dev/null +++ b/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -0,0 +1,45 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + actor: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feeds: AppBskyFeedDefs.GeneratorView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts b/src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts index 0dad0a97c..176d44c6b 100644 --- a/src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts +++ b/src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts @@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' -import * as AppBskyActorDefs from '../actor/defs' +import * as AppBskyFeedDefs from './defs' export interface QueryParams { limit: number @@ -18,7 +18,7 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string - feeds: AppBskyActorDefs.ProfileView[] + feeds: AppBskyFeedDefs.GeneratorView[] [k: string]: unknown } diff --git a/src/lexicon/types/app/bsky/graph/defs.ts b/src/lexicon/types/app/bsky/graph/defs.ts new file mode 100644 index 000000000..cc2395abc --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/defs.ts @@ -0,0 +1,95 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import * as AppBskyActorDefs from '../actor/defs' +import * as AppBskyRichtextFacet from '../richtext/facet' + +export interface ListViewBasic { + uri: string + name: string + purpose: ListPurpose + avatar?: string + viewer?: ListViewerState + indexedAt?: string + [k: string]: unknown +} + +export function isListViewBasic(v: unknown): v is ListViewBasic { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.graph.defs#listViewBasic' + ) +} + +export function validateListViewBasic(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.defs#listViewBasic', v) +} + +export interface ListView { + uri: string + creator: AppBskyActorDefs.ProfileView + name: string + purpose: ListPurpose + description?: string + descriptionFacets?: AppBskyRichtextFacet.Main[] + avatar?: string + viewer?: ListViewerState + indexedAt: string + [k: string]: unknown +} + +export function isListView(v: unknown): v is ListView { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.graph.defs#listView' + ) +} + +export function validateListView(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.defs#listView', v) +} + +export interface ListItemView { + subject: AppBskyActorDefs.ProfileView + [k: string]: unknown +} + +export function isListItemView(v: unknown): v is ListItemView { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.graph.defs#listItemView' + ) +} + +export function validateListItemView(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.defs#listItemView', v) +} + +export type ListPurpose = 'app.bsky.graph.defs#modlist' | (string & {}) + +/** A list of actors to apply an aggregate moderation action (mute/block) on */ +export const MODLIST = 'app.bsky.graph.defs#modlist' + +export interface ListViewerState { + muted?: boolean + [k: string]: unknown +} + +export function isListViewerState(v: unknown): v is ListViewerState { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.graph.defs#listViewerState' + ) +} + +export function validateListViewerState(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.defs#listViewerState', v) +} diff --git a/src/lexicon/types/app/bsky/graph/getList.ts b/src/lexicon/types/app/bsky/graph/getList.ts new file mode 100644 index 000000000..31bb029f5 --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/getList.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + list: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + list: AppBskyGraphDefs.ListView + items: AppBskyGraphDefs.ListItemView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/graph/getListMutes.ts b/src/lexicon/types/app/bsky/graph/getListMutes.ts new file mode 100644 index 000000000..981f9cdb3 --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -0,0 +1,44 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + lists: AppBskyGraphDefs.ListView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/graph/getLists.ts b/src/lexicon/types/app/bsky/graph/getLists.ts new file mode 100644 index 000000000..9b4c06280 --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/getLists.ts @@ -0,0 +1,45 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + actor: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + lists: AppBskyGraphDefs.ListView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/graph/list.ts b/src/lexicon/types/app/bsky/graph/list.ts new file mode 100644 index 000000000..4304ca98b --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/list.ts @@ -0,0 +1,32 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import * as AppBskyGraphDefs from './defs' +import * as AppBskyRichtextFacet from '../richtext/facet' + +export interface Record { + purpose: AppBskyGraphDefs.ListPurpose + name: string + description?: string + descriptionFacets?: AppBskyRichtextFacet.Main[] + avatar?: BlobRef + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.graph.list#main' || + v.$type === 'app.bsky.graph.list') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.list#main', v) +} diff --git a/src/lexicon/types/app/bsky/graph/listitem.ts b/src/lexicon/types/app/bsky/graph/listitem.ts new file mode 100644 index 000000000..69eff329e --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/listitem.ts @@ -0,0 +1,27 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' + +export interface Record { + subject: string + list: string + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.graph.listitem#main' || + v.$type === 'app.bsky.graph.listitem') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.listitem#main', v) +} diff --git a/src/lexicon/types/app/bsky/graph/muteActorList.ts b/src/lexicon/types/app/bsky/graph/muteActorList.ts new file mode 100644 index 000000000..e099011b3 --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + list: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/src/lexicon/types/app/bsky/graph/unmuteActorList.ts new file mode 100644 index 000000000..e099011b3 --- /dev/null +++ b/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + list: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/com/atproto/admin/defs.ts b/src/lexicon/types/com/atproto/admin/defs.ts index f78afce41..38938d4b2 100644 --- a/src/lexicon/types/com/atproto/admin/defs.ts +++ b/src/lexicon/types/com/atproto/admin/defs.ts @@ -177,6 +177,7 @@ export interface RepoView { indexedAt: string moderation: Moderation invitedBy?: ComAtprotoServerDefs.InviteCode + invitesDisabled?: boolean [k: string]: unknown } @@ -202,6 +203,7 @@ export interface RepoViewDetail { labels?: ComAtprotoLabelDefs.Label[] invitedBy?: ComAtprotoServerDefs.InviteCode invites?: ComAtprotoServerDefs.InviteCode[] + invitesDisabled?: boolean [k: string]: unknown } diff --git a/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts new file mode 100644 index 000000000..fdba69e2e --- /dev/null +++ b/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + account: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts new file mode 100644 index 000000000..fdba69e2e --- /dev/null +++ b/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + account: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/com/atproto/repo/rebaseRepo.ts b/src/lexicon/types/com/atproto/repo/rebaseRepo.ts new file mode 100644 index 000000000..b3dfe322f --- /dev/null +++ b/src/lexicon/types/com/atproto/repo/rebaseRepo.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** The handle or DID of the repo. */ + repo: string + /** Compare and swap with the previous commit by cid. */ + swapCommit?: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string + error?: 'InvalidSwap' +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput From 2bff86cb4e402141855a8a073db1409cb49a0e32 Mon Sep 17 00:00:00 2001 From: dholms Date: Sun, 14 May 2023 20:13:56 -0500 Subject: [PATCH 20/38] add at:// prefix --- README.md | 2 +- src/feed-generation.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aff81a61e..b389e8694 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Once the custom algorithms feature launches, you'll be able to publish your feed ## Running the Server -Install dependencies with `yarn` and then run the server with `yarn start`. This will start the server on port 3000, or what's defined in `.env`. You can then watch the firehose output in the console and access the output of the default custom ALF feed at [http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=did:example:alice/app.bsky.feed.generator/whats-alf](http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=did:example:alice/app.bsky.feed.generator/whats-alf). +Install dependencies with `yarn` and then run the server with `yarn start`. This will start the server on port 3000, or what's defined in `.env`. You can then watch the firehose output in the console and access the output of the default custom ALF feed at [http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:example:alice/app.bsky.feed.generator/whats-alf](http://localhost:3000/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:example:alice/app.bsky.feed.generator/whats-alf). ## Some Details diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 5eab87b4b..63947173e 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -5,7 +5,9 @@ import { validateAuth } from './auth' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { - if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') { + if ( + params.feed !== 'at://did:example:alice/app.bsky.feed.generator/whats-alf' + ) { throw new InvalidRequestError( 'Unsupported algorithm', 'UnsupportedAlgorithm', From d93c993883bc7d3a97034afc7d3a1c2fa28042ea Mon Sep 17 00:00:00 2001 From: Simon Fondrie-Teitler Date: Mon, 15 May 2023 13:26:38 -0400 Subject: [PATCH 21/38] Use limit parameter in post query (#18) Fixes #14 --- src/feed-generation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 63947173e..f877f1aa7 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -28,6 +28,7 @@ export default function (server: Server, ctx: AppContext) { .selectAll() .orderBy('indexedAt', 'desc') .orderBy('cid', 'desc') + .limit(params.limit) if (params.cursor) { const [indexedAt, cid] = params.cursor.split('..') From 531aab43b155c7c64303ab5adec28b4881305b67 Mon Sep 17 00:00:00 2001 From: Cloudhunter Date: Mon, 15 May 2023 18:27:13 +0100 Subject: [PATCH 22/38] Fix feed generation cursor split (#16) --- src/feed-generation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feed-generation.ts b/src/feed-generation.ts index f877f1aa7..e5c057833 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -31,7 +31,7 @@ export default function (server: Server, ctx: AppContext) { .limit(params.limit) if (params.cursor) { - const [indexedAt, cid] = params.cursor.split('..') + const [indexedAt, cid] = params.cursor.split('::') if (!indexedAt || !cid) { throw new InvalidRequestError('malformed cursor') } From 285ef14a682736bdf44261a0ef2a8c4d3dcc5723 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 15 May 2023 22:06:25 -0500 Subject: [PATCH 23/38] add build script --- package.json | 3 ++- tsconfig.json | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tsconfig.json diff --git a/package.json b/package.json index f4e8b3fa9..c0581794b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "author": "dholms ", "license": "MIT", "scripts": { - "start": "ts-node src/index.ts" + "start": "ts-node src/index.ts", + "build": "tsc" }, "dependencies": { "@atproto/did-resolver": "^0.1.0", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..ab357f482 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ + +{ + "compilerOptions": { + "lib": [ + "ESNext", + ], + "outDir": "dist", + "module": "CommonJS", + "target": "ES6", + "esModuleInterop": true, + "moduleResolution": "node", + "alwaysStrict": true, + "allowUnreachableCode": false, + "strictNullChecks": true, + "skipLibCheck": true + }, + "include": ["./src/**/*.ts"], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file From 3606414b792ebac16e27bd92e049ce7aa8fbaa38 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Fri, 19 May 2023 10:31:28 -0500 Subject: [PATCH 24/38] Add describeFeedGenerator route + multiple feeds (#19) * describeFeedGenerator route + multiple feeds * tweak readme --- README.md | 8 +- src/algos/index.ts | 14 + src/algos/whats-alf.ts | 42 +++ src/feed-generation.ts | 64 ---- src/lexicon/index.ts | 65 +++- src/lexicon/lexicons.ts | 336 +++++++++++++++--- src/lexicon/types/app/bsky/actor/defs.ts | 41 +++ .../types/app/bsky/actor/getPreferences.ts | 40 +++ .../types/app/bsky/actor/putPreferences.ts | 36 ++ src/lexicon/types/app/bsky/embed/record.ts | 2 + src/lexicon/types/app/bsky/feed/defs.ts | 4 +- .../app/bsky/feed/describeFeedGenerator.ts | 76 ++++ src/lexicon/types/app/bsky/feed/getFeed.ts | 1 + .../types/app/bsky/feed/getFeedGenerator.ts | 44 +++ .../types/app/bsky/feed/getFeedSkeleton.ts | 1 + ...getBookmarkedFeeds.ts => getSavedFeeds.ts} | 0 .../feed/{bookmarkFeed.ts => saveFeed.ts} | 0 .../feed/{unbookmarkFeed.ts => unsaveFeed.ts} | 0 .../com/atproto/admin/getModerationReports.ts | 6 + .../types/com/atproto/server/createAccount.ts | 3 + src/methods/describe-generator.ts | 16 + src/methods/feed-generation.ts | 32 ++ src/server.ts | 4 +- 23 files changed, 692 insertions(+), 143 deletions(-) create mode 100644 src/algos/index.ts create mode 100644 src/algos/whats-alf.ts delete mode 100644 src/feed-generation.ts create mode 100644 src/lexicon/types/app/bsky/actor/getPreferences.ts create mode 100644 src/lexicon/types/app/bsky/actor/putPreferences.ts create mode 100644 src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts create mode 100644 src/lexicon/types/app/bsky/feed/getFeedGenerator.ts rename src/lexicon/types/app/bsky/feed/{getBookmarkedFeeds.ts => getSavedFeeds.ts} (100%) rename src/lexicon/types/app/bsky/feed/{bookmarkFeed.ts => saveFeed.ts} (100%) rename src/lexicon/types/app/bsky/feed/{unbookmarkFeed.ts => unsaveFeed.ts} (100%) create mode 100644 src/methods/describe-generator.ts create mode 100644 src/methods/feed-generation.ts diff --git a/README.md b/README.md index b389e8694..cc3156d85 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ Next you will need to do two things: This will subscribe to the repo subscription stream on startup, parse events & index them according to your provided logic. -2. Implement feed generation logic in `src/feed-generation.ts` - - The types are in place and you will just need to return something that satisfies the `SkeletonFeedPost[]` type. +2. Implement feed generation logic in `src/algos` + + For inspiration, we've provided a very simple feed algorithm (`whats-alf`) that returns all posts related to the titular character of the TV show ALF. -For inspiration, we've provided a very simple feed algorithm ("whats alf") that returns all posts related to the titular character of the TV show ALF. + You can either edit it or add another algorithm alongside it. The types are in place an dyou will just need to return something that satisfies the `SkeletonFeedPost[]` type. We've taken care of setting this server up with a did:web. However, you're free to switch this out for did:plc if you like - you may want to if you expect this Feed Generator to be long-standing and possibly migrating domains. diff --git a/src/algos/index.ts b/src/algos/index.ts new file mode 100644 index 000000000..910c0e9a4 --- /dev/null +++ b/src/algos/index.ts @@ -0,0 +1,14 @@ +import { AppContext } from '../config' +import { + QueryParams, + OutputSchema as AlgoOutput, +} from '../lexicon/types/app/bsky/feed/getFeedSkeleton' +import * as whatsAlf from './whats-alf' + +type AlgoHandler = (ctx: AppContext, params: QueryParams) => Promise + +const algos: Record = { + [whatsAlf.uri]: whatsAlf.handler, +} + +export default algos diff --git a/src/algos/whats-alf.ts b/src/algos/whats-alf.ts new file mode 100644 index 000000000..0afcadde8 --- /dev/null +++ b/src/algos/whats-alf.ts @@ -0,0 +1,42 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { QueryParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton' +import { AppContext } from '../config' + +export const uri = 'at://did:example:alice/app.bsky.feed.generator/whats-alf' + +export const handler = async (ctx: AppContext, params: QueryParams) => { + let builder = ctx.db + .selectFrom('post') + .selectAll() + .orderBy('indexedAt', 'desc') + .orderBy('cid', 'desc') + .limit(params.limit) + + if (params.cursor) { + const [indexedAt, cid] = params.cursor.split('::') + if (!indexedAt || !cid) { + throw new InvalidRequestError('malformed cursor') + } + const timeStr = new Date(parseInt(indexedAt, 10)).toISOString() + builder = builder + .where('post.indexedAt', '<', timeStr) + .orWhere((qb) => qb.where('post.indexedAt', '=', timeStr)) + .where('post.cid', '<', cid) + } + const res = await builder.execute() + + const feed = res.map((row) => ({ + post: row.uri, + })) + + let cursor: string | undefined + const last = res.at(-1) + if (last) { + cursor = `${new Date(last.indexedAt).getTime()}::${last.cid}` + } + + return { + cursor, + feed, + } +} diff --git a/src/feed-generation.ts b/src/feed-generation.ts deleted file mode 100644 index e5c057833..000000000 --- a/src/feed-generation.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { InvalidRequestError } from '@atproto/xrpc-server' -import { Server } from './lexicon' -import { AppContext } from './config' -import { validateAuth } from './auth' - -export default function (server: Server, ctx: AppContext) { - server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { - if ( - params.feed !== 'at://did:example:alice/app.bsky.feed.generator/whats-alf' - ) { - throw new InvalidRequestError( - 'Unsupported algorithm', - 'UnsupportedAlgorithm', - ) - } - /** - * Example of how to check auth if giving user-specific results: - * - * const requesterDid = await validateAuth( - * req, - * ctx.cfg.serviceDid, - * ctx.didResolver, - * ) - */ - - let builder = ctx.db - .selectFrom('post') - .selectAll() - .orderBy('indexedAt', 'desc') - .orderBy('cid', 'desc') - .limit(params.limit) - - if (params.cursor) { - const [indexedAt, cid] = params.cursor.split('::') - if (!indexedAt || !cid) { - throw new InvalidRequestError('malformed cursor') - } - const timeStr = new Date(parseInt(indexedAt, 10)).toISOString() - builder = builder - .where('post.indexedAt', '<', timeStr) - .orWhere((qb) => qb.where('post.indexedAt', '=', timeStr)) - .where('post.cid', '<', cid) - } - const res = await builder.execute() - - const feed = res.map((row) => ({ - post: row.uri, - })) - - let cursor: string | undefined - const last = res.at(-1) - if (last) { - cursor = `${new Date(last.indexedAt).getTime()}::${last.cid}` - } - - return { - encoding: 'application/json', - body: { - cursor, - feed, - }, - } - }) -} diff --git a/src/lexicon/index.ts b/src/lexicon/index.ts index 7759d837d..02e3ada18 100644 --- a/src/lexicon/index.ts +++ b/src/lexicon/index.ts @@ -67,23 +67,27 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' +import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles' import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestions' +import * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences' import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors' import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead' -import * as AppBskyFeedBookmarkFeed from './types/app/bsky/feed/bookmarkFeed' +import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator' import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' -import * as AppBskyFeedGetBookmarkedFeeds from './types/app/bsky/feed/getBookmarkedFeeds' import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' +import * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator' import * as AppBskyFeedGetFeedSkeleton from './types/app/bsky/feed/getFeedSkeleton' import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes' import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread' import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts' import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' +import * as AppBskyFeedGetSavedFeeds from './types/app/bsky/feed/getSavedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' -import * as AppBskyFeedUnbookmarkFeed from './types/app/bsky/feed/unbookmarkFeed' +import * as AppBskyFeedSaveFeed from './types/app/bsky/feed/saveFeed' +import * as AppBskyFeedUnsaveFeed from './types/app/bsky/feed/unsaveFeed' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' @@ -730,6 +734,13 @@ export class ActorNS { this._server = server } + getPreferences( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.actor.getPreferences' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getProfile( cfg: ConfigOf>>, ) { @@ -751,6 +762,13 @@ export class ActorNS { return this._server.xrpc.method(nsid, cfg) } + putPreferences( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.actor.putPreferences' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + searchActors( cfg: ConfigOf>>, ) { @@ -784,10 +802,13 @@ export class FeedNS { this._server = server } - bookmarkFeed( - cfg: ConfigOf>>, + describeFeedGenerator( + cfg: ConfigOf< + AV, + AppBskyFeedDescribeFeedGenerator.Handler> + >, ) { - const nsid = 'app.bsky.feed.bookmarkFeed' // @ts-ignore + const nsid = 'app.bsky.feed.describeFeedGenerator' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } @@ -805,17 +826,17 @@ export class FeedNS { return this._server.xrpc.method(nsid, cfg) } - getBookmarkedFeeds( - cfg: ConfigOf>>, + getFeed( + cfg: ConfigOf>>, ) { - const nsid = 'app.bsky.feed.getBookmarkedFeeds' // @ts-ignore + const nsid = 'app.bsky.feed.getFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - getFeed( - cfg: ConfigOf>>, + getFeedGenerator( + cfg: ConfigOf>>, ) { - const nsid = 'app.bsky.feed.getFeed' // @ts-ignore + const nsid = 'app.bsky.feed.getFeedGenerator' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } @@ -854,6 +875,13 @@ export class FeedNS { return this._server.xrpc.method(nsid, cfg) } + getSavedFeeds( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.feed.getSavedFeeds' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getTimeline( cfg: ConfigOf>>, ) { @@ -861,10 +889,17 @@ export class FeedNS { return this._server.xrpc.method(nsid, cfg) } - unbookmarkFeed( - cfg: ConfigOf>>, + saveFeed( + cfg: ConfigOf>>, + ) { + const nsid = 'app.bsky.feed.saveFeed' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + unsaveFeed( + cfg: ConfigOf>>, ) { - const nsid = 'app.bsky.feed.unbookmarkFeed' // @ts-ignore + const nsid = 'app.bsky.feed.unsaveFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } } diff --git a/src/lexicon/lexicons.ts b/src/lexicon/lexicons.ts index d242054aa..70be29cef 100644 --- a/src/lexicon/lexicons.ts +++ b/src/lexicon/lexicons.ts @@ -835,6 +835,15 @@ export const schemaDict = { resolved: { type: 'boolean', }, + actionType: { + type: 'string', + knownValues: [ + 'com.atproto.admin.defs#takedown', + 'com.atproto.admin.defs#flag', + 'com.atproto.admin.defs#acknowledge', + 'com.atproto.admin.defs#escalate', + ], + }, limit: { type: 'integer', minimum: 1, @@ -2114,6 +2123,10 @@ export const schemaDict = { type: 'string', format: 'handle', }, + did: { + type: 'string', + format: 'did', + }, inviteCode: { type: 'string', }, @@ -2165,6 +2178,12 @@ export const schemaDict = { { name: 'UnsupportedDomain', }, + { + name: 'UnresolvableDid', + }, + { + name: 'IncompatibleDidDoc', + }, ], }, }, @@ -3509,6 +3528,66 @@ export const schemaDict = { }, }, }, + preferences: { + type: 'array', + items: { + type: 'union', + refs: [ + 'lex:app.bsky.actor.defs#adultContentPref', + 'lex:app.bsky.actor.defs#contentLabelPref', + ], + }, + }, + adultContentPref: { + type: 'object', + required: ['enabled'], + properties: { + enabled: { + type: 'boolean', + default: false, + }, + }, + }, + contentLabelPref: { + type: 'object', + required: ['label', 'visibility'], + properties: { + label: { + type: 'string', + }, + visibility: { + type: 'string', + knownValues: ['show', 'warn', 'hide'], + }, + }, + }, + }, + }, + AppBskyActorGetPreferences: { + lexicon: 1, + id: 'app.bsky.actor.getPreferences', + defs: { + main: { + type: 'query', + description: 'Get private preferences attached to the account.', + parameters: { + type: 'params', + properties: {}, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['preferences'], + properties: { + preferences: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#preferences', + }, + }, + }, + }, + }, }, }, AppBskyActorGetProfile: { @@ -3655,6 +3734,29 @@ export const schemaDict = { }, }, }, + AppBskyActorPutPreferences: { + lexicon: 1, + id: 'app.bsky.actor.putPreferences', + defs: { + main: { + type: 'procedure', + description: 'Sets the private preferences attached to the account.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['preferences'], + properties: { + preferences: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#preferences', + }, + }, + }, + }, + }, + }, + }, AppBskyActorSearchActors: { lexicon: 1, id: 'app.bsky.actor.searchActors', @@ -3899,6 +4001,7 @@ export const schemaDict = { 'lex:app.bsky.embed.record#viewRecord', 'lex:app.bsky.embed.record#viewNotFound', 'lex:app.bsky.embed.record#viewBlocked', + 'lex:app.bsky.feed.defs#generatorView', ], }, }, @@ -4008,29 +4111,6 @@ export const schemaDict = { }, }, }, - AppBskyFeedBookmarkFeed: { - lexicon: 1, - id: 'app.bsky.feed.bookmarkFeed', - defs: { - main: { - type: 'procedure', - description: 'Bookmark a 3rd party feed for use across clients', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['feed'], - properties: { - feed: { - type: 'string', - format: 'at-uri', - }, - }, - }, - }, - }, - }, - }, AppBskyFeedDefs: { lexicon: 1, id: 'app.bsky.feed.defs', @@ -4215,12 +4295,16 @@ export const schemaDict = { }, generatorView: { type: 'object', - required: ['uri', 'creator', 'indexedAt'], + required: ['uri', 'cid', 'creator', 'indexedAt'], properties: { uri: { type: 'string', format: 'at-uri', }, + cid: { + type: 'string', + format: 'cid', + }, did: { type: 'string', format: 'did', @@ -4247,6 +4331,10 @@ export const schemaDict = { avatar: { type: 'string', }, + likeCount: { + type: 'integer', + minimum: 0, + }, viewer: { type: 'ref', ref: 'lex:app.bsky.feed.defs#generatorViewerState', @@ -4260,7 +4348,7 @@ export const schemaDict = { generatorViewerState: { type: 'object', properties: { - subscribed: { + saved: { type: 'boolean', }, like: { @@ -4295,6 +4383,62 @@ export const schemaDict = { }, }, }, + AppBskyFeedDescribeFeedGenerator: { + lexicon: 1, + id: 'app.bsky.feed.describeFeedGenerator', + defs: { + main: { + type: 'query', + description: + 'Returns information about a given feed generator including TOS & offered feed URIs', + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did', 'feeds'], + properties: { + did: { + type: 'string', + format: 'did', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.describeFeedGenerator#feed', + }, + }, + links: { + type: 'ref', + ref: 'lex:app.bsky.feed.describeFeedGenerator#links', + }, + }, + }, + }, + }, + feed: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + links: { + type: 'object', + properties: { + privacyPolicy: { + type: 'string', + }, + termsOfService: { + type: 'string', + }, + }, + }, + }, + }, AppBskyFeedGenerator: { lexicon: 1, id: 'app.bsky.feed.generator', @@ -4446,17 +4590,22 @@ export const schemaDict = { }, }, }, - AppBskyFeedGetBookmarkedFeeds: { + AppBskyFeedGetFeed: { lexicon: 1, - id: 'app.bsky.feed.getBookmarkedFeeds', + id: 'app.bsky.feed.getFeed', defs: { main: { type: 'query', description: - "Retrieve a list of the authenticated user's bookmarked feeds", + "Compose and hydrate a feed from a user's selected feed generator", parameters: { type: 'params', + required: ['feed'], properties: { + feed: { + type: 'string', + format: 'at-uri', + }, limit: { type: 'integer', minimum: 1, @@ -4472,32 +4621,37 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['feeds'], + required: ['feed'], properties: { cursor: { type: 'string', }, - feeds: { + feed: { type: 'array', items: { type: 'ref', - ref: 'lex:app.bsky.feed.defs#generatorView', + ref: 'lex:app.bsky.feed.defs#feedViewPost', }, }, }, }, }, + errors: [ + { + name: 'UnknownFeed', + }, + ], }, }, }, - AppBskyFeedGetFeed: { + AppBskyFeedGetFeedGenerator: { lexicon: 1, - id: 'app.bsky.feed.getFeed', + id: 'app.bsky.feed.getFeedGenerator', defs: { main: { type: 'query', description: - "Compose and hydrate a feed from a user's selected feed generator", + 'Get information about a specific feed offered by a feed generator, such as its online status', parameters: { type: 'params', required: ['feed'], @@ -4506,32 +4660,23 @@ export const schemaDict = { type: 'string', format: 'at-uri', }, - limit: { - type: 'integer', - minimum: 1, - maximum: 100, - default: 50, - }, - cursor: { - type: 'string', - }, }, }, output: { encoding: 'application/json', schema: { type: 'object', - required: ['feed'], + required: ['view', 'isOnline', 'isValid'], properties: { - cursor: { - type: 'string', + view: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', }, - feed: { - type: 'array', - items: { - type: 'ref', - ref: 'lex:app.bsky.feed.defs#feedViewPost', - }, + isOnline: { + type: 'boolean', + }, + isValid: { + type: 'boolean', }, }, }, @@ -4584,6 +4729,11 @@ export const schemaDict = { }, }, }, + errors: [ + { + name: 'UnknownFeed', + }, + ], }, }, }, @@ -4807,6 +4957,49 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetSavedFeeds: { + lexicon: 1, + id: 'app.bsky.feed.getSavedFeeds', + defs: { + main: { + type: 'query', + description: "Retrieve a list of the authenticated user's saved feeds", + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feeds'], + properties: { + cursor: { + type: 'string', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyFeedGetTimeline: { lexicon: 1, id: 'app.bsky.feed.getTimeline', @@ -5002,13 +5195,36 @@ export const schemaDict = { }, }, }, - AppBskyFeedUnbookmarkFeed: { + AppBskyFeedSaveFeed: { + lexicon: 1, + id: 'app.bsky.feed.saveFeed', + defs: { + main: { + type: 'procedure', + description: 'Save a 3rd party feed for use across clients', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + feed: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, + }, + }, + AppBskyFeedUnsaveFeed: { lexicon: 1, - id: 'app.bsky.feed.unbookmarkFeed', + id: 'app.bsky.feed.unsaveFeed', defs: { main: { type: 'procedure', - description: 'Remove a bookmark for a 3rd party feed', + description: 'Unsave a 3rd party feed', input: { encoding: 'application/json', schema: { @@ -6029,33 +6245,37 @@ export const ids = { ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', AppBskyActorDefs: 'app.bsky.actor.defs', + AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', AppBskyActorGetProfile: 'app.bsky.actor.getProfile', AppBskyActorGetProfiles: 'app.bsky.actor.getProfiles', AppBskyActorGetSuggestions: 'app.bsky.actor.getSuggestions', AppBskyActorProfile: 'app.bsky.actor.profile', + AppBskyActorPutPreferences: 'app.bsky.actor.putPreferences', AppBskyActorSearchActors: 'app.bsky.actor.searchActors', AppBskyActorSearchActorsTypeahead: 'app.bsky.actor.searchActorsTypeahead', AppBskyEmbedExternal: 'app.bsky.embed.external', AppBskyEmbedImages: 'app.bsky.embed.images', AppBskyEmbedRecord: 'app.bsky.embed.record', AppBskyEmbedRecordWithMedia: 'app.bsky.embed.recordWithMedia', - AppBskyFeedBookmarkFeed: 'app.bsky.feed.bookmarkFeed', AppBskyFeedDefs: 'app.bsky.feed.defs', + AppBskyFeedDescribeFeedGenerator: 'app.bsky.feed.describeFeedGenerator', AppBskyFeedGenerator: 'app.bsky.feed.generator', AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds', AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed', - AppBskyFeedGetBookmarkedFeeds: 'app.bsky.feed.getBookmarkedFeeds', AppBskyFeedGetFeed: 'app.bsky.feed.getFeed', + AppBskyFeedGetFeedGenerator: 'app.bsky.feed.getFeedGenerator', AppBskyFeedGetFeedSkeleton: 'app.bsky.feed.getFeedSkeleton', AppBskyFeedGetLikes: 'app.bsky.feed.getLikes', AppBskyFeedGetPostThread: 'app.bsky.feed.getPostThread', AppBskyFeedGetPosts: 'app.bsky.feed.getPosts', AppBskyFeedGetRepostedBy: 'app.bsky.feed.getRepostedBy', + AppBskyFeedGetSavedFeeds: 'app.bsky.feed.getSavedFeeds', AppBskyFeedGetTimeline: 'app.bsky.feed.getTimeline', AppBskyFeedLike: 'app.bsky.feed.like', AppBskyFeedPost: 'app.bsky.feed.post', AppBskyFeedRepost: 'app.bsky.feed.repost', - AppBskyFeedUnbookmarkFeed: 'app.bsky.feed.unbookmarkFeed', + AppBskyFeedSaveFeed: 'app.bsky.feed.saveFeed', + AppBskyFeedUnsaveFeed: 'app.bsky.feed.unsaveFeed', AppBskyGraphBlock: 'app.bsky.graph.block', AppBskyGraphDefs: 'app.bsky.graph.defs', AppBskyGraphFollow: 'app.bsky.graph.follow', diff --git a/src/lexicon/types/app/bsky/actor/defs.ts b/src/lexicon/types/app/bsky/actor/defs.ts index 162e45ada..deb26d731 100644 --- a/src/lexicon/types/app/bsky/actor/defs.ts +++ b/src/lexicon/types/app/bsky/actor/defs.ts @@ -103,3 +103,44 @@ export function isViewerState(v: unknown): v is ViewerState { export function validateViewerState(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#viewerState', v) } + +export type Preferences = ( + | AdultContentPref + | ContentLabelPref + | { $type: string; [k: string]: unknown } +)[] + +export interface AdultContentPref { + enabled: boolean + [k: string]: unknown +} + +export function isAdultContentPref(v: unknown): v is AdultContentPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#adultContentPref' + ) +} + +export function validateAdultContentPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#adultContentPref', v) +} + +export interface ContentLabelPref { + label: string + visibility: 'show' | 'warn' | 'hide' | (string & {}) + [k: string]: unknown +} + +export function isContentLabelPref(v: unknown): v is ContentLabelPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#contentLabelPref' + ) +} + +export function validateContentLabelPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v) +} diff --git a/src/lexicon/types/app/bsky/actor/getPreferences.ts b/src/lexicon/types/app/bsky/actor/getPreferences.ts new file mode 100644 index 000000000..018905c86 --- /dev/null +++ b/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -0,0 +1,40 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyActorDefs from './defs' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + preferences: AppBskyActorDefs.Preferences + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/actor/putPreferences.ts b/src/lexicon/types/app/bsky/actor/putPreferences.ts new file mode 100644 index 000000000..ba0531cc3 --- /dev/null +++ b/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyActorDefs from './defs' + +export interface QueryParams {} + +export interface InputSchema { + preferences: AppBskyActorDefs.Preferences + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/embed/record.ts b/src/lexicon/types/app/bsky/embed/record.ts index 3b13e3d57..c45049b7d 100644 --- a/src/lexicon/types/app/bsky/embed/record.ts +++ b/src/lexicon/types/app/bsky/embed/record.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import * as AppBskyFeedDefs from '../feed/defs' import * as AppBskyActorDefs from '../actor/defs' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as AppBskyEmbedImages from './images' @@ -35,6 +36,7 @@ export interface View { | ViewRecord | ViewNotFound | ViewBlocked + | AppBskyFeedDefs.GeneratorView | { $type: string; [k: string]: unknown } [k: string]: unknown } diff --git a/src/lexicon/types/app/bsky/feed/defs.ts b/src/lexicon/types/app/bsky/feed/defs.ts index 45a152038..1272c2256 100644 --- a/src/lexicon/types/app/bsky/feed/defs.ts +++ b/src/lexicon/types/app/bsky/feed/defs.ts @@ -188,12 +188,14 @@ export function validateBlockedPost(v: unknown): ValidationResult { export interface GeneratorView { uri: string + cid: string did?: string creator: AppBskyActorDefs.ProfileView displayName?: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: string + likeCount?: number viewer?: GeneratorViewerState indexedAt: string [k: string]: unknown @@ -212,7 +214,7 @@ export function validateGeneratorView(v: unknown): ValidationResult { } export interface GeneratorViewerState { - subscribed?: boolean + saved?: boolean like?: string [k: string]: unknown } diff --git a/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts new file mode 100644 index 000000000..82f1d152f --- /dev/null +++ b/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -0,0 +1,76 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + did: string + feeds: Feed[] + links?: Links + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput + +export interface Feed { + uri: string + [k: string]: unknown +} + +export function isFeed(v: unknown): v is Feed { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.describeFeedGenerator#feed' + ) +} + +export function validateFeed(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.describeFeedGenerator#feed', v) +} + +export interface Links { + privacyPolicy?: string + termsOfService?: string + [k: string]: unknown +} + +export function isLinks(v: unknown): v is Links { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.describeFeedGenerator#links' + ) +} + +export function validateLinks(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.describeFeedGenerator#links', v) +} diff --git a/src/lexicon/types/app/bsky/feed/getFeed.ts b/src/lexicon/types/app/bsky/feed/getFeed.ts index 59114f984..850a44a84 100644 --- a/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -33,6 +33,7 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'UnknownFeed' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts new file mode 100644 index 000000000..0a2005db9 --- /dev/null +++ b/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -0,0 +1,44 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + feed: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + view: AppBskyFeedDefs.GeneratorView + isOnline: boolean + isValid: boolean + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type Handler = (ctx: { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +}) => Promise | HandlerOutput diff --git a/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index efbb2ea31..d19a27530 100644 --- a/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -33,6 +33,7 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'UnknownFeed' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts b/src/lexicon/types/app/bsky/feed/getSavedFeeds.ts similarity index 100% rename from src/lexicon/types/app/bsky/feed/getBookmarkedFeeds.ts rename to src/lexicon/types/app/bsky/feed/getSavedFeeds.ts diff --git a/src/lexicon/types/app/bsky/feed/bookmarkFeed.ts b/src/lexicon/types/app/bsky/feed/saveFeed.ts similarity index 100% rename from src/lexicon/types/app/bsky/feed/bookmarkFeed.ts rename to src/lexicon/types/app/bsky/feed/saveFeed.ts diff --git a/src/lexicon/types/app/bsky/feed/unbookmarkFeed.ts b/src/lexicon/types/app/bsky/feed/unsaveFeed.ts similarity index 100% rename from src/lexicon/types/app/bsky/feed/unbookmarkFeed.ts rename to src/lexicon/types/app/bsky/feed/unsaveFeed.ts diff --git a/src/lexicon/types/com/atproto/admin/getModerationReports.ts b/src/lexicon/types/com/atproto/admin/getModerationReports.ts index f92f20760..8fc5ac887 100644 --- a/src/lexicon/types/com/atproto/admin/getModerationReports.ts +++ b/src/lexicon/types/com/atproto/admin/getModerationReports.ts @@ -12,6 +12,12 @@ import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { subject?: string resolved?: boolean + actionType?: + | 'com.atproto.admin.defs#takedown' + | 'com.atproto.admin.defs#flag' + | 'com.atproto.admin.defs#acknowledge' + | 'com.atproto.admin.defs#escalate' + | (string & {}) limit: number cursor?: string } diff --git a/src/lexicon/types/com/atproto/server/createAccount.ts b/src/lexicon/types/com/atproto/server/createAccount.ts index 7e5ff93ac..4e212bfa0 100644 --- a/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/src/lexicon/types/com/atproto/server/createAccount.ts @@ -13,6 +13,7 @@ export interface QueryParams {} export interface InputSchema { email: string handle: string + did?: string inviteCode?: string password: string recoveryKey?: string @@ -46,6 +47,8 @@ export interface HandlerError { | 'InvalidInviteCode' | 'HandleNotAvailable' | 'UnsupportedDomain' + | 'UnresolvableDid' + | 'IncompatibleDidDoc' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/src/methods/describe-generator.ts b/src/methods/describe-generator.ts new file mode 100644 index 000000000..3155e4fc2 --- /dev/null +++ b/src/methods/describe-generator.ts @@ -0,0 +1,16 @@ +import { Server } from '../lexicon' +import { AppContext } from '../config' +import algos from '../algos' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.describeFeedGenerator(async () => { + const feeds = Object.keys(algos).map((uri) => ({ uri })) + return { + encoding: 'application/json', + body: { + did: ctx.cfg.serviceDid, + feeds, + }, + } + }) +} diff --git a/src/methods/feed-generation.ts b/src/methods/feed-generation.ts new file mode 100644 index 000000000..0096a97e2 --- /dev/null +++ b/src/methods/feed-generation.ts @@ -0,0 +1,32 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { Server } from '../lexicon' +import { AppContext } from '../config' +import algos from '../algos' +import { validateAuth } from '../auth' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { + const algo = algos[params.feed] + if (!algo) { + throw new InvalidRequestError( + 'Unsupported algorithm', + 'UnsupportedAlgorithm', + ) + } + /** + * Example of how to check auth if giving user-specific results: + * + * const requesterDid = await validateAuth( + * req, + * ctx.cfg.serviceDid, + * ctx.didResolver, + * ) + */ + + const body = await algo(ctx, params) + return { + encoding: 'application/json', + body: body, + } + }) +} diff --git a/src/server.ts b/src/server.ts index 07d23b3b1..b57dffe0e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,7 +3,8 @@ import events from 'events' import express from 'express' import { DidResolver, MemoryCache } from '@atproto/did-resolver' import { createServer } from './lexicon' -import feedGeneration from './feed-generation' +import feedGeneration from './methods/feed-generation' +import describeGenerator from './methods/describe-generator' import { createDb, Database, migrateToLatest } from './db' import { FirehoseSubscription } from './subscription' import { AppContext, Config } from './config' @@ -60,6 +61,7 @@ export class FeedGenerator { cfg, } feedGeneration(server, ctx) + describeGenerator(server, ctx) app.use(server.xrpc.router) app.use(wellKnown(cfg.hostname)) From 745023cfc2f1008051a08e19ce84469966b82aa4 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Fri, 19 May 2023 10:31:39 -0500 Subject: [PATCH 25/38] Improve .env (#20) * describeFeedGenerator route + multiple feeds * tweak readme * improve env --- .env.example | 12 +++++++++++- src/index.ts | 14 ++++++++++---- src/server.ts | 11 ++--------- src/well-known.ts | 10 +++++++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index ebd0e2b1a..4cfdab337 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,14 @@ +# Whichever port you want to run this on FEEDGEN_PORT=3000 + +# Set to something like db.sqlite to store persistently FEEDGEN_SQLITE_LOCATION=":memory:" + +# Don't change unless you're working in a different environment than the primary Bluesky network FEEDGEN_SUBSCRIPTION_ENDPOINT="wss://bsky.social" -FEEDGEN_SERVICE_DID="did:example:test" + +# Set this to the hostname that you intend to run the service at +FEEDGEN_HOSTNAME="example.com" + +# Only use this if you want a service did different from did:web +# FEEDGEN_SERVICE_DID="did:plc:abcde..." \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a8486d79d..496004457 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,17 @@ import FeedGenerator from './server' const run = async () => { dotenv.config() + const hostname = maybeStr(process.env.FEEDGEN_HOSTNAME) ?? 'example.com' + const serviceDid = + maybeStr(process.env.FEEDGEN_SERVICE_DID) ?? `did:web:${hostname}` const server = FeedGenerator.create({ - port: maybeInt(process.env.FEEDGEN_PORT), - sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION), - subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT), - serviceDid: maybeStr(process.env.FEEDGEN_SERVICE_DID), + port: maybeInt(process.env.FEEDGEN_PORT) ?? 3000, + sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION) ?? ':memory:', + subscriptionEndpoint: + maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT) ?? + 'wss://bsky.social', + hostname, + serviceDid, }) await server.start() console.log( diff --git a/src/server.ts b/src/server.ts index b57dffe0e..4e1449c93 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,14 +29,7 @@ export class FeedGenerator { this.cfg = cfg } - static create(config?: Partial) { - const cfg: Config = { - port: config?.port ?? 3000, - hostname: config?.hostname ?? 'feed-generator.test', - sqliteLocation: config?.sqliteLocation ?? ':memory:', - subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', - serviceDid: config?.serviceDid ?? 'did:example:test', - } + static create(cfg: Config) { const app = express() const db = createDb(cfg.sqliteLocation) const firehose = new FirehoseSubscription(db, cfg.subscriptionEndpoint) @@ -63,7 +56,7 @@ export class FeedGenerator { feedGeneration(server, ctx) describeGenerator(server, ctx) app.use(server.xrpc.router) - app.use(wellKnown(cfg.hostname)) + app.use(wellKnown(ctx)) return new FeedGenerator(app, db, firehose, cfg) } diff --git a/src/well-known.ts b/src/well-known.ts index b869b6ca9..11c230464 100644 --- a/src/well-known.ts +++ b/src/well-known.ts @@ -1,17 +1,21 @@ import express from 'express' +import { AppContext } from './config' -const makeRouter = (serverHostname: string) => { +const makeRouter = (ctx: AppContext) => { const router = express.Router() router.get('/.well-known/did.json', (_req, res) => { + if (!ctx.cfg.serviceDid.endsWith(ctx.cfg.hostname)) { + return res.sendStatus(404) + } res.json({ '@context': ['https://www.w3.org/ns/did/v1'], - id: `did:web:${serverHostname}`, + id: ctx.cfg.serviceDid, service: [ { id: '#bsky_fg', type: 'BskyFeedGenerator', - serviceEndpoint: `https://${serverHostname}`, + serviceEndpoint: `https://${ctx.cfg.hostname}`, }, ], }) From 2f620bd46d7f85e4c5b8e8580b84e9647f2e55a9 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Fri, 19 May 2023 10:33:12 -0500 Subject: [PATCH 26/38] Publish script (#21) * describeFeedGenerator route + multiple feeds * tweak readme * improve env * publish script * create -> put * readme * handle blob encoding * add check that feeds are available --- README.md | 8 +++- package.json | 2 + scripts/publishFeedGen.ts | 89 +++++++++++++++++++++++++++++++++++++++ scripts/publishMany.ts | 81 +++++++++++++++++++++++++++++++++++ yarn.lock | 43 +++++++++++++++++++ 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 scripts/publishFeedGen.ts create mode 100644 scripts/publishMany.ts diff --git a/README.md b/README.md index cc3156d85..bebc99b23 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,13 @@ Next you will need to do two things: We've taken care of setting this server up with a did:web. However, you're free to switch this out for did:plc if you like - you may want to if you expect this Feed Generator to be long-standing and possibly migrating domains. -Once the custom algorithms feature launches, you'll be able to publish your feed in-app by providing the DID of your service. +### Publishing your feed + +To publish your feed, go to the script at `scripts/publishFeedGen.ts` & fill in the variables at the top. Examples are included and some are optional. To publish your feed generator, simply run `yarn publishFeed`. + +To update your feed's display data (name, avatar, description, etc), just update the relevant variables & re-run the script. + +After successfully running the script, you should be able to see your feed from within the app, as well as share it by embedding a link in a post (similar to a quote post). ## Running the Server diff --git a/package.json b/package.json index c0581794b..c234f4c5e 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "author": "dholms ", "license": "MIT", "scripts": { + "publishFeed": "ts-node scripts/publishFeedGen.ts", "start": "ts-node src/index.ts", "build": "tsc" }, "dependencies": { + "@atproto/api": "^0.3.7", "@atproto/did-resolver": "^0.1.0", "@atproto/lexicon": "^0.1.0", "@atproto/repo": "^0.1.0", diff --git a/scripts/publishFeedGen.ts b/scripts/publishFeedGen.ts new file mode 100644 index 000000000..9f226ba02 --- /dev/null +++ b/scripts/publishFeedGen.ts @@ -0,0 +1,89 @@ +import dotenv from 'dotenv' +import { AtpAgent, BlobRef } from '@atproto/api' +import fs from 'fs/promises' +import { ids } from '../src/lexicon/lexicons' + +const run = async () => { + dotenv.config() + + // YOUR bluesky handle + // Ex: user.bsky.social + const handle = '' + + // YOUR bluesky password, or preferably an App Password (found in your client settings) + // Ex: abcd-1234-efgh-5678 + const password = '' + + // A short name for the record that will show in urls + // Lowercase with no spaces. + // Ex: whats-hot + const recordName = '' + + // A display name for your feed + // Ex: What's Hot + const displayName = '' + + // (Optional) A description of your feed + // Ex: Top trending content from the whole network + const description = '' + + // (Optional) The path to an image to be used as your feed's avatar + // Ex: ~/path/to/avatar.jpeg + const avatar: string = '' + + // ------------------------------------- + // NO NEED TO TOUCH ANYTHING BELOW HERE + // ------------------------------------- + + if (!process.env.FEEDGEN_SERVICE_DID && !process.env.FEEDGEN_HOSTNAME) { + throw new Error('Please provide a hostname in the .env file') + } + const feedGenDid = + process.env.FEEDGEN_SERVICE_DID ?? `did:web:${process.env.FEEDGEN_HOSTNAME}` + + // only update this if in a test environment + const agent = new AtpAgent({ service: 'https://bsky.social' }) + await agent.login({ identifier: handle, password }) + + try { + await agent.api.app.bsky.feed.describeFeedGenerator() + } catch (err) { + throw new Error( + 'The bluesky server is not ready to accept published custom feeds yet', + ) + } + + let avatarRef: BlobRef | undefined + if (avatar) { + let encoding: string + if (avatar.endsWith('png')) { + encoding = 'image/png' + } else if (avatar.endsWith('jpg') || avatar.endsWith('jpeg')) { + encoding = 'image/jpeg' + } else { + throw new Error('expected png or jpeg') + } + const img = await fs.readFile(avatar) + const blobRes = await agent.api.com.atproto.repo.uploadBlob(img, { + encoding, + }) + avatarRef = blobRes.data.blob + } + + await agent.api.com.atproto.repo.putRecord({ + repo: agent.session?.did ?? '', + collection: ids.AppBskyFeedGenerator, + rkey: recordName, + record: { + did: feedGenDid, + displayName: displayName, + description: description, + avatar: avatarRef, + createdAt: new Date().toISOString(), + }, + }) + + console.log('All done 🎉') +} + +run() diff --git a/scripts/publishMany.ts b/scripts/publishMany.ts new file mode 100644 index 000000000..ff966854e --- /dev/null +++ b/scripts/publishMany.ts @@ -0,0 +1,81 @@ +import { AtpAgent, BlobRef } from '@atproto/api' +import fs from 'fs/promises' +import { ids } from '../src/lexicon/lexicons' + +const run = async () => { + const handle = 'bsky.app' + const password = 'abcd-1234-4321-dcba' // ask emily for app password + const feedGenDid = '' + + const agent = new AtpAgent({ service: 'https://bsky.social' }) + await agent.login({ identifier: handle, password }) + + await publishGen( + agent, + feedGenDid, + 'whats-hot', + `What's Hot`, + 'Top trending content from the whole network', + './whats-hot.jpg', + ) + + await publishGen( + agent, + feedGenDid, + 'hot-classic', + `What's Hot Classic`, + `The original What's Hot experience`, + './hot-classic.jpg', + ) + + await publishGen( + agent, + feedGenDid, + 'bsky-team', + `Bluesky Team`, + 'Posts from members of the Bluesky Team', + './bsky-team.jpg', + ) + + await publishGen( + agent, + feedGenDid, + 'with-friends', + `Popular With Friends`, + 'A mix of popular content from accounts you follow and content that your follows like.', + './with-friends.jpg', + ) + + console.log('All done 🎉') +} + +const publishGen = async ( + agent: AtpAgent, + feedGenDid: string, + recordName: string, + displayName: string, + description: string, + avatar: string, +) => { + let avatarRef: BlobRef | undefined + if (avatar) { + const img = await fs.readFile(avatar) + const blobRes = await agent.api.com.atproto.repo.uploadBlob(img) + avatarRef = blobRes.data.blob + } + + await agent.api.com.atproto.repo.putRecord({ + repo: agent.session?.did ?? '', + collection: ids.AppBskyFeedGenerator, + rkey: recordName, + record: { + did: feedGenDid, + displayName: displayName, + description: description, + avatar: avatarRef, + createdAt: new Date().toISOString(), + }, + }) +} + +run() diff --git a/yarn.lock b/yarn.lock index ef4f11f02..32c0bde72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,17 @@ # yarn lockfile v1 +"@atproto/api@^0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.7.tgz#5cc4b0ccc5c6690eb0e5a3ae138a84ce20697e2f" + integrity sha512-JHN3rHNGro4AaJWU64hsmpTUzd2+FbfMBiDkqyBmoKtj972ueBJeH8tz6WdnPcsIRfCj1kRthKFj2yJwgt6aSQ== + dependencies: + "@atproto/common-web" "*" + "@atproto/uri" "*" + "@atproto/xrpc" "*" + tlds "^1.234.0" + typed-emitter "^2.1.0" + "@atproto/common-web@*": version "0.1.0" resolved "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.1.0.tgz" @@ -108,6 +119,14 @@ ws "^8.12.0" zod "^3.14.2" +"@atproto/xrpc@*": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.1.0.tgz#798569095538ac060475ae51f1b4c071ff8776d6" + integrity sha512-LhBeZkQwPezjEtricGYnG62udFglOqlnmMSS0KyWgEAPi4KMp4H2F4jNoXcf5NPtZ9S4N4hJaErHX4PJYv2lfA== + dependencies: + "@atproto/lexicon" "*" + zod "^3.14.2" + "@cbor-extract/cbor-extract-darwin-arm64@2.1.1": version "2.1.1" resolved "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz" @@ -1011,6 +1030,13 @@ real-require@^0.2.0: resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== +rxjs@^7.5.2: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -1147,6 +1173,11 @@ thread-stream@^2.0.0: dependencies: real-require "^0.2.0" +tlds@^1.234.0: + version "1.238.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.238.0.tgz#ffe7c19c8940c35b497cda187a6927f9450325a4" + integrity sha512-lFPF9pZFhLrPodaJ0wt9QIN0l8jOxqmUezGZnm7BfkDSVd9q667oVIJukLVzhF+4oW7uDlrLlfJrL5yu9RWwew== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" @@ -1171,6 +1202,11 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@^2.1.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338" + integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" @@ -1186,6 +1222,13 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" + integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== + optionalDependencies: + rxjs "^7.5.2" + typescript@^5.0.4: version "5.0.4" resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz" From 8d5c1b118265a644fa1ba1ccffb9e9cf248f2e09 Mon Sep 17 00:00:00 2001 From: dholms Date: Fri, 19 May 2023 10:40:51 -0500 Subject: [PATCH 27/38] remove publishMany script --- scripts/publishMany.ts | 81 ------------------------------------------ 1 file changed, 81 deletions(-) delete mode 100644 scripts/publishMany.ts diff --git a/scripts/publishMany.ts b/scripts/publishMany.ts deleted file mode 100644 index ff966854e..000000000 --- a/scripts/publishMany.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { AtpAgent, BlobRef } from '@atproto/api' -import fs from 'fs/promises' -import { ids } from '../src/lexicon/lexicons' - -const run = async () => { - const handle = 'bsky.app' - const password = 'abcd-1234-4321-dcba' // ask emily for app password - const feedGenDid = '' - - const agent = new AtpAgent({ service: 'https://bsky.social' }) - await agent.login({ identifier: handle, password }) - - await publishGen( - agent, - feedGenDid, - 'whats-hot', - `What's Hot`, - 'Top trending content from the whole network', - './whats-hot.jpg', - ) - - await publishGen( - agent, - feedGenDid, - 'hot-classic', - `What's Hot Classic`, - `The original What's Hot experience`, - './hot-classic.jpg', - ) - - await publishGen( - agent, - feedGenDid, - 'bsky-team', - `Bluesky Team`, - 'Posts from members of the Bluesky Team', - './bsky-team.jpg', - ) - - await publishGen( - agent, - feedGenDid, - 'with-friends', - `Popular With Friends`, - 'A mix of popular content from accounts you follow and content that your follows like.', - './with-friends.jpg', - ) - - console.log('All done 🎉') -} - -const publishGen = async ( - agent: AtpAgent, - feedGenDid: string, - recordName: string, - displayName: string, - description: string, - avatar: string, -) => { - let avatarRef: BlobRef | undefined - if (avatar) { - const img = await fs.readFile(avatar) - const blobRes = await agent.api.com.atproto.repo.uploadBlob(img) - avatarRef = blobRes.data.blob - } - - await agent.api.com.atproto.repo.putRecord({ - repo: agent.session?.did ?? '', - collection: ids.AppBskyFeedGenerator, - rkey: recordName, - record: { - did: feedGenDid, - displayName: displayName, - description: description, - avatar: avatarRef, - createdAt: new Date().toISOString(), - }, - }) -} - -run() From 9395b18214e3283f8b562fe49f026decff12e308 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Fri, 19 May 2023 12:00:39 -0400 Subject: [PATCH 28/38] Add temp fix for blobref validation issue (#23) --- src/util/subscription.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/util/subscription.ts b/src/util/subscription.ts index e3c0111d4..4061f3f3a 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -1,5 +1,6 @@ import { Subscription } from '@atproto/xrpc-server' import { cborToLexRecord, readCar } from '@atproto/repo' +import { BlobRef } from '@atproto/lexicon' import { ids, lexicons } from '../lexicon/lexicons' import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' import { Record as RepostRecord } from '../lexicon/types/app/bsky/feed/repost' @@ -156,9 +157,29 @@ export const isFollow = (obj: unknown): obj is FollowRecord => { const isType = (obj: unknown, nsid: string) => { try { - lexicons.assertValidRecord(nsid, obj) + lexicons.assertValidRecord(nsid, fixBlobRefs(obj)) return true } catch (err) { return false } } + +// @TODO right now record validation fails on BlobRefs +// simply because multiple packages have their own copy +// of the BlobRef class, causing instanceof checks to fail. +// This is a temporary solution. +const fixBlobRefs = (obj: unknown): unknown => { + if (Array.isArray(obj)) { + return obj.map(fixBlobRefs) + } + if (obj && typeof obj === 'object') { + if (obj.constructor.name === 'BlobRef') { + const blob = obj as BlobRef + return new BlobRef(blob.ref, blob.mimeType, blob.size, blob.original) + } + return Object.entries(obj).reduce((acc, [key, val]) => { + return Object.assign(acc, { [key]: fixBlobRefs(val) }) + }, {} as Record) + } + return obj +} From 1b7d6e3e6dc6492e8b051dcd74bfa96fd1e5f751 Mon Sep 17 00:00:00 2001 From: Joe Sondow <614132+joesondow@users.noreply.github.com> Date: Wed, 24 May 2023 15:50:51 -0700 Subject: [PATCH 29/38] Say what PDS stands for (#29) So readers won't have to google Bluesky PDS to try to figure out what it means --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bebc99b23..32b4cc373 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 🚧 Work in Progress 🚧 -We are actively developing Feed Generator integration into the Bluesky PDS. Though we are reasonably confident about the general shape and interfaces laid out here, these interfaces and implementation details _are_ subject to change. +We are actively developing Feed Generator integration into the Bluesky Personal Data Server (PDS). Though we are reasonably confident about the general shape and interfaces laid out here, these interfaces and implementation details _are_ subject to change. In the meantime, we've put together this starter kit for devs. It doesn't do everything, but it should be enough to get you familiar with the system & started building! From 51ca4d06590d949c544e948a27fac5680d28aa62 Mon Sep 17 00:00:00 2001 From: Markus Amalthea Magnuson Date: Thu, 25 May 2023 00:51:50 +0200 Subject: [PATCH 30/38] Fix some typos and clean up README. (#25) --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 32b4cc373..732c63e91 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ We are actively developing Feed Generator integration into the Bluesky Personal Data Server (PDS). Though we are reasonably confident about the general shape and interfaces laid out here, these interfaces and implementation details _are_ subject to change. -In the meantime, we've put together this starter kit for devs. It doesn't do everything, but it should be enough to get you familiar with the system & started building! +In the meantime, we've put together this starter kit for devs. It doesn't do everything, but it should be enough to get you familiar with the system and started building! ## Overview @@ -20,35 +20,35 @@ The general flow of providing a custom algorithm to a user is as follows: - The PDS sends a `getFeedSkeleton` request to the service endpoint declared in the Feed Generator's DID doc - This request is authenticated by a JWT signed by the user's repo signing key - The Feed Generator returns a skeleton of the feed to the user's PDS -- The PDS hydrates the feed (user info, post contents, aggregates, etc) - - In the future, the PDS will hydrate the feed with the help of an App View, but for now the PDS handles hydration itself +- The PDS hydrates the feed (user info, post contents, aggregates, etc.) + - In the future, the PDS will hydrate the feed with the help of an App View, but for now, the PDS handles hydration itself - The PDS returns the hydrated feed to the user For users, this should feel like visiting a page in the app. Once they subscribe to a custom algorithm, it will appear in their home interface as one of their available feeds. ## Getting Started -We've set up this simple server with SQLite to store & query data. Feel free to switch this out for whichever database you prefer. +We've set up this simple server with SQLite to store and query data. Feel free to switch this out for whichever database you prefer. -Next you will need to do two things: +Next, you will need to do two things: 1. Implement indexing logic in `src/subscription.ts`. - This will subscribe to the repo subscription stream on startup, parse events & index them according to your provided logic. + This will subscribe to the repo subscription stream on startup, parse events and index them according to your provided logic. 2. Implement feed generation logic in `src/algos` For inspiration, we've provided a very simple feed algorithm (`whats-alf`) that returns all posts related to the titular character of the TV show ALF. - You can either edit it or add another algorithm alongside it. The types are in place an dyou will just need to return something that satisfies the `SkeletonFeedPost[]` type. + You can either edit it or add another algorithm alongside it. The types are in place, and you will just need to return something that satisfies the `SkeletonFeedPost[]` type. We've taken care of setting this server up with a did:web. However, you're free to switch this out for did:plc if you like - you may want to if you expect this Feed Generator to be long-standing and possibly migrating domains. ### Publishing your feed -To publish your feed, go to the script at `scripts/publishFeedGen.ts` & fill in the variables at the top. Examples are included and some are optional. To publish your feed generator, simply run `yarn publishFeed`. +To publish your feed, go to the script at `scripts/publishFeedGen.ts` and fill in the variables at the top. Examples are included, and some are optional. To publish your feed generator, simply run `yarn publishFeed`. -To update your feed's display data (name, avatar, description, etc), just update the relevant variables & re-run the script. +To update your feed's display data (name, avatar, description, etc.), just update the relevant variables and re-run the script. After successfully running the script, you should be able to see your feed from within the app, as well as share it by embedding a link in a post (similar to a quote post). @@ -70,7 +70,7 @@ The skeleton that a Feed Generator puts together is, in its simplest form, a lis ] ``` -However, we include an additionl location to attach some context. Here is the full schema: +However, we include an additional location to attach some context. Here is the full schema: ```ts type SkeletonItem = { @@ -117,9 +117,9 @@ const payload = { We provide utilities for verifying user JWTs in the `@atproto/xrpc-server` package, and you can see them in action in `src/auth.ts`. ### Pagination -You'll notice that the `getFeedSkeleton` method returns a `cursor` in its response & takes a `cursor` param as input. +You'll notice that the `getFeedSkeleton` method returns a `cursor` in its response and takes a `cursor` param as input. -This cursor is treated as an opaque value & fully at the Feed Generator's discretion. It is simply pased through the PDS directly to & from the client. +This cursor is treated as an opaque value and fully at the Feed Generator's discretion. It is simply passed through the PDS directly to and from the client. We strongly encourage that the cursor be _unique per feed item_ to prevent unexpected behavior in pagination. @@ -137,10 +137,10 @@ Depending on your algorithm, you likely do not need to keep posts around for lon Some examples: ### Reimplementing What's Hot -To reimplement "What's Hot", you may subscribe to the firehose & filter for all posts & likes (ignoring profiles/reposts/follows/etc). You would keep a running tally of likes per post & when a PDS requests a feed, you would send the most recent posts that pass some threshold of likes. +To reimplement "What's Hot", you may subscribe to the firehose and filter for all posts and likes (ignoring profiles/reposts/follows/etc.). You would keep a running tally of likes per post and when a PDS requests a feed, you would send the most recent posts that pass some threshold of likes. ### A Community Feed -You might create a feed for a given community by compiling a list of DIDs within that community & filtering the firehose for all posts from users within that list. +You might create a feed for a given community by compiling a list of DIDs within that community and filtering the firehose for all posts from users within that list. ### A Topical Feed To implement a topical feed, you might filter the algorithm for posts and pass the post text through some filtering mechanism (an LLM, a keyword matcher, etc.) that filters for the topic of your choice. From e849ac7f666d4e4e3099f0adac13e441ae3e94be Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Wed, 24 May 2023 18:53:37 -0400 Subject: [PATCH 31/38] add listenhost option (#28) (cherry picked from commit 1b2f04af6af57b36e800903b943aa4e01023b341) --- .env.example | 3 +++ src/config.ts | 1 + src/index.ts | 3 ++- src/server.ts | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 4cfdab337..49f71dcc5 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ # Whichever port you want to run this on FEEDGEN_PORT=3000 +# Change this to use a different bind address +FEEDGEN_LISTENHOST="localhost" + # Set to something like db.sqlite to store persistently FEEDGEN_SQLITE_LOCATION=":memory:" diff --git a/src/config.ts b/src/config.ts index 0ad853576..98c22e61c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,6 +9,7 @@ export type AppContext = { export type Config = { port: number + listenhost: string hostname: string sqliteLocation: string subscriptionEndpoint: string diff --git a/src/index.ts b/src/index.ts index 496004457..aa34a9758 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ const run = async () => { maybeStr(process.env.FEEDGEN_SERVICE_DID) ?? `did:web:${hostname}` const server = FeedGenerator.create({ port: maybeInt(process.env.FEEDGEN_PORT) ?? 3000, + listenhost: maybeStr(process.env.FEEDGEN_LISTENHOST) ?? 'localhost', sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION) ?? ':memory:', subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT) ?? @@ -17,7 +18,7 @@ const run = async () => { }) await server.start() console.log( - `🤖 running feed generator at http://localhost:${server.cfg.port}`, + `🤖 running feed generator at http://${server.cfg.listenhost}:${server.cfg.port}`, ) } diff --git a/src/server.ts b/src/server.ts index 4e1449c93..90a227bd1 100644 --- a/src/server.ts +++ b/src/server.ts @@ -64,7 +64,7 @@ export class FeedGenerator { async start(): Promise { await migrateToLatest(this.db) this.firehose.run() - this.server = this.app.listen(this.cfg.port) + this.server = this.app.listen(this.cfg.port, this.cfg.listenhost) await events.once(this.server, 'listening') return this.server } From cbdac3417ee3f257e062b63640b474594d23fe5e Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Tue, 30 May 2023 20:20:04 -0500 Subject: [PATCH 32/38] Docs and helpers (#38) * improve docs & helpers around publisher dids * add docs to env --- .env.example | 5 +++++ README.md | 11 ++++++----- src/algos/index.ts | 2 +- src/algos/whats-alf.ts | 3 ++- src/config.ts | 1 + src/index.ts | 2 ++ src/methods/describe-generator.ts | 9 ++++++++- src/methods/feed-generation.ts | 10 ++++++++-- 8 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 49f71dcc5..5139bb5ee 100644 --- a/.env.example +++ b/.env.example @@ -13,5 +13,10 @@ FEEDGEN_SUBSCRIPTION_ENDPOINT="wss://bsky.social" # Set this to the hostname that you intend to run the service at FEEDGEN_HOSTNAME="example.com" +# Set this to the DID of the account you'll use to publish the feed +# You can find your accounts DID by going to +# https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${YOUR_HANDLE} +FEEDGEN_PUBLISHER_DID="did:plc:abcde...." + # Only use this if you want a service did different from did:web # FEEDGEN_SERVICE_DID="did:plc:abcde..." \ No newline at end of file diff --git a/README.md b/README.md index 732c63e91..a9f0dadd2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # ATProto Feed Generator -🚧 Work in Progress 🚧 - -We are actively developing Feed Generator integration into the Bluesky Personal Data Server (PDS). Though we are reasonably confident about the general shape and interfaces laid out here, these interfaces and implementation details _are_ subject to change. - -In the meantime, we've put together this starter kit for devs. It doesn't do everything, but it should be enough to get you familiar with the system and started building! +This is a starter kit for creating ATProto Feed Generators. It's not feature complete, but should give you a good starting ground off of which to build and deploy a feed. ## Overview @@ -44,6 +40,11 @@ Next, you will need to do two things: We've taken care of setting this server up with a did:web. However, you're free to switch this out for did:plc if you like - you may want to if you expect this Feed Generator to be long-standing and possibly migrating domains. +### Deploying your feed +Your feed will need to be accessible at the value supplied to the `FEEDGEN_HOSTNAME` environment variable. + +The service must be set up to respond to HTTPS queries over port 443. + ### Publishing your feed To publish your feed, go to the script at `scripts/publishFeedGen.ts` and fill in the variables at the top. Examples are included, and some are optional. To publish your feed generator, simply run `yarn publishFeed`. diff --git a/src/algos/index.ts b/src/algos/index.ts index 910c0e9a4..b7ee48a76 100644 --- a/src/algos/index.ts +++ b/src/algos/index.ts @@ -8,7 +8,7 @@ import * as whatsAlf from './whats-alf' type AlgoHandler = (ctx: AppContext, params: QueryParams) => Promise const algos: Record = { - [whatsAlf.uri]: whatsAlf.handler, + [whatsAlf.shortname]: whatsAlf.handler, } export default algos diff --git a/src/algos/whats-alf.ts b/src/algos/whats-alf.ts index 0afcadde8..a131547e1 100644 --- a/src/algos/whats-alf.ts +++ b/src/algos/whats-alf.ts @@ -2,7 +2,8 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { QueryParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton' import { AppContext } from '../config' -export const uri = 'at://did:example:alice/app.bsky.feed.generator/whats-alf' +// max 15 chars +export const shortname = 'whats-alf' export const handler = async (ctx: AppContext, params: QueryParams) => { let builder = ctx.db diff --git a/src/config.ts b/src/config.ts index 98c22e61c..3b521f735 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,4 +14,5 @@ export type Config = { sqliteLocation: string subscriptionEndpoint: string serviceDid: string + publisherDid: string } diff --git a/src/index.ts b/src/index.ts index aa34a9758..34c6107c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,8 @@ const run = async () => { subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT) ?? 'wss://bsky.social', + publisherDid: + maybeStr(process.env.FEEDGEN_PUBLISHER_DID) ?? 'did:example:alice', hostname, serviceDid, }) diff --git a/src/methods/describe-generator.ts b/src/methods/describe-generator.ts index 3155e4fc2..cbd87a848 100644 --- a/src/methods/describe-generator.ts +++ b/src/methods/describe-generator.ts @@ -1,10 +1,17 @@ import { Server } from '../lexicon' import { AppContext } from '../config' import algos from '../algos' +import { AtUri } from '@atproto/uri' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.describeFeedGenerator(async () => { - const feeds = Object.keys(algos).map((uri) => ({ uri })) + const feeds = Object.keys(algos).map((shortname) => ({ + uri: AtUri.make( + ctx.cfg.publisherDid, + 'app.bsky.feed.generator', + shortname, + ).toString(), + })) return { encoding: 'application/json', body: { diff --git a/src/methods/feed-generation.ts b/src/methods/feed-generation.ts index 0096a97e2..0e114ec96 100644 --- a/src/methods/feed-generation.ts +++ b/src/methods/feed-generation.ts @@ -3,11 +3,17 @@ import { Server } from '../lexicon' import { AppContext } from '../config' import algos from '../algos' import { validateAuth } from '../auth' +import { AtUri } from '@atproto/uri' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { - const algo = algos[params.feed] - if (!algo) { + const feedUri = new AtUri(params.feed) + const algo = algos[feedUri.rkey] + if ( + feedUri.hostname !== ctx.cfg.publisherDid || + feedUri.collection !== 'app.bsky.feed.generator' || + !algo + ) { throw new InvalidRequestError( 'Unsupported algorithm', 'UnsupportedAlgorithm', From f02b1137478f2d712c57333221007958a38dc607 Mon Sep 17 00:00:00 2001 From: Thomas Fuchs Date: Tue, 30 May 2023 18:28:30 -0700 Subject: [PATCH 33/38] Update tsconfig.json (#37) Cosmetic: remove trailing space --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index ab357f482..0b4f7cc68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "ESNext", - ], + ], "outDir": "dist", "module": "CommonJS", "target": "ES6", @@ -18,4 +18,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +} From e70ea59066fed9360aa0c1f0891767ce5b119efb Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 31 May 2023 19:46:54 -0500 Subject: [PATCH 34/38] add link to community template --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a9f0dadd2..2b9c7cf8f 100644 --- a/README.md +++ b/README.md @@ -145,3 +145,7 @@ You might create a feed for a given community by compiling a list of DIDs within ### A Topical Feed To implement a topical feed, you might filter the algorithm for posts and pass the post text through some filtering mechanism (an LLM, a keyword matcher, etc.) that filters for the topic of your choice. + +## Community Feed Generator Templates + +- [Python](https://github.com/MarshalX/bluesky-feed-generator) - [@MarshalX](https://github.com/MarshalX) \ No newline at end of file From 3e4011acc2740893638268d87471ba1b02b5342e Mon Sep 17 00:00:00 2001 From: Kuba Suder Date: Mon, 12 Jun 2023 18:04:17 +0200 Subject: [PATCH 35/38] Add MIT license file (#47) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9bcd684ad --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Bluesky PBLLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From f4b815926432bedaf66a7da99b6c8eb65144f2c6 Mon Sep 17 00:00:00 2001 From: Edison Lee Date: Fri, 16 Jun 2023 05:52:04 +0800 Subject: [PATCH 36/38] fix: handle firehose subscription error reconnect (Close #44) (#46) --- .env.example | 5 ++++- src/config.ts | 1 + src/index.ts | 2 ++ src/server.ts | 2 +- src/util/subscription.ts | 25 +++++++++++++++---------- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 5139bb5ee..d6e5de9d6 100644 --- a/.env.example +++ b/.env.example @@ -19,4 +19,7 @@ FEEDGEN_HOSTNAME="example.com" FEEDGEN_PUBLISHER_DID="did:plc:abcde...." # Only use this if you want a service did different from did:web -# FEEDGEN_SERVICE_DID="did:plc:abcde..." \ No newline at end of file +# FEEDGEN_SERVICE_DID="did:plc:abcde..." + +# Delay between reconnect attempts to the firehose subscription endpoint (in milliseconds) +FEEDGEN_SUBSCRIPTION_RECONNECT_DELAY=3000 diff --git a/src/config.ts b/src/config.ts index 3b521f735..c394fc93d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,4 +15,5 @@ export type Config = { subscriptionEndpoint: string serviceDid: string publisherDid: string + subscriptionReconnectDelay: number } diff --git a/src/index.ts b/src/index.ts index 34c6107c2..f05fa1bda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,8 @@ const run = async () => { 'wss://bsky.social', publisherDid: maybeStr(process.env.FEEDGEN_PUBLISHER_DID) ?? 'did:example:alice', + subscriptionReconnectDelay: + maybeInt(process.env.FEEDGEN_SUBSCRIPTION_RECONNECT_DELAY) ?? 3000, hostname, serviceDid, }) diff --git a/src/server.ts b/src/server.ts index 90a227bd1..ddbd2d2f0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -63,7 +63,7 @@ export class FeedGenerator { async start(): Promise { await migrateToLatest(this.db) - this.firehose.run() + this.firehose.run(this.cfg.subscriptionReconnectDelay) this.server = this.app.listen(this.cfg.port, this.cfg.listenhost) await events.once(this.server, 'listening') return this.server diff --git a/src/util/subscription.ts b/src/util/subscription.ts index 4061f3f3a..87ab11626 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -36,17 +36,22 @@ export abstract class FirehoseSubscriptionBase { abstract handleEvent(evt: RepoEvent): Promise - async run() { - for await (const evt of this.sub) { - try { - await this.handleEvent(evt) - } catch (err) { - console.error('repo subscription could not handle message', err) - } - // update stored cursor every 20 events or so - if (isCommit(evt) && evt.seq % 20 === 0) { - await this.updateCursor(evt.seq) + async run(subscriptionReconnectDelay: number) { + try { + for await (const evt of this.sub) { + try { + await this.handleEvent(evt) + } catch (err) { + console.error('repo subscription could not handle message', err) + } + // update stored cursor every 20 events or so + if (isCommit(evt) && evt.seq % 20 === 0) { + await this.updateCursor(evt.seq) + } } + } catch (err) { + console.error('repo subscription errored', err) + setTimeout(() => this.run(subscriptionReconnectDelay), subscriptionReconnectDelay) } } From 040801af3a68552199793751c15ad1dd1a379b84 Mon Sep 17 00:00:00 2001 From: Kuba Suder Date: Mon, 19 Jun 2023 23:38:21 +0200 Subject: [PATCH 37/38] added link to a Ruby implementation to the readme (#48) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b9c7cf8f..ee3401404 100644 --- a/README.md +++ b/README.md @@ -148,4 +148,5 @@ To implement a topical feed, you might filter the algorithm for posts and pass t ## Community Feed Generator Templates -- [Python](https://github.com/MarshalX/bluesky-feed-generator) - [@MarshalX](https://github.com/MarshalX) \ No newline at end of file +- [Python](https://github.com/MarshalX/bluesky-feed-generator) - [@MarshalX](https://github.com/MarshalX) +- [Ruby](https://github.com/mackuba/bluesky-feeds-rb) - [@mackuba](https://github.com/mackuba) From 7cb58c39ebbd00ba89d1991c7feffc1b8396519c Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 31 Jul 2023 13:48:07 +0200 Subject: [PATCH 38/38] Added simple dockerfile and compose --- Dockerfile | 15 +++++++++++++++ docker-compose.yml | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..96bbeca4d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine + +WORKDIR /app +COPY package.json . + +RUN yarn install + +# Bare neccesary +COPY src/ ./src +COPY .env . + +EXPOSE 3000 + +# Nu kör vi +CMD ["yarn" , "start"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..c4d887798 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +gversion: "3.2" +services: + + swefeed: + build: . + image: feed + ports: + - "3000:3000" + restart: unless-stopped +# Uncomment if you want to use a persitent sqlite database +# volumes: +# - type: bind +# source: ./db.sqlite +# target: /app/db.sqlite +