Bots to use with the firehose/jetstream
at main 124 lines 3.4 kB view raw
1import { AtpAgent, AtpAgentOptions } from "@atproto/api"; 2import { Logger } from "../utils/logger"; 3import type { Bot } from "../types/bot"; 4 5/** 6 * Base class for all bot agents with common functionality. 7 * Provides correlation tracking and structured logging capabilities. 8 */ 9export abstract class BotAgent extends AtpAgent { 10 protected currentCorrelationId: string | null = null; 11 protected operationStartTime: number | null = null; 12 13 constructor( 14 public opts: AtpAgentOptions, 15 protected bot: Bot 16 ) { 17 super(opts); 18 } 19 20 /** 21 * Start tracking an operation with correlation ID and timing. 22 * @protected 23 */ 24 protected startOperationTracking(): void { 25 this.currentCorrelationId = Logger.generateCorrelationId(); 26 this.operationStartTime = Date.now(); 27 } 28 29 /** 30 * Clear operation tracking state. 31 * @protected 32 */ 33 protected clearOperationTracking(): void { 34 this.currentCorrelationId = null; 35 this.operationStartTime = null; 36 } 37 38 /** 39 * Get the bot identifier for logging purposes. 40 * @protected 41 */ 42 protected getBotId(): string { 43 return this.bot.username || this.bot.identifier; 44 } 45 46 /** 47 * Log a message with correlation ID during bot execution. 48 * Call this from within your bot methods to log with proper correlation tracking. 49 */ 50 logAction( 51 level: "info" | "warn" | "error", 52 message: string, 53 additionalContext?: Record<string, unknown> 54 ): void { 55 const logContext: Record<string, unknown> = { 56 botId: this.getBotId(), 57 ...additionalContext, 58 }; 59 60 if (this.currentCorrelationId && this.operationStartTime) { 61 logContext.correlationId = this.currentCorrelationId; 62 logContext.operation = this.getOperationName(); 63 logContext.duration = `${Date.now() - this.operationStartTime}ms`; 64 } 65 66 switch (level) { 67 case "info": 68 Logger.info(message, logContext); 69 break; 70 case "warn": 71 Logger.warn(message, logContext); 72 break; 73 case "error": 74 Logger.error(message, logContext); 75 break; 76 } 77 } 78 79 /** 80 * Get the operation name for logging. Override in subclasses. 81 * @protected 82 */ 83 protected abstract getOperationName(): string; 84} 85 86/** 87 * Generic bot initialization function that handles common setup. 88 */ 89export async function initializeBotAgent<T extends BotAgent>( 90 botType: string, 91 bot: Bot, 92 createAgent: (opts: AtpAgentOptions, bot: Bot) => T 93): Promise<T | null> { 94 const botId = bot.username ?? bot.identifier; 95 const correlationId = Logger.startOperation(`initialize${botType}`, { botId }); 96 const startTime = Date.now(); 97 98 const agent = createAgent({ service: bot.service }, bot); 99 100 try { 101 Logger.info(`Initializing ${botType.toLowerCase()}`, { correlationId, botId }); 102 103 const login = await agent.login({ 104 identifier: bot.identifier, 105 password: bot.password!, 106 }); 107 108 if (!login.success) { 109 Logger.warn(`${botType} login failed`, { correlationId, botId }); 110 return null; 111 } 112 113 Logger.endOperation(`initialize${botType}`, startTime, { correlationId, botId }); 114 return agent; 115 } catch (error) { 116 Logger.error(`Failed to initialize ${botType.toLowerCase()}`, { 117 correlationId, 118 botId, 119 error: error instanceof Error ? error.message : String(error), 120 duration: Date.now() - startTime, 121 }); 122 return null; 123 } 124}