add bot custom feed
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s

This commit is contained in:
2024-04-13 02:05:05 +09:00
parent 5d60645c0f
commit 840320d0d2
13 changed files with 845 additions and 13 deletions

10
at/feed-generator/env Normal file
View File

@ -0,0 +1,10 @@
FEEDGEN_PORT=3000
FEEDGEN_LISTENHOST="0.0.0.0"
FEEDGEN_SQLITE_LOCATION="/data/db.sqlite"
FEEDGEN_SUBSCRIPTION_ENDPOINT="wss://bgs.syu.is"
FEEDGEN_PUBLISHER_DID="did:web:feed.syu.is"
FEEDGEN_HOSTNAME="feed.syu.is"
FEEDGEN_SUBSCRIPTION_RECONNECT_DELAY=3000
FEEDGEN_PUBLISHER_DID=did:plc:4hqjfn7m6n5hno3doamuhgef
FEEDGEN_SUBSCRIPTION_ENDPOINT="wss://bsky.network"

View File

@ -0,0 +1,26 @@
# custom feed
- at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd
- [bsky.app](https://bsky.app/profile/did:plc:4hqjfn7m6n5hno3doamuhgef/feed/cmd)
- [app.bsky.feed.getFeedSkeleton](https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd)
```sh
did=did:plc:4hqjfn7m6n5hno3doamuhgef
col=app.bsky.feed.generator
cid=cmd
uri=at://$did/$col/$cid
echo $uri
```
## bsky-feed
```sh
$ git clone https://github.com/bluesky-social/feed-generator
```
```sh
docker compose build feed-generator
docker build -t publish_feed -f Dockerfile.feed .
docker run publish_feed
```

View File

@ -0,0 +1,43 @@
import { InvalidRequestError } from '@atproto/xrpc-server'
import { QueryParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
import { AppContext } from '../config'
// max 15 chars
export const shortname = 'cmd'
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,
}
}

View File

@ -0,0 +1,14 @@
import { AppContext } from '../config'
import {
QueryParams,
OutputSchema as AlgoOutput,
} from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
import * as cmd from './cmd'
type AlgoHandler = (ctx: AppContext, params: QueryParams) => Promise<AlgoOutput>
const algos: Record<string, AlgoHandler> = {
[cmd.shortname]: cmd.handler,
}
export default algos

View File

@ -0,0 +1,50 @@
import {
OutputSchema as RepoEvent,
isCommit,
} from './lexicon/types/com/atproto/sync/subscribeRepos'
import { FirehoseSubscriptionBase, getOpsByType } from './util/subscription'
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) => {
return create.record.text.match('^/[a-z]') || create.record.text.match('^@ai ') || create.record.text.match('/ai ');
//return create.record.text.toLowerCase().includes('alf')
})
.map((create) => {
// map alf-related posts to a db row
return {
uri: create.uri,
cid: create.cid,
replyParent: create.record?.reply?.parent.uri ?? null,
replyRoot: create.record?.reply?.root.uri ?? null,
indexedAt: new Date().toISOString(),
}
})
if (postsToDelete.length > 0) {
await this.db
.deleteFrom('post')
.where('uri', 'in', postsToDelete)
.execute()
}
if (postsToCreate.length > 0) {
await this.db
.insertInto('post')
.values(postsToCreate)
.onConflict((oc) => oc.doNothing())
.execute()
}
}
}