add bot custom feed
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
				
			This commit is contained in:
		
							
								
								
									
										10
									
								
								at/feed-generator/env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								at/feed-generator/env
									
									
									
									
									
										Normal 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"
 | 
			
		||||
							
								
								
									
										26
									
								
								at/feed-generator/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								at/feed-generator/readme.md
									
									
									
									
									
										Normal 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
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										43
									
								
								at/feed-generator/src/algos/cmd.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								at/feed-generator/src/algos/cmd.ts
									
									
									
									
									
										Normal 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,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								at/feed-generator/src/algos/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								at/feed-generator/src/algos/index.ts
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										50
									
								
								at/feed-generator/src/subscription.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								at/feed-generator/src/subscription.ts
									
									
									
									
									
										Normal 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 ') || 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()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user