diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..952f0e52 Binary files /dev/null and b/.DS_Store differ diff --git a/.env.example b/.env.example deleted file mode 100644 index 4cfdab33..00000000 --- a/.env.example +++ /dev/null @@ -1,14 +0,0 @@ -# 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" - -# 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/.gitignore b/.gitignore index 6a7d6d8e..f4e0a32d 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ web_modules/ .env.test.local .env.production.local .env.local +.env.prod # parcel-bundler cache (https://parceljs.org/) .cache @@ -119,6 +120,9 @@ dist # TernJS port file .tern-port +# database +tmp/ + # Stores VSCode versions used for testing VSCode extensions .vscode-test diff --git a/scripts/blacksky.png b/scripts/blacksky.png new file mode 100644 index 00000000..cde32549 Binary files /dev/null and b/scripts/blacksky.png differ diff --git a/scripts/publishFeedGen.ts b/scripts/publishFeedGen.ts index 9f226ba0..c2a73b40 100644 --- a/scripts/publishFeedGen.ts +++ b/scripts/publishFeedGen.ts @@ -8,28 +8,28 @@ const run = async () => { // YOUR bluesky handle // Ex: user.bsky.social - const handle = '' + const handle = process.env.IDENTIFIER // YOUR bluesky password, or preferably an App Password (found in your client settings) // Ex: abcd-1234-efgh-5678 - const password = '' + const password = process.env.PASSWORD // A short name for the record that will show in urls // Lowercase with no spaces. // Ex: whats-hot - const recordName = '' + const recordName = 'blacksky' // A display name for your feed // Ex: What's Hot - const displayName = '' + const displayName = 'Blacksky' // (Optional) A description of your feed // Ex: Top trending content from the whole network - const description = '' + const description = 'Posts from the Black users of Bluesky, based on the original thread (https://bsky.app/profile/did:plc:xgjcudwc5yk4z2uex5v6e7bl/post/3jtypsmfaoh2x)' // (Optional) The path to an image to be used as your feed's avatar // Ex: ~/path/to/avatar.jpeg - const avatar: string = '' + const avatar: string = 'blacksky.png' // ------------------------------------- // NO NEED TO TOUCH ANYTHING BELOW HERE diff --git a/src/algos/whats-alf.ts b/src/algos/blacksky.ts similarity index 92% rename from src/algos/whats-alf.ts rename to src/algos/blacksky.ts index 0afcadde..856cfdfe 100644 --- a/src/algos/whats-alf.ts +++ b/src/algos/blacksky.ts @@ -2,7 +2,7 @@ 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 uri = 'at://did:plc:w4xbfzo7kqfes5zb7r6qv3rw/app.bsky.feed.generator/blacksky' export const handler = async (ctx: AppContext, params: QueryParams) => { let builder = ctx.db diff --git a/src/algos/index.ts b/src/algos/index.ts index 910c0e9a..455fbd12 100644 --- a/src/algos/index.ts +++ b/src/algos/index.ts @@ -3,12 +3,12 @@ import { QueryParams, OutputSchema as AlgoOutput, } from '../lexicon/types/app/bsky/feed/getFeedSkeleton' -import * as whatsAlf from './whats-alf' +import * as blacksky from './blacksky' type AlgoHandler = (ctx: AppContext, params: QueryParams) => Promise const algos: Record = { - [whatsAlf.uri]: whatsAlf.handler, + [blacksky.uri]: blacksky.handler, } export default algos diff --git a/src/index.ts b/src/index.ts index 49600445..852d1770 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ const run = async () => { const serviceDid = maybeStr(process.env.FEEDGEN_SERVICE_DID) ?? `did:web:${hostname}` const server = FeedGenerator.create({ - port: maybeInt(process.env.FEEDGEN_PORT) ?? 3000, + port: maybeInt(process.env.PORT) ?? 3000, sqliteLocation: maybeStr(process.env.FEEDGEN_SQLITE_LOCATION) ?? ':memory:', subscriptionEndpoint: maybeStr(process.env.FEEDGEN_SUBSCRIPTION_ENDPOINT) ?? diff --git a/src/subscription.ts b/src/subscription.ts index b3a141e8..c8a8bafa 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -2,28 +2,52 @@ import { OutputSchema as RepoEvent, isCommit, } from './lexicon/types/com/atproto/sync/subscribeRepos' +import { + OutputSchema as ThreadPost +} from './lexicon/types/app/bsky/feed/getPostThread' import { FirehoseSubscriptionBase, getOpsByType } from './util/subscription' +import { AtpAgent, BlobRef } from '@atproto/api' +import dotenv from 'dotenv' + +function parseReplies(threadPost) { + let identifiers: any[] = []; + if (threadPost.replies) { + identifiers = [...new Set(threadPost.replies + .map((reply) => { + return parseReplies(reply) + }))] + } + identifiers.push(threadPost.post.author.did) + return identifiers.flat() +} export class FirehoseSubscription extends FirehoseSubscriptionBase { async handleEvent(evt: RepoEvent) { if (!isCommit(evt)) return - const ops = await getOpsByType(evt) + dotenv.config() - // 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 handle = process.env.IDENTIFIER ?? '' + const password = process.env.PASSWORD ?? '' + const uri = process.env.BLACKSKYTHREAD ?? '' + + const agent = new AtpAgent({ service: 'https://bsky.social' }) + await agent.login({ identifier: handle, password }) + + const blackskyThread = await agent.api.app.bsky.feed.getPostThread({uri}) + const blacksky = new Set(parseReplies(blackskyThread.data.thread)) + + 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 create.record.text.toLowerCase().includes('alf') + // Filter for authors from the blacksky thread + return blacksky.has(create.author) }) .map((create) => { - // map alf-related posts to a db row + // Create Blacksky posts in db + console.log(`Blacksky author ${create.author}!`) + console.log(create) return { uri: create.uri, cid: create.cid, diff --git a/src/util/subscription.ts b/src/util/subscription.ts index 4061f3f3..341017cf 100644 --- a/src/util/subscription.ts +++ b/src/util/subscription.ts @@ -80,7 +80,7 @@ export const getOpsByType = async (evt: Commit): Promise => { for (const op of evt.ops) { const uri = `at://${evt.repo}/${op.path}` const [collection] = op.path.split('/') - + if (op.action === 'update') continue // updates not supported yet if (op.action === 'create') {