A chill Bluesky bot, with responses powered by Gemini.
at main 2.6 kB view raw
1import { graphemeLength, Post, PostReference } from "@skyware/bot"; 2import { muted_threads } from "../db/schema"; 3import * as c from "../constants"; 4import { eq } from "drizzle-orm"; 5import * as yaml from "js-yaml"; 6import bot from "../bot"; 7import db from "../db"; 8 9/* 10 Traversal 11*/ 12export async function traverseThread(post: Post): Promise<Post[]> { 13 const thread: Post[] = [ 14 post, 15 ]; 16 let currentPost: Post | undefined = post; 17 let parentCount = 0; 18 19 while ( 20 currentPost && parentCount < c.MAX_THREAD_DEPTH 21 ) { 22 const parentPost = await currentPost.fetchParent(); 23 24 if (parentPost) { 25 thread.push(parentPost); 26 currentPost = parentPost; 27 } else { 28 break; 29 } 30 parentCount++; 31 } 32 33 return thread.reverse(); 34} 35 36export function parseThread(thread: Post[]) { 37 return yaml.dump({ 38 uri: thread[0]!.uri, 39 posts: thread.map((post) => ({ 40 author: `${post.author.displayName} (${post.author.handle})`, 41 text: post.text, 42 })), 43 }); 44} 45 46/* 47 Split Responses 48*/ 49export function exceedsGraphemes(content: string) { 50 return graphemeLength(content) > c.MAX_GRAPHEMES; 51} 52 53export function splitResponse(text: string): string[] { 54 const words = text.split(" "); 55 const chunks: string[] = []; 56 let currentChunk = ""; 57 58 for (const word of words) { 59 if (currentChunk.length + word.length + 1 < c.MAX_GRAPHEMES - 10) { 60 currentChunk += ` ${word}`; 61 } else { 62 chunks.push(currentChunk.trim()); 63 currentChunk = word; 64 } 65 } 66 67 if (currentChunk.trim()) { 68 chunks.push(currentChunk.trim()); 69 } 70 71 const total = chunks.length; 72 if (total <= 1) return [text]; 73 74 return chunks.map((chunk, i) => `(${i + 1}/${total}) ${chunk}`); 75} 76 77export async function multipartResponse(content: string, post?: Post) { 78 const parts = splitResponse(content).filter((p) => p.trim().length > 0); 79 80 let latest: PostReference; 81 let rootUri: string; 82 83 if (post) { 84 rootUri = (post as any).rootUri ?? (post as any).uri; 85 latest = await post.reply({ text: parts[0]! }); 86 } else { 87 latest = await bot.post({ text: parts[0]! }); 88 rootUri = latest.uri; 89 } 90 91 for (const text of parts.slice(1)) { 92 latest = await latest.reply({ text }); 93 } 94 95 return rootUri; 96} 97 98/* 99 Misc. 100*/ 101export async function isThreadMuted(post: Post): Promise<boolean> { 102 const root = post.root || post; 103 if (!root) return false; 104 105 console.log("Found root: ", root.text); 106 107 const [mute] = await db 108 .select() 109 .from(muted_threads) 110 .where(eq(muted_threads.uri, root.uri)); 111 112 return mute !== undefined; 113}