+1
-1
dist/index.js
+1
-1
dist/index.js
···
1042
1042
// src/utils/wsToFeed.ts
1043
1043
function websocketToFeedEntry(data) {
1044
1044
var _a, _b, _c, _d;
1045
-
const message = data;
1045
+
const message = JSON.parse(data.toString());
1046
1046
if (!message.commit || !message.commit.record || !message.commit.record["$type"] || !message.did || !message.commit.cid || !message.commit.rkey || message.commit.operation !== "create") {
1047
1047
return null;
1048
1048
}
+1
-1
dist/index.js.map
+1
-1
dist/index.js.map
···
1
-
{"version":3,"sources":["../src/index.ts","../src/utils/logger.ts","../src/bots/baseBotAgent.ts","../src/bots/actionBot.ts","../src/bots/cronBot.ts","../src/bots/keywordBot.ts","../src/utils/websocketClient.ts","../src/utils/healthCheck.ts","../src/utils/jetstreamSubscription.ts","../src/utils/strings.ts","../src/utils/wsToFeed.ts"],"sourcesContent":["export * from \"./types/bot\";\nexport * from \"./types/message\";\nexport * from \"./types/post\";\nexport * from \"./bots/actionBot\";\nexport * from \"./bots/baseBotAgent\";\nexport * from \"./bots/cronBot\";\nexport * from \"./bots/keywordBot\";\nexport * from \"./utils/jetstreamSubscription\";\nexport * from \"./utils/logger\";\nexport * from \"./utils/strings\";\nexport * from \"./utils/websocketClient\";\nexport * from \"./utils/wsToFeed\";\nexport * from \"./utils/healthCheck\";\n","export enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n correlationId?: string;\n botId?: string;\n operation?: string;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * A performance-optimized logging utility class providing static methods for various log levels.\n * Each log message is prefixed with a timestamp and log level.\n * Supports conditional logging based on log levels and configurable timezone.\n */\nexport class Logger {\n private static logLevel: LogLevel = LogLevel.INFO;\n private static timezone: string = \"Europe/Vienna\";\n private static correlationId: string | null = null;\n\n /**\n * Generate a new correlation ID for tracking related operations.\n */\n static generateCorrelationId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Set the correlation ID for subsequent log entries.\n * @param id - The correlation ID to use, or null to generate a new one\n */\n static setCorrelationId(id?: string | null) {\n this.correlationId = id || this.generateCorrelationId();\n }\n\n /**\n * Get the current correlation ID.\n */\n static getCorrelationId(): string | null {\n return this.correlationId;\n }\n\n /**\n * Clear the current correlation ID.\n */\n static clearCorrelationId() {\n this.correlationId = null;\n }\n\n /**\n * Set the minimum log level. Messages below this level will not be logged.\n * @param level - The minimum log level\n */\n static setLogLevel(level: LogLevel) {\n this.logLevel = level;\n }\n\n /**\n * Set the timezone for log timestamps.\n * @param timezone - The timezone string (e.g., \"Europe/Vienna\", \"UTC\")\n */\n static setTimezone(timezone: string) {\n this.timezone = timezone;\n }\n\n /**\n * Get the current log level.\n */\n static getLogLevel(): LogLevel {\n return this.logLevel;\n }\n\n /**\n * Generate a formatted timestamp string.\n * @private\n */\n private static getTimestamp(): string {\n return new Date().toLocaleString(\"de-DE\", { timeZone: this.timezone });\n }\n\n /**\n * Internal logging method that checks log level before processing.\n * @private\n */\n private static log(\n level: LogLevel,\n levelName: string,\n message: string,\n context?: LogContext | object | string,\n logFn = console.log\n ) {\n if (level < this.logLevel) {\n return; // Skip logging if below threshold\n }\n\n const timestamp = this.getTimestamp();\n let formattedMessage = `${timestamp} [${levelName}]`;\n\n // Add correlation ID if available\n if (this.correlationId) {\n formattedMessage += ` [${this.correlationId}]`;\n }\n\n // Add context correlation ID if provided and different from global one\n if (\n context &&\n typeof context === \"object\" &&\n \"correlationId\" in context &&\n context.correlationId &&\n context.correlationId !== this.correlationId\n ) {\n formattedMessage += ` [${context.correlationId}]`;\n }\n\n formattedMessage += `: ${message}`;\n\n if (context) {\n // Create structured log entry for objects\n if (typeof context === \"object\") {\n const logEntry = {\n timestamp: new Date().toISOString(),\n level: levelName,\n message,\n correlationId: this.correlationId,\n ...context,\n };\n logFn(formattedMessage, logEntry);\n } else {\n logFn(formattedMessage, context);\n }\n } else {\n logFn(formattedMessage);\n }\n }\n /**\n * Logs an informational message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static info(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.INFO, \"INFO\", message, context, console.info);\n }\n\n /**\n * Logs a warning message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static warn(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.WARN, \"WARNING\", message, context, console.warn);\n }\n\n /**\n * Logs an error message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static error(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.ERROR, \"ERROR\", message, context, console.error);\n }\n\n /**\n * Logs a debug message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static debug(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.DEBUG, \"DEBUG\", message, context, console.debug);\n }\n\n /**\n * Log operation start with timing.\n * @param operation - The operation name\n * @param context - Additional context\n */\n static startOperation(operation: string, context?: LogContext): string {\n const correlationId = context?.correlationId || this.generateCorrelationId();\n this.setCorrelationId(correlationId);\n\n this.info(`Starting operation: ${operation}`, {\n operation,\n correlationId,\n ...context,\n });\n\n return correlationId;\n }\n\n /**\n * Log operation completion with timing.\n * @param operation - The operation name\n * @param startTime - The start time from Date.now()\n * @param context - Additional context\n */\n static endOperation(operation: string, startTime: number, context?: LogContext) {\n const duration = Date.now() - startTime;\n\n this.info(`Completed operation: ${operation}`, {\n operation,\n duration: `${duration}ms`,\n ...context,\n });\n }\n}\n","import { AtpAgent, AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { Bot } from \"../types/bot\";\n\n/**\n * Base class for all bot agents with common functionality.\n * Provides correlation tracking and structured logging capabilities.\n */\nexport abstract class BotAgent extends AtpAgent {\n protected currentCorrelationId: string | null = null;\n protected operationStartTime: number | null = null;\n\n constructor(\n public opts: AtpAgentOptions,\n protected bot: Bot\n ) {\n super(opts);\n }\n\n /**\n * Start tracking an operation with correlation ID and timing.\n * @protected\n */\n protected startOperationTracking(): void {\n this.currentCorrelationId = Logger.generateCorrelationId();\n this.operationStartTime = Date.now();\n }\n\n /**\n * Clear operation tracking state.\n * @protected\n */\n protected clearOperationTracking(): void {\n this.currentCorrelationId = null;\n this.operationStartTime = null;\n }\n\n /**\n * Get the bot identifier for logging purposes.\n * @protected\n */\n protected getBotId(): string {\n return this.bot.username || this.bot.identifier;\n }\n\n /**\n * Log a message with correlation ID during bot execution.\n * Call this from within your bot methods to log with proper correlation tracking.\n */\n logAction(\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n additionalContext?: Record<string, unknown>\n ): void {\n const logContext: Record<string, unknown> = {\n botId: this.getBotId(),\n ...additionalContext,\n };\n\n if (this.currentCorrelationId && this.operationStartTime) {\n logContext.correlationId = this.currentCorrelationId;\n logContext.operation = this.getOperationName();\n logContext.duration = `${Date.now() - this.operationStartTime}ms`;\n }\n\n switch (level) {\n case \"info\":\n Logger.info(message, logContext);\n break;\n case \"warn\":\n Logger.warn(message, logContext);\n break;\n case \"error\":\n Logger.error(message, logContext);\n break;\n }\n }\n\n /**\n * Get the operation name for logging. Override in subclasses.\n * @protected\n */\n protected abstract getOperationName(): string;\n}\n\n/**\n * Generic bot initialization function that handles common setup.\n */\nexport async function initializeBotAgent<T extends BotAgent>(\n botType: string,\n bot: Bot,\n createAgent: (opts: AtpAgentOptions, bot: Bot) => T\n): Promise<T | null> {\n const botId = bot.username ?? bot.identifier;\n const correlationId = Logger.startOperation(`initialize${botType}`, { botId });\n const startTime = Date.now();\n\n const agent = createAgent({ service: bot.service }, bot);\n\n try {\n Logger.info(`Initializing ${botType.toLowerCase()}`, { correlationId, botId });\n\n const login = await agent.login({\n identifier: bot.identifier,\n password: bot.password!,\n });\n\n if (!login.success) {\n Logger.warn(`${botType} login failed`, { correlationId, botId });\n return null;\n }\n\n Logger.endOperation(`initialize${botType}`, startTime, { correlationId, botId });\n return agent;\n } catch (error) {\n Logger.error(`Failed to initialize ${botType.toLowerCase()}`, {\n correlationId,\n botId,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n });\n return null;\n }\n}\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { ActionBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class ActionBotAgent extends BotAgent {\n public actionBot: ActionBot;\n\n constructor(opts: AtpAgentOptions, actionBot: ActionBot) {\n super(opts, actionBot);\n this.actionBot = actionBot;\n }\n\n async doAction(params?: unknown): Promise<void> {\n // Start operation tracking but don't log yet\n this.startOperationTracking();\n\n try {\n await this.actionBot.action(this, params);\n } catch (error) {\n Logger.error(\"Action bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"actionBot.doAction\";\n }\n}\n\nexport const useActionBotAgent = async (actionBot: ActionBot): Promise<ActionBotAgent | null> => {\n return initializeBotAgent(\n \"ActionBot\",\n actionBot,\n (opts, bot) => new ActionBotAgent(opts, bot as ActionBot)\n );\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { CronJob } from \"cron\";\nimport { Logger } from \"../utils/logger\";\nimport type { CronBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class CronBotAgent extends BotAgent {\n public job: CronJob;\n public cronBot: CronBot;\n\n constructor(opts: AtpAgentOptions, cronBot: CronBot) {\n super(opts, cronBot);\n this.cronBot = cronBot;\n\n this.job = new CronJob(\n cronBot.cronJob.scheduleExpression,\n async () => {\n // Start operation tracking for cron execution\n this.startOperationTracking();\n\n try {\n await cronBot.action(this);\n } catch (error) {\n Logger.error(\"Cron bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"cronBot.action\",\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n },\n cronBot.cronJob.callback,\n false,\n cronBot.cronJob.timeZone\n );\n }\n\n protected getOperationName(): string {\n return \"cronBot.action\";\n }\n}\n\nexport const useCronBotAgent = async (cronBot: CronBot): Promise<CronBotAgent | null> => {\n const agent = await initializeBotAgent(\n \"CronBot\",\n cronBot,\n (opts, bot) => new CronBotAgent(opts, bot as CronBot)\n );\n\n // Start the cron job after successful initialization\n if (agent) {\n agent.job.start();\n }\n\n return agent;\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport type { BotReply, KeywordBot } from \"../types/bot\";\nimport type { Post, UriCid } from \"../types/post\";\nimport { Logger } from \"../utils/logger\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class KeywordBotAgent extends BotAgent {\n public keywordBot: KeywordBot;\n\n constructor(opts: AtpAgentOptions, keywordBot: KeywordBot) {\n super(opts, keywordBot);\n this.keywordBot = keywordBot;\n }\n\n async likeAndReplyIfFollower(post: Post): Promise<void> {\n if (post.authorDid === this.assertDid) {\n return;\n }\n\n const replies = filterBotReplies(post.text, this.keywordBot.replies);\n if (replies.length < 1) {\n return;\n }\n\n // Start operation tracking when actual work begins\n this.startOperationTracking();\n\n try {\n const actorProfile = await this.getProfile({ actor: post.authorDid });\n\n if (actorProfile.success) {\n if (!actorProfile.data.viewer?.followedBy) {\n return;\n }\n\n const replyCfg = replies[Math.floor(Math.random() * replies.length)];\n const message = replyCfg.messages[Math.floor(Math.random() * replyCfg.messages.length)];\n const reply = buildReplyToPost(\n { uri: post.rootUri, cid: post.rootCid },\n { uri: post.uri, cid: post.cid },\n message\n );\n\n await Promise.all([this.like(post.uri, post.cid), this.post(reply)]);\n\n this.logAction(\"info\", `Replied to post: ${post.uri}`, {\n postUri: post.uri,\n authorDid: post.authorDid,\n keyword: replyCfg.keyword,\n message: message,\n });\n }\n } catch (error) {\n Logger.error(\"Keyword bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"keywordBot.likeAndReplyIfFollower\",\n error: error instanceof Error ? error.message : String(error),\n postUri: post.uri,\n authorDid: post.authorDid,\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"keywordBot.likeAndReplyIfFollower\";\n }\n}\n\nexport function buildReplyToPost(root: UriCid, parent: UriCid, message: string) {\n return {\n $type: \"app.bsky.feed.post\" as const,\n text: message,\n reply: {\n root: root,\n parent: parent,\n },\n };\n}\n\nexport function filterBotReplies(text: string, botReplies: BotReply[]) {\n // Cache the lowercased text to avoid multiple toLowerCase() calls\n const lowerText = text.toLowerCase();\n\n return botReplies.filter(reply => {\n // Use cached lowercase comparison\n const keyword = reply.keyword.toLowerCase();\n if (!lowerText.includes(keyword)) {\n return false;\n }\n\n // Early return if no exclusions\n if (!Array.isArray(reply.exclude) || reply.exclude.length === 0) {\n return true;\n }\n\n // Use some() for early exit on first match\n const hasExcludedWord = reply.exclude.some(excludeWord =>\n lowerText.includes(excludeWord.toLowerCase())\n );\n\n return !hasExcludedWord;\n });\n}\n\nexport const useKeywordBotAgent = async (\n keywordBot: KeywordBot\n): Promise<KeywordBotAgent | null> => {\n return initializeBotAgent(\n \"KeywordBot\",\n keywordBot,\n (opts, bot) => new KeywordBotAgent(opts, bot as KeywordBot)\n );\n};\n","import WebSocket from \"ws\";\nimport { Logger } from \"./logger\";\nimport { healthMonitor } from \"./healthCheck\";\n\ninterface WebSocketClientOptions {\n /** The URL of the WebSocket server to connect to. */\n service: string | string[];\n /** The interval in milliseconds to wait before attempting to reconnect when the connection closes. Default is 5000ms. */\n reconnectInterval?: number;\n /** The interval in milliseconds for sending ping messages (heartbeats) to keep the connection alive. Default is 10000ms. */\n pingInterval?: number;\n /** Maximum number of consecutive reconnection attempts per service. Default is 3. */\n maxReconnectAttempts?: number;\n /** Maximum delay between reconnection attempts in milliseconds. Default is 30000ms (30 seconds). */\n maxReconnectDelay?: number;\n /** Exponential backoff factor for reconnection delays. Default is 1.5. */\n backoffFactor?: number;\n /** Maximum number of attempts to cycle through all services before giving up. Default is 2. */\n maxServiceCycles?: number;\n}\n\n/**\n * A WebSocket client that automatically attempts to reconnect upon disconnection\n * and periodically sends ping messages (heartbeats) to ensure the connection remains alive.\n *\n * Extend this class and override the protected `onOpen`, `onMessage`, `onError`, and `onClose` methods\n * to implement custom handling of WebSocket events.\n */\nexport class WebSocketClient {\n private service: string | string[];\n private reconnectInterval: number;\n private pingInterval: number;\n private ws: WebSocket | null = null;\n private pingTimeout: NodeJS.Timeout | null = null;\n private serviceIndex = 0;\n private reconnectAttempts = 0;\n private serviceCycles = 0;\n private maxReconnectAttempts: number;\n private maxServiceCycles: number;\n private maxReconnectDelay: number;\n private backoffFactor: number;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n private isConnecting = false;\n private shouldReconnect = true;\n private messageCount = 0;\n private lastMessageTime = 0;\n private healthCheckName: string;\n\n /**\n * Creates a new instance of `WebSocketClient`.\n *\n * @param options - Configuration options for the WebSocket client, including URL, reconnect interval, and ping interval.\n */\n constructor(options: WebSocketClientOptions) {\n this.service = options.service;\n this.reconnectInterval = options.reconnectInterval || 5000;\n this.pingInterval = options.pingInterval || 10000;\n this.maxReconnectAttempts = options.maxReconnectAttempts || 3;\n this.maxServiceCycles = options.maxServiceCycles || 2;\n this.maxReconnectDelay = options.maxReconnectDelay || 30000;\n this.backoffFactor = options.backoffFactor || 1.5;\n\n // Generate unique health check name\n this.healthCheckName = `websocket_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;\n\n // Register health check\n healthMonitor.registerHealthCheck(this.healthCheckName, async () => {\n return this.getConnectionState() === \"CONNECTED\";\n });\n\n // Initialize metrics\n healthMonitor.setMetric(`${this.healthCheckName}_messages_received`, 0);\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, 0);\n\n this.run();\n }\n\n /**\n * Initiates a WebSocket connection to the specified URL.\n *\n * This method sets up event listeners for `open`, `message`, `error`, and `close` events.\n * When the connection opens, it starts the heartbeat mechanism.\n * On close, it attempts to reconnect after a specified interval.\n */\n private run() {\n if (this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n const currentService = Array.isArray(this.service)\n ? this.service[this.serviceIndex]\n : this.service;\n\n Logger.info(`Attempting to connect to WebSocket: ${currentService}`);\n this.ws = new WebSocket(currentService);\n\n this.ws.on(\"open\", () => {\n Logger.info(\"WebSocket connected successfully\", {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n });\n this.isConnecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n this.serviceCycles = 0; // Reset cycles on successful connection\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n this.startHeartbeat();\n this.onOpen();\n });\n\n this.ws.on(\"message\", (data: WebSocket.Data) => {\n this.messageCount++;\n this.lastMessageTime = Date.now();\n healthMonitor.incrementMetric(`${this.healthCheckName}_messages_received`);\n this.onMessage(data);\n });\n\n this.ws.on(\"error\", error => {\n Logger.error(\"WebSocket error:\", error);\n this.isConnecting = false;\n this.onError(error);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n Logger.info(`WebSocket disconnected. Code: ${code}, Reason: ${reason.toString()}`);\n this.isConnecting = false;\n this.stopHeartbeat();\n this.onClose();\n\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n }\n\n /**\n * Attempts to reconnect to the WebSocket server after the specified `reconnectInterval`.\n * It clears all event listeners on the old WebSocket and initiates a new connection.\n */\n private scheduleReconnect() {\n this.reconnectAttempts++;\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n\n // Check if we should try the next service\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.shouldTryNextService()) {\n this.moveToNextService();\n return; // Try next service immediately\n } else {\n Logger.error(\"All services exhausted after maximum cycles\", {\n totalServices: Array.isArray(this.service) ? this.service.length : 1,\n maxServiceCycles: this.maxServiceCycles,\n serviceCycles: this.serviceCycles,\n });\n return; // Give up entirely\n }\n }\n\n const delay = Math.min(\n this.reconnectInterval * Math.pow(this.backoffFactor, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n Logger.info(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} for service`,\n {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n delay: `${delay}ms`,\n }\n );\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n }\n\n this.reconnectTimeout = setTimeout(() => {\n this.cleanup();\n this.run();\n }, delay);\n }\n\n /**\n * Check if we should try the next service in the array.\n */\n private shouldTryNextService(): boolean {\n if (!Array.isArray(this.service)) {\n return false; // Single service, can't switch\n }\n\n return this.serviceCycles < this.maxServiceCycles;\n }\n\n /**\n * Move to the next service in the array and reset reconnection attempts.\n */\n private moveToNextService() {\n if (!Array.isArray(this.service)) {\n return;\n }\n\n const previousIndex = this.serviceIndex;\n this.serviceIndex = (this.serviceIndex + 1) % this.service.length;\n\n // If we've gone through all services once, increment the cycle counter\n if (this.serviceIndex === 0) {\n this.serviceCycles++;\n }\n\n this.reconnectAttempts = 0; // Reset attempts for the new service\n\n Logger.info(\"Switching to next service\", {\n previousService: this.service[previousIndex],\n previousIndex,\n newService: this.getCurrentService(),\n newIndex: this.serviceIndex,\n serviceCycle: this.serviceCycles,\n });\n\n // Try the new service immediately\n this.cleanup();\n this.run();\n }\n\n private cleanup() {\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close();\n }\n this.ws = null;\n }\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n }\n\n /**\n * Starts sending periodic ping messages to the server.\n *\n * This function uses `setInterval` to send a ping at the configured `pingInterval`.\n * If the WebSocket is not open, pings are not sent.\n */\n private startHeartbeat() {\n this.pingTimeout = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, this.pingInterval);\n }\n\n /**\n * Stops sending heartbeat pings by clearing the ping interval.\n */\n private stopHeartbeat() {\n if (this.pingTimeout) {\n clearInterval(this.pingTimeout);\n this.pingTimeout = null;\n }\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n *\n * Override this method in a subclass to implement custom logic on connection.\n */\n protected onOpen() {\n // Custom logic for connection open\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * @param data - The data received from the WebSocket server.\n *\n * Override this method in a subclass to implement custom message handling.\n */\n protected onMessage(_data: WebSocket.Data) {\n // Custom logic for handling received messages\n }\n\n /**\n * Called when a WebSocket error occurs.\n *\n * @param error - The error that occurred.\n *\n * Override this method in a subclass to implement custom error handling.\n * Note: Service switching is now handled in the reconnection logic, not here.\n */\n protected onError(_error: Error) {\n // Custom logic for handling errors - override in subclasses\n // Service switching is handled automatically in scheduleReconnect()\n }\n\n /**\n * Called when the WebSocket connection is closed.\n *\n * Override this method in a subclass to implement custom logic on disconnection.\n */\n protected onClose() {\n // Custom logic for handling connection close\n }\n\n /**\n * Sends data to the connected WebSocket server, if the connection is open.\n *\n * @param data - The data to send.\n */\n public send(data: string | Buffer | ArrayBuffer | Buffer[]) {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(data);\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n */\n public close() {\n this.shouldReconnect = false;\n this.stopHeartbeat();\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.ws) {\n this.ws.close();\n }\n\n // Unregister health check when closing\n healthMonitor.unregisterHealthCheck(this.healthCheckName);\n }\n\n public getConnectionState(): string {\n if (!this.ws) return \"DISCONNECTED\";\n\n switch (this.ws.readyState) {\n case WebSocket.CONNECTING:\n return \"CONNECTING\";\n case WebSocket.OPEN:\n return \"CONNECTED\";\n case WebSocket.CLOSING:\n return \"CLOSING\";\n case WebSocket.CLOSED:\n return \"DISCONNECTED\";\n default:\n return \"UNKNOWN\";\n }\n }\n\n public getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n public getServiceCycles(): number {\n return this.serviceCycles;\n }\n\n public getServiceIndex(): number {\n return this.serviceIndex;\n }\n\n public getAllServices(): string[] {\n return Array.isArray(this.service) ? [...this.service] : [this.service];\n }\n\n public getCurrentService(): string {\n return Array.isArray(this.service) ? this.service[this.serviceIndex] : this.service;\n }\n\n public getMessageCount(): number {\n return this.messageCount;\n }\n\n public getLastMessageTime(): number {\n return this.lastMessageTime;\n }\n\n public getHealthCheckName(): string {\n return this.healthCheckName;\n }\n}\n","import { Logger } from \"./logger\";\n\nexport interface HealthStatus {\n healthy: boolean;\n timestamp: number;\n checks: Record<string, boolean>;\n metrics: Record<string, number>;\n details?: Record<string, unknown>;\n}\n\nexport interface HealthCheckOptions {\n interval?: number; // milliseconds\n timeout?: number; // milliseconds\n retries?: number;\n}\n\n/**\n * Health monitoring system for bot components.\n * Provides health checks and basic metrics collection.\n */\nexport class HealthMonitor {\n private checks = new Map<string, () => Promise<boolean>>();\n private metrics = new Map<string, number>();\n private lastCheckResults = new Map<string, boolean>();\n private checkInterval: NodeJS.Timeout | null = null;\n private options: Required<HealthCheckOptions>;\n\n constructor(options: HealthCheckOptions = {}) {\n this.options = {\n interval: options.interval || 30000, // 30 seconds\n timeout: options.timeout || 5000, // 5 seconds\n retries: options.retries || 2,\n };\n }\n\n /**\n * Register a health check function.\n * @param name - Unique name for the health check\n * @param checkFn - Function that returns true if healthy\n */\n registerHealthCheck(name: string, checkFn: () => Promise<boolean>) {\n this.checks.set(name, checkFn);\n Logger.debug(`Registered health check: ${name}`);\n }\n\n /**\n * Remove a health check.\n * @param name - Name of the health check to remove\n */\n unregisterHealthCheck(name: string) {\n this.checks.delete(name);\n this.lastCheckResults.delete(name);\n Logger.debug(`Unregistered health check: ${name}`);\n }\n\n /**\n * Set a metric value.\n * @param name - Metric name\n * @param value - Metric value\n */\n setMetric(name: string, value: number) {\n this.metrics.set(name, value);\n }\n\n /**\n * Increment a counter metric.\n * @param name - Metric name\n * @param increment - Value to add (default: 1)\n */\n incrementMetric(name: string, increment = 1) {\n const current = this.metrics.get(name) || 0;\n this.metrics.set(name, current + increment);\n }\n\n /**\n * Get current metric value.\n * @param name - Metric name\n * @returns Current value or 0 if not found\n */\n getMetric(name: string): number {\n return this.metrics.get(name) || 0;\n }\n\n /**\n * Get all current metrics.\n * @returns Object with all metrics\n */\n getAllMetrics(): Record<string, number> {\n return Object.fromEntries(this.metrics);\n }\n\n /**\n * Run a single health check with timeout and retries.\n * @private\n */\n private async runHealthCheck(name: string, checkFn: () => Promise<boolean>): Promise<boolean> {\n for (let attempt = 0; attempt <= this.options.retries; attempt++) {\n try {\n const result = await this.withTimeout(checkFn(), this.options.timeout);\n if (result) {\n return true;\n }\n } catch (error) {\n Logger.debug(\n `Health check \"${name}\" failed (attempt ${attempt + 1}/${this.options.retries + 1}):`,\n { error: error.message }\n );\n }\n }\n return false;\n }\n\n /**\n * Wrap a promise with a timeout.\n * @private\n */\n private withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs)\n ),\n ]);\n }\n\n /**\n * Run all health checks and return the current health status.\n */\n async getHealthStatus(): Promise<HealthStatus> {\n const timestamp = Date.now();\n const checkResults: Record<string, boolean> = {};\n const details: Record<string, unknown> = {};\n\n // Run all health checks\n const checkPromises = Array.from(this.checks.entries()).map(async ([name, checkFn]) => {\n const result = await this.runHealthCheck(name, checkFn);\n checkResults[name] = result;\n this.lastCheckResults.set(name, result);\n\n if (!result) {\n details[`${name}_last_failure`] = new Date().toISOString();\n }\n\n return result;\n });\n\n await Promise.allSettled(checkPromises);\n\n // Determine overall health\n const healthy = Object.values(checkResults).every(result => result);\n\n // Get current metrics\n const metrics = this.getAllMetrics();\n\n return {\n healthy,\n timestamp,\n checks: checkResults,\n metrics,\n details,\n };\n }\n\n /**\n * Start periodic health monitoring.\n */\n start() {\n if (this.checkInterval) {\n this.stop();\n }\n\n Logger.info(`Starting health monitor with ${this.options.interval}ms interval`);\n\n this.checkInterval = setInterval(async () => {\n try {\n const status = await this.getHealthStatus();\n\n if (!status.healthy) {\n const failedChecks = Object.entries(status.checks)\n .filter(([, healthy]) => !healthy)\n .map(([name]) => name);\n\n Logger.warn(`Health check failed`, {\n operation: \"health_check\",\n failed_checks: failedChecks,\n metrics: status.metrics,\n });\n } else {\n Logger.debug(\"Health check passed\", {\n operation: \"health_check\",\n metrics: status.metrics,\n });\n }\n } catch (error) {\n Logger.error(\"Error during health check:\", { error: error.message });\n }\n }, this.options.interval);\n }\n\n /**\n * Stop periodic health monitoring.\n */\n stop() {\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n Logger.info(\"Stopped health monitor\");\n }\n }\n\n /**\n * Get a summary of the last health check results.\n */\n getLastCheckSummary(): Record<string, boolean> {\n return Object.fromEntries(this.lastCheckResults);\n }\n}\n\n// Global health monitor instance\nexport const healthMonitor = new HealthMonitor();\n","import WebSocket from \"ws\";\nimport { WebSocketClient } from \"./websocketClient\";\nimport { Logger } from \"./logger\";\n\n/**\n * Represents a subscription to a Jetstream feed over WebSocket.\n *\n * This class extends `WebSocketClient` to automatically handle reconnections and heartbeats.\n * It invokes a provided callback function whenever a message is received from the Jetstream server.\n */\nexport class JetstreamSubscription extends WebSocketClient {\n /**\n * Creates a new `JetstreamSubscription`.\n *\n * @param service - The URL(-Array) of the Jetstream server(s) to connect to.\n * @param interval - The interval (in milliseconds) for reconnect attempts.\n * @param onMessageCallback - An optional callback function that is invoked whenever a message is received from the server.\n */\n constructor(\n service: string | string[],\n public interval: number,\n private onMessageCallback?: (data: WebSocket.Data) => void\n ) {\n super({ service, reconnectInterval: interval });\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n * Logs a message indicating that the connection to the Jetstream server has been established.\n */\n protected onOpen() {\n Logger.info(\"Connected to Jetstream server.\");\n super.onOpen();\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * If an `onMessageCallback` was provided, it is invoked with the received data.\n *\n * @param data - The data received from the Jetstream server.\n */\n protected onMessage(data: WebSocket.Data) {\n if (this.onMessageCallback) {\n this.onMessageCallback(data);\n }\n }\n\n /**\n * Called when a WebSocket error occurs.\n * Logs the error message indicating that Jetstream encountered an error.\n *\n * @param error - The error that occurred.\n */\n protected onError(error: Error) {\n Logger.error(\"Jetstream encountered an error:\", error);\n super.onError(error);\n }\n\n /**\n * Called when the WebSocket connection is closed.\n * Logs a message indicating that the Jetstream connection has closed.\n */\n protected onClose() {\n Logger.info(\"Jetstream connection closed.\");\n super.onClose();\n }\n}\n","/**\n * Returns the given string if it is defined; otherwise returns `undefined`.\n *\n * @param val - The optional string value to check.\n * @returns The given string if defined, or `undefined` if `val` is falsy.\n */\nexport const maybeStr = (val?: string): string | undefined => {\n if (!val) return undefined;\n return val;\n};\n\n/**\n * Parses the given string as an integer if it is defined and a valid integer; otherwise returns `undefined`.\n *\n * @param val - The optional string value to parse.\n * @returns The parsed integer if successful, or `undefined` if the string is falsy or not a valid integer.\n */\nexport const maybeInt = (val?: string): number | undefined => {\n if (!val) return undefined;\n const int = parseInt(val, 10);\n if (isNaN(int)) return undefined;\n return int;\n};\n","import WebSocket from \"ws\";\nimport { Post } from \"../types/post\";\nimport { WebsocketMessage } from \"../types/message\";\n/**\n * Converts a raw WebSocket message into a `FeedEntry` object, if possible.\n *\n * This function checks if the incoming WebSocket data is structured like a feed commit message\n * with the required properties for a created post. If the data matches the expected shape,\n * it extracts and returns a `FeedEntry` object. Otherwise, it returns `null`.\n *\n * @param data - The raw WebSocket data.\n * @returns A `FeedEntry` object if the data represents a newly created post, otherwise `null`.\n */\nexport function websocketToFeedEntry(data: WebSocket.Data): Post | null {\n const message = data as WebsocketMessage;\n if (\n !message.commit ||\n !message.commit.record ||\n !message.commit.record[\"$type\"] ||\n !message.did ||\n !message.commit.cid ||\n !message.commit.rkey ||\n message.commit.operation !== \"create\"\n ) {\n return null;\n }\n const messageUri = `at://${message.did}/${message.commit.record[\"$type\"]}/${message.commit.rkey}`;\n return {\n cid: message.commit.cid,\n uri: messageUri,\n authorDid: message.did,\n text: message.commit.record.text,\n rootCid: message.commit.record.reply?.root.cid ?? message.commit.cid,\n rootUri: message.commit.record.reply?.root.uri ?? messageUri,\n };\n}\n"],"mappings":"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","names":["LogLevel","WebSocket"]}
1
+
{"version":3,"sources":["../src/index.ts","../src/utils/logger.ts","../src/bots/baseBotAgent.ts","../src/bots/actionBot.ts","../src/bots/cronBot.ts","../src/bots/keywordBot.ts","../src/utils/websocketClient.ts","../src/utils/healthCheck.ts","../src/utils/jetstreamSubscription.ts","../src/utils/strings.ts","../src/utils/wsToFeed.ts"],"sourcesContent":["export * from \"./types/bot\";\nexport * from \"./types/message\";\nexport * from \"./types/post\";\nexport * from \"./bots/actionBot\";\nexport * from \"./bots/baseBotAgent\";\nexport * from \"./bots/cronBot\";\nexport * from \"./bots/keywordBot\";\nexport * from \"./utils/jetstreamSubscription\";\nexport * from \"./utils/logger\";\nexport * from \"./utils/strings\";\nexport * from \"./utils/websocketClient\";\nexport * from \"./utils/wsToFeed\";\nexport * from \"./utils/healthCheck\";\n","export enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n correlationId?: string;\n botId?: string;\n operation?: string;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * A performance-optimized logging utility class providing static methods for various log levels.\n * Each log message is prefixed with a timestamp and log level.\n * Supports conditional logging based on log levels and configurable timezone.\n */\nexport class Logger {\n private static logLevel: LogLevel = LogLevel.INFO;\n private static timezone: string = \"Europe/Vienna\";\n private static correlationId: string | null = null;\n\n /**\n * Generate a new correlation ID for tracking related operations.\n */\n static generateCorrelationId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Set the correlation ID for subsequent log entries.\n * @param id - The correlation ID to use, or null to generate a new one\n */\n static setCorrelationId(id?: string | null) {\n this.correlationId = id || this.generateCorrelationId();\n }\n\n /**\n * Get the current correlation ID.\n */\n static getCorrelationId(): string | null {\n return this.correlationId;\n }\n\n /**\n * Clear the current correlation ID.\n */\n static clearCorrelationId() {\n this.correlationId = null;\n }\n\n /**\n * Set the minimum log level. Messages below this level will not be logged.\n * @param level - The minimum log level\n */\n static setLogLevel(level: LogLevel) {\n this.logLevel = level;\n }\n\n /**\n * Set the timezone for log timestamps.\n * @param timezone - The timezone string (e.g., \"Europe/Vienna\", \"UTC\")\n */\n static setTimezone(timezone: string) {\n this.timezone = timezone;\n }\n\n /**\n * Get the current log level.\n */\n static getLogLevel(): LogLevel {\n return this.logLevel;\n }\n\n /**\n * Generate a formatted timestamp string.\n * @private\n */\n private static getTimestamp(): string {\n return new Date().toLocaleString(\"de-DE\", { timeZone: this.timezone });\n }\n\n /**\n * Internal logging method that checks log level before processing.\n * @private\n */\n private static log(\n level: LogLevel,\n levelName: string,\n message: string,\n context?: LogContext | object | string,\n logFn = console.log\n ) {\n if (level < this.logLevel) {\n return; // Skip logging if below threshold\n }\n\n const timestamp = this.getTimestamp();\n let formattedMessage = `${timestamp} [${levelName}]`;\n\n // Add correlation ID if available\n if (this.correlationId) {\n formattedMessage += ` [${this.correlationId}]`;\n }\n\n // Add context correlation ID if provided and different from global one\n if (\n context &&\n typeof context === \"object\" &&\n \"correlationId\" in context &&\n context.correlationId &&\n context.correlationId !== this.correlationId\n ) {\n formattedMessage += ` [${context.correlationId}]`;\n }\n\n formattedMessage += `: ${message}`;\n\n if (context) {\n // Create structured log entry for objects\n if (typeof context === \"object\") {\n const logEntry = {\n timestamp: new Date().toISOString(),\n level: levelName,\n message,\n correlationId: this.correlationId,\n ...context,\n };\n logFn(formattedMessage, logEntry);\n } else {\n logFn(formattedMessage, context);\n }\n } else {\n logFn(formattedMessage);\n }\n }\n /**\n * Logs an informational message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static info(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.INFO, \"INFO\", message, context, console.info);\n }\n\n /**\n * Logs a warning message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static warn(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.WARN, \"WARNING\", message, context, console.warn);\n }\n\n /**\n * Logs an error message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static error(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.ERROR, \"ERROR\", message, context, console.error);\n }\n\n /**\n * Logs a debug message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static debug(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.DEBUG, \"DEBUG\", message, context, console.debug);\n }\n\n /**\n * Log operation start with timing.\n * @param operation - The operation name\n * @param context - Additional context\n */\n static startOperation(operation: string, context?: LogContext): string {\n const correlationId = context?.correlationId || this.generateCorrelationId();\n this.setCorrelationId(correlationId);\n\n this.info(`Starting operation: ${operation}`, {\n operation,\n correlationId,\n ...context,\n });\n\n return correlationId;\n }\n\n /**\n * Log operation completion with timing.\n * @param operation - The operation name\n * @param startTime - The start time from Date.now()\n * @param context - Additional context\n */\n static endOperation(operation: string, startTime: number, context?: LogContext) {\n const duration = Date.now() - startTime;\n\n this.info(`Completed operation: ${operation}`, {\n operation,\n duration: `${duration}ms`,\n ...context,\n });\n }\n}\n","import { AtpAgent, AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { Bot } from \"../types/bot\";\n\n/**\n * Base class for all bot agents with common functionality.\n * Provides correlation tracking and structured logging capabilities.\n */\nexport abstract class BotAgent extends AtpAgent {\n protected currentCorrelationId: string | null = null;\n protected operationStartTime: number | null = null;\n\n constructor(\n public opts: AtpAgentOptions,\n protected bot: Bot\n ) {\n super(opts);\n }\n\n /**\n * Start tracking an operation with correlation ID and timing.\n * @protected\n */\n protected startOperationTracking(): void {\n this.currentCorrelationId = Logger.generateCorrelationId();\n this.operationStartTime = Date.now();\n }\n\n /**\n * Clear operation tracking state.\n * @protected\n */\n protected clearOperationTracking(): void {\n this.currentCorrelationId = null;\n this.operationStartTime = null;\n }\n\n /**\n * Get the bot identifier for logging purposes.\n * @protected\n */\n protected getBotId(): string {\n return this.bot.username || this.bot.identifier;\n }\n\n /**\n * Log a message with correlation ID during bot execution.\n * Call this from within your bot methods to log with proper correlation tracking.\n */\n logAction(\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n additionalContext?: Record<string, unknown>\n ): void {\n const logContext: Record<string, unknown> = {\n botId: this.getBotId(),\n ...additionalContext,\n };\n\n if (this.currentCorrelationId && this.operationStartTime) {\n logContext.correlationId = this.currentCorrelationId;\n logContext.operation = this.getOperationName();\n logContext.duration = `${Date.now() - this.operationStartTime}ms`;\n }\n\n switch (level) {\n case \"info\":\n Logger.info(message, logContext);\n break;\n case \"warn\":\n Logger.warn(message, logContext);\n break;\n case \"error\":\n Logger.error(message, logContext);\n break;\n }\n }\n\n /**\n * Get the operation name for logging. Override in subclasses.\n * @protected\n */\n protected abstract getOperationName(): string;\n}\n\n/**\n * Generic bot initialization function that handles common setup.\n */\nexport async function initializeBotAgent<T extends BotAgent>(\n botType: string,\n bot: Bot,\n createAgent: (opts: AtpAgentOptions, bot: Bot) => T\n): Promise<T | null> {\n const botId = bot.username ?? bot.identifier;\n const correlationId = Logger.startOperation(`initialize${botType}`, { botId });\n const startTime = Date.now();\n\n const agent = createAgent({ service: bot.service }, bot);\n\n try {\n Logger.info(`Initializing ${botType.toLowerCase()}`, { correlationId, botId });\n\n const login = await agent.login({\n identifier: bot.identifier,\n password: bot.password!,\n });\n\n if (!login.success) {\n Logger.warn(`${botType} login failed`, { correlationId, botId });\n return null;\n }\n\n Logger.endOperation(`initialize${botType}`, startTime, { correlationId, botId });\n return agent;\n } catch (error) {\n Logger.error(`Failed to initialize ${botType.toLowerCase()}`, {\n correlationId,\n botId,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n });\n return null;\n }\n}\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { ActionBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class ActionBotAgent extends BotAgent {\n public actionBot: ActionBot;\n\n constructor(opts: AtpAgentOptions, actionBot: ActionBot) {\n super(opts, actionBot);\n this.actionBot = actionBot;\n }\n\n async doAction(params?: unknown): Promise<void> {\n // Start operation tracking but don't log yet\n this.startOperationTracking();\n\n try {\n await this.actionBot.action(this, params);\n } catch (error) {\n Logger.error(\"Action bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"actionBot.doAction\";\n }\n}\n\nexport const useActionBotAgent = async (actionBot: ActionBot): Promise<ActionBotAgent | null> => {\n return initializeBotAgent(\n \"ActionBot\",\n actionBot,\n (opts, bot) => new ActionBotAgent(opts, bot as ActionBot)\n );\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { CronJob } from \"cron\";\nimport { Logger } from \"../utils/logger\";\nimport type { CronBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class CronBotAgent extends BotAgent {\n public job: CronJob;\n public cronBot: CronBot;\n\n constructor(opts: AtpAgentOptions, cronBot: CronBot) {\n super(opts, cronBot);\n this.cronBot = cronBot;\n\n this.job = new CronJob(\n cronBot.cronJob.scheduleExpression,\n async () => {\n // Start operation tracking for cron execution\n this.startOperationTracking();\n\n try {\n await cronBot.action(this);\n } catch (error) {\n Logger.error(\"Cron bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"cronBot.action\",\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n },\n cronBot.cronJob.callback,\n false,\n cronBot.cronJob.timeZone\n );\n }\n\n protected getOperationName(): string {\n return \"cronBot.action\";\n }\n}\n\nexport const useCronBotAgent = async (cronBot: CronBot): Promise<CronBotAgent | null> => {\n const agent = await initializeBotAgent(\n \"CronBot\",\n cronBot,\n (opts, bot) => new CronBotAgent(opts, bot as CronBot)\n );\n\n // Start the cron job after successful initialization\n if (agent) {\n agent.job.start();\n }\n\n return agent;\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport type { BotReply, KeywordBot } from \"../types/bot\";\nimport type { Post, UriCid } from \"../types/post\";\nimport { Logger } from \"../utils/logger\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class KeywordBotAgent extends BotAgent {\n public keywordBot: KeywordBot;\n\n constructor(opts: AtpAgentOptions, keywordBot: KeywordBot) {\n super(opts, keywordBot);\n this.keywordBot = keywordBot;\n }\n\n async likeAndReplyIfFollower(post: Post): Promise<void> {\n if (post.authorDid === this.assertDid) {\n return;\n }\n\n const replies = filterBotReplies(post.text, this.keywordBot.replies);\n if (replies.length < 1) {\n return;\n }\n\n // Start operation tracking when actual work begins\n this.startOperationTracking();\n\n try {\n const actorProfile = await this.getProfile({ actor: post.authorDid });\n\n if (actorProfile.success) {\n if (!actorProfile.data.viewer?.followedBy) {\n return;\n }\n\n const replyCfg = replies[Math.floor(Math.random() * replies.length)];\n const message = replyCfg.messages[Math.floor(Math.random() * replyCfg.messages.length)];\n const reply = buildReplyToPost(\n { uri: post.rootUri, cid: post.rootCid },\n { uri: post.uri, cid: post.cid },\n message\n );\n\n await Promise.all([this.like(post.uri, post.cid), this.post(reply)]);\n\n this.logAction(\"info\", `Replied to post: ${post.uri}`, {\n postUri: post.uri,\n authorDid: post.authorDid,\n keyword: replyCfg.keyword,\n message: message,\n });\n }\n } catch (error) {\n Logger.error(\"Keyword bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"keywordBot.likeAndReplyIfFollower\",\n error: error instanceof Error ? error.message : String(error),\n postUri: post.uri,\n authorDid: post.authorDid,\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"keywordBot.likeAndReplyIfFollower\";\n }\n}\n\nexport function buildReplyToPost(root: UriCid, parent: UriCid, message: string) {\n return {\n $type: \"app.bsky.feed.post\" as const,\n text: message,\n reply: {\n root: root,\n parent: parent,\n },\n };\n}\n\nexport function filterBotReplies(text: string, botReplies: BotReply[]) {\n // Cache the lowercased text to avoid multiple toLowerCase() calls\n const lowerText = text.toLowerCase();\n\n return botReplies.filter(reply => {\n // Use cached lowercase comparison\n const keyword = reply.keyword.toLowerCase();\n if (!lowerText.includes(keyword)) {\n return false;\n }\n\n // Early return if no exclusions\n if (!Array.isArray(reply.exclude) || reply.exclude.length === 0) {\n return true;\n }\n\n // Use some() for early exit on first match\n const hasExcludedWord = reply.exclude.some(excludeWord =>\n lowerText.includes(excludeWord.toLowerCase())\n );\n\n return !hasExcludedWord;\n });\n}\n\nexport const useKeywordBotAgent = async (\n keywordBot: KeywordBot\n): Promise<KeywordBotAgent | null> => {\n return initializeBotAgent(\n \"KeywordBot\",\n keywordBot,\n (opts, bot) => new KeywordBotAgent(opts, bot as KeywordBot)\n );\n};\n","import WebSocket from \"ws\";\nimport { Logger } from \"./logger\";\nimport { healthMonitor } from \"./healthCheck\";\n\ninterface WebSocketClientOptions {\n /** The URL of the WebSocket server to connect to. */\n service: string | string[];\n /** The interval in milliseconds to wait before attempting to reconnect when the connection closes. Default is 5000ms. */\n reconnectInterval?: number;\n /** The interval in milliseconds for sending ping messages (heartbeats) to keep the connection alive. Default is 10000ms. */\n pingInterval?: number;\n /** Maximum number of consecutive reconnection attempts per service. Default is 3. */\n maxReconnectAttempts?: number;\n /** Maximum delay between reconnection attempts in milliseconds. Default is 30000ms (30 seconds). */\n maxReconnectDelay?: number;\n /** Exponential backoff factor for reconnection delays. Default is 1.5. */\n backoffFactor?: number;\n /** Maximum number of attempts to cycle through all services before giving up. Default is 2. */\n maxServiceCycles?: number;\n}\n\n/**\n * A WebSocket client that automatically attempts to reconnect upon disconnection\n * and periodically sends ping messages (heartbeats) to ensure the connection remains alive.\n *\n * Extend this class and override the protected `onOpen`, `onMessage`, `onError`, and `onClose` methods\n * to implement custom handling of WebSocket events.\n */\nexport class WebSocketClient {\n private service: string | string[];\n private reconnectInterval: number;\n private pingInterval: number;\n private ws: WebSocket | null = null;\n private pingTimeout: NodeJS.Timeout | null = null;\n private serviceIndex = 0;\n private reconnectAttempts = 0;\n private serviceCycles = 0;\n private maxReconnectAttempts: number;\n private maxServiceCycles: number;\n private maxReconnectDelay: number;\n private backoffFactor: number;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n private isConnecting = false;\n private shouldReconnect = true;\n private messageCount = 0;\n private lastMessageTime = 0;\n private healthCheckName: string;\n\n /**\n * Creates a new instance of `WebSocketClient`.\n *\n * @param options - Configuration options for the WebSocket client, including URL, reconnect interval, and ping interval.\n */\n constructor(options: WebSocketClientOptions) {\n this.service = options.service;\n this.reconnectInterval = options.reconnectInterval || 5000;\n this.pingInterval = options.pingInterval || 10000;\n this.maxReconnectAttempts = options.maxReconnectAttempts || 3;\n this.maxServiceCycles = options.maxServiceCycles || 2;\n this.maxReconnectDelay = options.maxReconnectDelay || 30000;\n this.backoffFactor = options.backoffFactor || 1.5;\n\n // Generate unique health check name\n this.healthCheckName = `websocket_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;\n\n // Register health check\n healthMonitor.registerHealthCheck(this.healthCheckName, async () => {\n return this.getConnectionState() === \"CONNECTED\";\n });\n\n // Initialize metrics\n healthMonitor.setMetric(`${this.healthCheckName}_messages_received`, 0);\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, 0);\n\n this.run();\n }\n\n /**\n * Initiates a WebSocket connection to the specified URL.\n *\n * This method sets up event listeners for `open`, `message`, `error`, and `close` events.\n * When the connection opens, it starts the heartbeat mechanism.\n * On close, it attempts to reconnect after a specified interval.\n */\n private run() {\n if (this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n const currentService = Array.isArray(this.service)\n ? this.service[this.serviceIndex]\n : this.service;\n\n Logger.info(`Attempting to connect to WebSocket: ${currentService}`);\n this.ws = new WebSocket(currentService);\n\n this.ws.on(\"open\", () => {\n Logger.info(\"WebSocket connected successfully\", {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n });\n this.isConnecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n this.serviceCycles = 0; // Reset cycles on successful connection\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n this.startHeartbeat();\n this.onOpen();\n });\n\n this.ws.on(\"message\", (data: WebSocket.Data) => {\n this.messageCount++;\n this.lastMessageTime = Date.now();\n healthMonitor.incrementMetric(`${this.healthCheckName}_messages_received`);\n this.onMessage(data);\n });\n\n this.ws.on(\"error\", error => {\n Logger.error(\"WebSocket error:\", error);\n this.isConnecting = false;\n this.onError(error);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n Logger.info(`WebSocket disconnected. Code: ${code}, Reason: ${reason.toString()}`);\n this.isConnecting = false;\n this.stopHeartbeat();\n this.onClose();\n\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n }\n\n /**\n * Attempts to reconnect to the WebSocket server after the specified `reconnectInterval`.\n * It clears all event listeners on the old WebSocket and initiates a new connection.\n */\n private scheduleReconnect() {\n this.reconnectAttempts++;\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n\n // Check if we should try the next service\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.shouldTryNextService()) {\n this.moveToNextService();\n return; // Try next service immediately\n } else {\n Logger.error(\"All services exhausted after maximum cycles\", {\n totalServices: Array.isArray(this.service) ? this.service.length : 1,\n maxServiceCycles: this.maxServiceCycles,\n serviceCycles: this.serviceCycles,\n });\n return; // Give up entirely\n }\n }\n\n const delay = Math.min(\n this.reconnectInterval * Math.pow(this.backoffFactor, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n Logger.info(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} for service`,\n {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n delay: `${delay}ms`,\n }\n );\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n }\n\n this.reconnectTimeout = setTimeout(() => {\n this.cleanup();\n this.run();\n }, delay);\n }\n\n /**\n * Check if we should try the next service in the array.\n */\n private shouldTryNextService(): boolean {\n if (!Array.isArray(this.service)) {\n return false; // Single service, can't switch\n }\n\n return this.serviceCycles < this.maxServiceCycles;\n }\n\n /**\n * Move to the next service in the array and reset reconnection attempts.\n */\n private moveToNextService() {\n if (!Array.isArray(this.service)) {\n return;\n }\n\n const previousIndex = this.serviceIndex;\n this.serviceIndex = (this.serviceIndex + 1) % this.service.length;\n\n // If we've gone through all services once, increment the cycle counter\n if (this.serviceIndex === 0) {\n this.serviceCycles++;\n }\n\n this.reconnectAttempts = 0; // Reset attempts for the new service\n\n Logger.info(\"Switching to next service\", {\n previousService: this.service[previousIndex],\n previousIndex,\n newService: this.getCurrentService(),\n newIndex: this.serviceIndex,\n serviceCycle: this.serviceCycles,\n });\n\n // Try the new service immediately\n this.cleanup();\n this.run();\n }\n\n private cleanup() {\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close();\n }\n this.ws = null;\n }\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n }\n\n /**\n * Starts sending periodic ping messages to the server.\n *\n * This function uses `setInterval` to send a ping at the configured `pingInterval`.\n * If the WebSocket is not open, pings are not sent.\n */\n private startHeartbeat() {\n this.pingTimeout = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, this.pingInterval);\n }\n\n /**\n * Stops sending heartbeat pings by clearing the ping interval.\n */\n private stopHeartbeat() {\n if (this.pingTimeout) {\n clearInterval(this.pingTimeout);\n this.pingTimeout = null;\n }\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n *\n * Override this method in a subclass to implement custom logic on connection.\n */\n protected onOpen() {\n // Custom logic for connection open\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * @param data - The data received from the WebSocket server.\n *\n * Override this method in a subclass to implement custom message handling.\n */\n protected onMessage(_data: WebSocket.Data) {\n // Custom logic for handling received messages\n }\n\n /**\n * Called when a WebSocket error occurs.\n *\n * @param error - The error that occurred.\n *\n * Override this method in a subclass to implement custom error handling.\n * Note: Service switching is now handled in the reconnection logic, not here.\n */\n protected onError(_error: Error) {\n // Custom logic for handling errors - override in subclasses\n // Service switching is handled automatically in scheduleReconnect()\n }\n\n /**\n * Called when the WebSocket connection is closed.\n *\n * Override this method in a subclass to implement custom logic on disconnection.\n */\n protected onClose() {\n // Custom logic for handling connection close\n }\n\n /**\n * Sends data to the connected WebSocket server, if the connection is open.\n *\n * @param data - The data to send.\n */\n public send(data: string | Buffer | ArrayBuffer | Buffer[]) {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(data);\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n */\n public close() {\n this.shouldReconnect = false;\n this.stopHeartbeat();\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.ws) {\n this.ws.close();\n }\n\n // Unregister health check when closing\n healthMonitor.unregisterHealthCheck(this.healthCheckName);\n }\n\n public getConnectionState(): string {\n if (!this.ws) return \"DISCONNECTED\";\n\n switch (this.ws.readyState) {\n case WebSocket.CONNECTING:\n return \"CONNECTING\";\n case WebSocket.OPEN:\n return \"CONNECTED\";\n case WebSocket.CLOSING:\n return \"CLOSING\";\n case WebSocket.CLOSED:\n return \"DISCONNECTED\";\n default:\n return \"UNKNOWN\";\n }\n }\n\n public getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n public getServiceCycles(): number {\n return this.serviceCycles;\n }\n\n public getServiceIndex(): number {\n return this.serviceIndex;\n }\n\n public getAllServices(): string[] {\n return Array.isArray(this.service) ? [...this.service] : [this.service];\n }\n\n public getCurrentService(): string {\n return Array.isArray(this.service) ? this.service[this.serviceIndex] : this.service;\n }\n\n public getMessageCount(): number {\n return this.messageCount;\n }\n\n public getLastMessageTime(): number {\n return this.lastMessageTime;\n }\n\n public getHealthCheckName(): string {\n return this.healthCheckName;\n }\n}\n","import { Logger } from \"./logger\";\n\nexport interface HealthStatus {\n healthy: boolean;\n timestamp: number;\n checks: Record<string, boolean>;\n metrics: Record<string, number>;\n details?: Record<string, unknown>;\n}\n\nexport interface HealthCheckOptions {\n interval?: number; // milliseconds\n timeout?: number; // milliseconds\n retries?: number;\n}\n\n/**\n * Health monitoring system for bot components.\n * Provides health checks and basic metrics collection.\n */\nexport class HealthMonitor {\n private checks = new Map<string, () => Promise<boolean>>();\n private metrics = new Map<string, number>();\n private lastCheckResults = new Map<string, boolean>();\n private checkInterval: NodeJS.Timeout | null = null;\n private options: Required<HealthCheckOptions>;\n\n constructor(options: HealthCheckOptions = {}) {\n this.options = {\n interval: options.interval || 30000, // 30 seconds\n timeout: options.timeout || 5000, // 5 seconds\n retries: options.retries || 2,\n };\n }\n\n /**\n * Register a health check function.\n * @param name - Unique name for the health check\n * @param checkFn - Function that returns true if healthy\n */\n registerHealthCheck(name: string, checkFn: () => Promise<boolean>) {\n this.checks.set(name, checkFn);\n Logger.debug(`Registered health check: ${name}`);\n }\n\n /**\n * Remove a health check.\n * @param name - Name of the health check to remove\n */\n unregisterHealthCheck(name: string) {\n this.checks.delete(name);\n this.lastCheckResults.delete(name);\n Logger.debug(`Unregistered health check: ${name}`);\n }\n\n /**\n * Set a metric value.\n * @param name - Metric name\n * @param value - Metric value\n */\n setMetric(name: string, value: number) {\n this.metrics.set(name, value);\n }\n\n /**\n * Increment a counter metric.\n * @param name - Metric name\n * @param increment - Value to add (default: 1)\n */\n incrementMetric(name: string, increment = 1) {\n const current = this.metrics.get(name) || 0;\n this.metrics.set(name, current + increment);\n }\n\n /**\n * Get current metric value.\n * @param name - Metric name\n * @returns Current value or 0 if not found\n */\n getMetric(name: string): number {\n return this.metrics.get(name) || 0;\n }\n\n /**\n * Get all current metrics.\n * @returns Object with all metrics\n */\n getAllMetrics(): Record<string, number> {\n return Object.fromEntries(this.metrics);\n }\n\n /**\n * Run a single health check with timeout and retries.\n * @private\n */\n private async runHealthCheck(name: string, checkFn: () => Promise<boolean>): Promise<boolean> {\n for (let attempt = 0; attempt <= this.options.retries; attempt++) {\n try {\n const result = await this.withTimeout(checkFn(), this.options.timeout);\n if (result) {\n return true;\n }\n } catch (error) {\n Logger.debug(\n `Health check \"${name}\" failed (attempt ${attempt + 1}/${this.options.retries + 1}):`,\n { error: error.message }\n );\n }\n }\n return false;\n }\n\n /**\n * Wrap a promise with a timeout.\n * @private\n */\n private withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs)\n ),\n ]);\n }\n\n /**\n * Run all health checks and return the current health status.\n */\n async getHealthStatus(): Promise<HealthStatus> {\n const timestamp = Date.now();\n const checkResults: Record<string, boolean> = {};\n const details: Record<string, unknown> = {};\n\n // Run all health checks\n const checkPromises = Array.from(this.checks.entries()).map(async ([name, checkFn]) => {\n const result = await this.runHealthCheck(name, checkFn);\n checkResults[name] = result;\n this.lastCheckResults.set(name, result);\n\n if (!result) {\n details[`${name}_last_failure`] = new Date().toISOString();\n }\n\n return result;\n });\n\n await Promise.allSettled(checkPromises);\n\n // Determine overall health\n const healthy = Object.values(checkResults).every(result => result);\n\n // Get current metrics\n const metrics = this.getAllMetrics();\n\n return {\n healthy,\n timestamp,\n checks: checkResults,\n metrics,\n details,\n };\n }\n\n /**\n * Start periodic health monitoring.\n */\n start() {\n if (this.checkInterval) {\n this.stop();\n }\n\n Logger.info(`Starting health monitor with ${this.options.interval}ms interval`);\n\n this.checkInterval = setInterval(async () => {\n try {\n const status = await this.getHealthStatus();\n\n if (!status.healthy) {\n const failedChecks = Object.entries(status.checks)\n .filter(([, healthy]) => !healthy)\n .map(([name]) => name);\n\n Logger.warn(`Health check failed`, {\n operation: \"health_check\",\n failed_checks: failedChecks,\n metrics: status.metrics,\n });\n } else {\n Logger.debug(\"Health check passed\", {\n operation: \"health_check\",\n metrics: status.metrics,\n });\n }\n } catch (error) {\n Logger.error(\"Error during health check:\", { error: error.message });\n }\n }, this.options.interval);\n }\n\n /**\n * Stop periodic health monitoring.\n */\n stop() {\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n Logger.info(\"Stopped health monitor\");\n }\n }\n\n /**\n * Get a summary of the last health check results.\n */\n getLastCheckSummary(): Record<string, boolean> {\n return Object.fromEntries(this.lastCheckResults);\n }\n}\n\n// Global health monitor instance\nexport const healthMonitor = new HealthMonitor();\n","import WebSocket from \"ws\";\nimport { WebSocketClient } from \"./websocketClient\";\nimport { Logger } from \"./logger\";\n\n/**\n * Represents a subscription to a Jetstream feed over WebSocket.\n *\n * This class extends `WebSocketClient` to automatically handle reconnections and heartbeats.\n * It invokes a provided callback function whenever a message is received from the Jetstream server.\n */\nexport class JetstreamSubscription extends WebSocketClient {\n /**\n * Creates a new `JetstreamSubscription`.\n *\n * @param service - The URL(-Array) of the Jetstream server(s) to connect to.\n * @param interval - The interval (in milliseconds) for reconnect attempts.\n * @param onMessageCallback - An optional callback function that is invoked whenever a message is received from the server.\n */\n constructor(\n service: string | string[],\n public interval: number,\n private onMessageCallback?: (data: WebSocket.Data) => void\n ) {\n super({ service, reconnectInterval: interval });\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n * Logs a message indicating that the connection to the Jetstream server has been established.\n */\n protected onOpen() {\n Logger.info(\"Connected to Jetstream server.\");\n super.onOpen();\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * If an `onMessageCallback` was provided, it is invoked with the received data.\n *\n * @param data - The data received from the Jetstream server.\n */\n protected onMessage(data: WebSocket.Data) {\n if (this.onMessageCallback) {\n this.onMessageCallback(data);\n }\n }\n\n /**\n * Called when a WebSocket error occurs.\n * Logs the error message indicating that Jetstream encountered an error.\n *\n * @param error - The error that occurred.\n */\n protected onError(error: Error) {\n Logger.error(\"Jetstream encountered an error:\", error);\n super.onError(error);\n }\n\n /**\n * Called when the WebSocket connection is closed.\n * Logs a message indicating that the Jetstream connection has closed.\n */\n protected onClose() {\n Logger.info(\"Jetstream connection closed.\");\n super.onClose();\n }\n}\n","/**\n * Returns the given string if it is defined; otherwise returns `undefined`.\n *\n * @param val - The optional string value to check.\n * @returns The given string if defined, or `undefined` if `val` is falsy.\n */\nexport const maybeStr = (val?: string): string | undefined => {\n if (!val) return undefined;\n return val;\n};\n\n/**\n * Parses the given string as an integer if it is defined and a valid integer; otherwise returns `undefined`.\n *\n * @param val - The optional string value to parse.\n * @returns The parsed integer if successful, or `undefined` if the string is falsy or not a valid integer.\n */\nexport const maybeInt = (val?: string): number | undefined => {\n if (!val) return undefined;\n const int = parseInt(val, 10);\n if (isNaN(int)) return undefined;\n return int;\n};\n","import WebSocket from \"ws\";\nimport { Post } from \"../types/post\";\nimport { WebsocketMessage } from \"../types/message\";\n/**\n * Converts a raw WebSocket message into a `FeedEntry` object, if possible.\n *\n * This function checks if the incoming WebSocket data is structured like a feed commit message\n * with the required properties for a created post. If the data matches the expected shape,\n * it extracts and returns a `FeedEntry` object. Otherwise, it returns `null`.\n *\n * @param data - The raw WebSocket data.\n * @returns A `FeedEntry` object if the data represents a newly created post, otherwise `null`.\n */\nexport function websocketToFeedEntry(data: WebSocket.Data): Post | null {\n const message = JSON.parse(data.toString()) as WebsocketMessage;\n if (\n !message.commit ||\n !message.commit.record ||\n !message.commit.record[\"$type\"] ||\n !message.did ||\n !message.commit.cid ||\n !message.commit.rkey ||\n message.commit.operation !== \"create\"\n ) {\n return null;\n }\n const messageUri = `at://${message.did}/${message.commit.record[\"$type\"]}/${message.commit.rkey}`;\n return {\n cid: message.commit.cid,\n uri: messageUri,\n authorDid: message.did,\n text: message.commit.record.text,\n rootCid: message.commit.record.reply?.root.cid ?? message.commit.cid,\n rootUri: message.commit.record.reply?.root.uri ?? messageUri,\n };\n}\n"],"mappings":"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","names":["LogLevel","WebSocket"]}
+1
-1
dist/index.mjs
+1
-1
dist/index.mjs
···
991
991
// src/utils/wsToFeed.ts
992
992
function websocketToFeedEntry(data) {
993
993
var _a, _b, _c, _d;
994
-
const message = data;
994
+
const message = JSON.parse(data.toString());
995
995
if (!message.commit || !message.commit.record || !message.commit.record["$type"] || !message.did || !message.commit.cid || !message.commit.rkey || message.commit.operation !== "create") {
996
996
return null;
997
997
}
+1
-1
dist/index.mjs.map
+1
-1
dist/index.mjs.map
···
1
-
{"version":3,"sources":["../src/utils/logger.ts","../src/bots/baseBotAgent.ts","../src/bots/actionBot.ts","../src/bots/cronBot.ts","../src/bots/keywordBot.ts","../src/utils/websocketClient.ts","../src/utils/healthCheck.ts","../src/utils/jetstreamSubscription.ts","../src/utils/strings.ts","../src/utils/wsToFeed.ts"],"sourcesContent":["export enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n correlationId?: string;\n botId?: string;\n operation?: string;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * A performance-optimized logging utility class providing static methods for various log levels.\n * Each log message is prefixed with a timestamp and log level.\n * Supports conditional logging based on log levels and configurable timezone.\n */\nexport class Logger {\n private static logLevel: LogLevel = LogLevel.INFO;\n private static timezone: string = \"Europe/Vienna\";\n private static correlationId: string | null = null;\n\n /**\n * Generate a new correlation ID for tracking related operations.\n */\n static generateCorrelationId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Set the correlation ID for subsequent log entries.\n * @param id - The correlation ID to use, or null to generate a new one\n */\n static setCorrelationId(id?: string | null) {\n this.correlationId = id || this.generateCorrelationId();\n }\n\n /**\n * Get the current correlation ID.\n */\n static getCorrelationId(): string | null {\n return this.correlationId;\n }\n\n /**\n * Clear the current correlation ID.\n */\n static clearCorrelationId() {\n this.correlationId = null;\n }\n\n /**\n * Set the minimum log level. Messages below this level will not be logged.\n * @param level - The minimum log level\n */\n static setLogLevel(level: LogLevel) {\n this.logLevel = level;\n }\n\n /**\n * Set the timezone for log timestamps.\n * @param timezone - The timezone string (e.g., \"Europe/Vienna\", \"UTC\")\n */\n static setTimezone(timezone: string) {\n this.timezone = timezone;\n }\n\n /**\n * Get the current log level.\n */\n static getLogLevel(): LogLevel {\n return this.logLevel;\n }\n\n /**\n * Generate a formatted timestamp string.\n * @private\n */\n private static getTimestamp(): string {\n return new Date().toLocaleString(\"de-DE\", { timeZone: this.timezone });\n }\n\n /**\n * Internal logging method that checks log level before processing.\n * @private\n */\n private static log(\n level: LogLevel,\n levelName: string,\n message: string,\n context?: LogContext | object | string,\n logFn = console.log\n ) {\n if (level < this.logLevel) {\n return; // Skip logging if below threshold\n }\n\n const timestamp = this.getTimestamp();\n let formattedMessage = `${timestamp} [${levelName}]`;\n\n // Add correlation ID if available\n if (this.correlationId) {\n formattedMessage += ` [${this.correlationId}]`;\n }\n\n // Add context correlation ID if provided and different from global one\n if (\n context &&\n typeof context === \"object\" &&\n \"correlationId\" in context &&\n context.correlationId &&\n context.correlationId !== this.correlationId\n ) {\n formattedMessage += ` [${context.correlationId}]`;\n }\n\n formattedMessage += `: ${message}`;\n\n if (context) {\n // Create structured log entry for objects\n if (typeof context === \"object\") {\n const logEntry = {\n timestamp: new Date().toISOString(),\n level: levelName,\n message,\n correlationId: this.correlationId,\n ...context,\n };\n logFn(formattedMessage, logEntry);\n } else {\n logFn(formattedMessage, context);\n }\n } else {\n logFn(formattedMessage);\n }\n }\n /**\n * Logs an informational message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static info(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.INFO, \"INFO\", message, context, console.info);\n }\n\n /**\n * Logs a warning message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static warn(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.WARN, \"WARNING\", message, context, console.warn);\n }\n\n /**\n * Logs an error message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static error(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.ERROR, \"ERROR\", message, context, console.error);\n }\n\n /**\n * Logs a debug message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static debug(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.DEBUG, \"DEBUG\", message, context, console.debug);\n }\n\n /**\n * Log operation start with timing.\n * @param operation - The operation name\n * @param context - Additional context\n */\n static startOperation(operation: string, context?: LogContext): string {\n const correlationId = context?.correlationId || this.generateCorrelationId();\n this.setCorrelationId(correlationId);\n\n this.info(`Starting operation: ${operation}`, {\n operation,\n correlationId,\n ...context,\n });\n\n return correlationId;\n }\n\n /**\n * Log operation completion with timing.\n * @param operation - The operation name\n * @param startTime - The start time from Date.now()\n * @param context - Additional context\n */\n static endOperation(operation: string, startTime: number, context?: LogContext) {\n const duration = Date.now() - startTime;\n\n this.info(`Completed operation: ${operation}`, {\n operation,\n duration: `${duration}ms`,\n ...context,\n });\n }\n}\n","import { AtpAgent, AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { Bot } from \"../types/bot\";\n\n/**\n * Base class for all bot agents with common functionality.\n * Provides correlation tracking and structured logging capabilities.\n */\nexport abstract class BotAgent extends AtpAgent {\n protected currentCorrelationId: string | null = null;\n protected operationStartTime: number | null = null;\n\n constructor(\n public opts: AtpAgentOptions,\n protected bot: Bot\n ) {\n super(opts);\n }\n\n /**\n * Start tracking an operation with correlation ID and timing.\n * @protected\n */\n protected startOperationTracking(): void {\n this.currentCorrelationId = Logger.generateCorrelationId();\n this.operationStartTime = Date.now();\n }\n\n /**\n * Clear operation tracking state.\n * @protected\n */\n protected clearOperationTracking(): void {\n this.currentCorrelationId = null;\n this.operationStartTime = null;\n }\n\n /**\n * Get the bot identifier for logging purposes.\n * @protected\n */\n protected getBotId(): string {\n return this.bot.username || this.bot.identifier;\n }\n\n /**\n * Log a message with correlation ID during bot execution.\n * Call this from within your bot methods to log with proper correlation tracking.\n */\n logAction(\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n additionalContext?: Record<string, unknown>\n ): void {\n const logContext: Record<string, unknown> = {\n botId: this.getBotId(),\n ...additionalContext,\n };\n\n if (this.currentCorrelationId && this.operationStartTime) {\n logContext.correlationId = this.currentCorrelationId;\n logContext.operation = this.getOperationName();\n logContext.duration = `${Date.now() - this.operationStartTime}ms`;\n }\n\n switch (level) {\n case \"info\":\n Logger.info(message, logContext);\n break;\n case \"warn\":\n Logger.warn(message, logContext);\n break;\n case \"error\":\n Logger.error(message, logContext);\n break;\n }\n }\n\n /**\n * Get the operation name for logging. Override in subclasses.\n * @protected\n */\n protected abstract getOperationName(): string;\n}\n\n/**\n * Generic bot initialization function that handles common setup.\n */\nexport async function initializeBotAgent<T extends BotAgent>(\n botType: string,\n bot: Bot,\n createAgent: (opts: AtpAgentOptions, bot: Bot) => T\n): Promise<T | null> {\n const botId = bot.username ?? bot.identifier;\n const correlationId = Logger.startOperation(`initialize${botType}`, { botId });\n const startTime = Date.now();\n\n const agent = createAgent({ service: bot.service }, bot);\n\n try {\n Logger.info(`Initializing ${botType.toLowerCase()}`, { correlationId, botId });\n\n const login = await agent.login({\n identifier: bot.identifier,\n password: bot.password!,\n });\n\n if (!login.success) {\n Logger.warn(`${botType} login failed`, { correlationId, botId });\n return null;\n }\n\n Logger.endOperation(`initialize${botType}`, startTime, { correlationId, botId });\n return agent;\n } catch (error) {\n Logger.error(`Failed to initialize ${botType.toLowerCase()}`, {\n correlationId,\n botId,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n });\n return null;\n }\n}\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { ActionBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class ActionBotAgent extends BotAgent {\n public actionBot: ActionBot;\n\n constructor(opts: AtpAgentOptions, actionBot: ActionBot) {\n super(opts, actionBot);\n this.actionBot = actionBot;\n }\n\n async doAction(params?: unknown): Promise<void> {\n // Start operation tracking but don't log yet\n this.startOperationTracking();\n\n try {\n await this.actionBot.action(this, params);\n } catch (error) {\n Logger.error(\"Action bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"actionBot.doAction\";\n }\n}\n\nexport const useActionBotAgent = async (actionBot: ActionBot): Promise<ActionBotAgent | null> => {\n return initializeBotAgent(\n \"ActionBot\",\n actionBot,\n (opts, bot) => new ActionBotAgent(opts, bot as ActionBot)\n );\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { CronJob } from \"cron\";\nimport { Logger } from \"../utils/logger\";\nimport type { CronBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class CronBotAgent extends BotAgent {\n public job: CronJob;\n public cronBot: CronBot;\n\n constructor(opts: AtpAgentOptions, cronBot: CronBot) {\n super(opts, cronBot);\n this.cronBot = cronBot;\n\n this.job = new CronJob(\n cronBot.cronJob.scheduleExpression,\n async () => {\n // Start operation tracking for cron execution\n this.startOperationTracking();\n\n try {\n await cronBot.action(this);\n } catch (error) {\n Logger.error(\"Cron bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"cronBot.action\",\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n },\n cronBot.cronJob.callback,\n false,\n cronBot.cronJob.timeZone\n );\n }\n\n protected getOperationName(): string {\n return \"cronBot.action\";\n }\n}\n\nexport const useCronBotAgent = async (cronBot: CronBot): Promise<CronBotAgent | null> => {\n const agent = await initializeBotAgent(\n \"CronBot\",\n cronBot,\n (opts, bot) => new CronBotAgent(opts, bot as CronBot)\n );\n\n // Start the cron job after successful initialization\n if (agent) {\n agent.job.start();\n }\n\n return agent;\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport type { BotReply, KeywordBot } from \"../types/bot\";\nimport type { Post, UriCid } from \"../types/post\";\nimport { Logger } from \"../utils/logger\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class KeywordBotAgent extends BotAgent {\n public keywordBot: KeywordBot;\n\n constructor(opts: AtpAgentOptions, keywordBot: KeywordBot) {\n super(opts, keywordBot);\n this.keywordBot = keywordBot;\n }\n\n async likeAndReplyIfFollower(post: Post): Promise<void> {\n if (post.authorDid === this.assertDid) {\n return;\n }\n\n const replies = filterBotReplies(post.text, this.keywordBot.replies);\n if (replies.length < 1) {\n return;\n }\n\n // Start operation tracking when actual work begins\n this.startOperationTracking();\n\n try {\n const actorProfile = await this.getProfile({ actor: post.authorDid });\n\n if (actorProfile.success) {\n if (!actorProfile.data.viewer?.followedBy) {\n return;\n }\n\n const replyCfg = replies[Math.floor(Math.random() * replies.length)];\n const message = replyCfg.messages[Math.floor(Math.random() * replyCfg.messages.length)];\n const reply = buildReplyToPost(\n { uri: post.rootUri, cid: post.rootCid },\n { uri: post.uri, cid: post.cid },\n message\n );\n\n await Promise.all([this.like(post.uri, post.cid), this.post(reply)]);\n\n this.logAction(\"info\", `Replied to post: ${post.uri}`, {\n postUri: post.uri,\n authorDid: post.authorDid,\n keyword: replyCfg.keyword,\n message: message,\n });\n }\n } catch (error) {\n Logger.error(\"Keyword bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"keywordBot.likeAndReplyIfFollower\",\n error: error instanceof Error ? error.message : String(error),\n postUri: post.uri,\n authorDid: post.authorDid,\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"keywordBot.likeAndReplyIfFollower\";\n }\n}\n\nexport function buildReplyToPost(root: UriCid, parent: UriCid, message: string) {\n return {\n $type: \"app.bsky.feed.post\" as const,\n text: message,\n reply: {\n root: root,\n parent: parent,\n },\n };\n}\n\nexport function filterBotReplies(text: string, botReplies: BotReply[]) {\n // Cache the lowercased text to avoid multiple toLowerCase() calls\n const lowerText = text.toLowerCase();\n\n return botReplies.filter(reply => {\n // Use cached lowercase comparison\n const keyword = reply.keyword.toLowerCase();\n if (!lowerText.includes(keyword)) {\n return false;\n }\n\n // Early return if no exclusions\n if (!Array.isArray(reply.exclude) || reply.exclude.length === 0) {\n return true;\n }\n\n // Use some() for early exit on first match\n const hasExcludedWord = reply.exclude.some(excludeWord =>\n lowerText.includes(excludeWord.toLowerCase())\n );\n\n return !hasExcludedWord;\n });\n}\n\nexport const useKeywordBotAgent = async (\n keywordBot: KeywordBot\n): Promise<KeywordBotAgent | null> => {\n return initializeBotAgent(\n \"KeywordBot\",\n keywordBot,\n (opts, bot) => new KeywordBotAgent(opts, bot as KeywordBot)\n );\n};\n","import WebSocket from \"ws\";\nimport { Logger } from \"./logger\";\nimport { healthMonitor } from \"./healthCheck\";\n\ninterface WebSocketClientOptions {\n /** The URL of the WebSocket server to connect to. */\n service: string | string[];\n /** The interval in milliseconds to wait before attempting to reconnect when the connection closes. Default is 5000ms. */\n reconnectInterval?: number;\n /** The interval in milliseconds for sending ping messages (heartbeats) to keep the connection alive. Default is 10000ms. */\n pingInterval?: number;\n /** Maximum number of consecutive reconnection attempts per service. Default is 3. */\n maxReconnectAttempts?: number;\n /** Maximum delay between reconnection attempts in milliseconds. Default is 30000ms (30 seconds). */\n maxReconnectDelay?: number;\n /** Exponential backoff factor for reconnection delays. Default is 1.5. */\n backoffFactor?: number;\n /** Maximum number of attempts to cycle through all services before giving up. Default is 2. */\n maxServiceCycles?: number;\n}\n\n/**\n * A WebSocket client that automatically attempts to reconnect upon disconnection\n * and periodically sends ping messages (heartbeats) to ensure the connection remains alive.\n *\n * Extend this class and override the protected `onOpen`, `onMessage`, `onError`, and `onClose` methods\n * to implement custom handling of WebSocket events.\n */\nexport class WebSocketClient {\n private service: string | string[];\n private reconnectInterval: number;\n private pingInterval: number;\n private ws: WebSocket | null = null;\n private pingTimeout: NodeJS.Timeout | null = null;\n private serviceIndex = 0;\n private reconnectAttempts = 0;\n private serviceCycles = 0;\n private maxReconnectAttempts: number;\n private maxServiceCycles: number;\n private maxReconnectDelay: number;\n private backoffFactor: number;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n private isConnecting = false;\n private shouldReconnect = true;\n private messageCount = 0;\n private lastMessageTime = 0;\n private healthCheckName: string;\n\n /**\n * Creates a new instance of `WebSocketClient`.\n *\n * @param options - Configuration options for the WebSocket client, including URL, reconnect interval, and ping interval.\n */\n constructor(options: WebSocketClientOptions) {\n this.service = options.service;\n this.reconnectInterval = options.reconnectInterval || 5000;\n this.pingInterval = options.pingInterval || 10000;\n this.maxReconnectAttempts = options.maxReconnectAttempts || 3;\n this.maxServiceCycles = options.maxServiceCycles || 2;\n this.maxReconnectDelay = options.maxReconnectDelay || 30000;\n this.backoffFactor = options.backoffFactor || 1.5;\n\n // Generate unique health check name\n this.healthCheckName = `websocket_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;\n\n // Register health check\n healthMonitor.registerHealthCheck(this.healthCheckName, async () => {\n return this.getConnectionState() === \"CONNECTED\";\n });\n\n // Initialize metrics\n healthMonitor.setMetric(`${this.healthCheckName}_messages_received`, 0);\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, 0);\n\n this.run();\n }\n\n /**\n * Initiates a WebSocket connection to the specified URL.\n *\n * This method sets up event listeners for `open`, `message`, `error`, and `close` events.\n * When the connection opens, it starts the heartbeat mechanism.\n * On close, it attempts to reconnect after a specified interval.\n */\n private run() {\n if (this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n const currentService = Array.isArray(this.service)\n ? this.service[this.serviceIndex]\n : this.service;\n\n Logger.info(`Attempting to connect to WebSocket: ${currentService}`);\n this.ws = new WebSocket(currentService);\n\n this.ws.on(\"open\", () => {\n Logger.info(\"WebSocket connected successfully\", {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n });\n this.isConnecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n this.serviceCycles = 0; // Reset cycles on successful connection\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n this.startHeartbeat();\n this.onOpen();\n });\n\n this.ws.on(\"message\", (data: WebSocket.Data) => {\n this.messageCount++;\n this.lastMessageTime = Date.now();\n healthMonitor.incrementMetric(`${this.healthCheckName}_messages_received`);\n this.onMessage(data);\n });\n\n this.ws.on(\"error\", error => {\n Logger.error(\"WebSocket error:\", error);\n this.isConnecting = false;\n this.onError(error);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n Logger.info(`WebSocket disconnected. Code: ${code}, Reason: ${reason.toString()}`);\n this.isConnecting = false;\n this.stopHeartbeat();\n this.onClose();\n\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n }\n\n /**\n * Attempts to reconnect to the WebSocket server after the specified `reconnectInterval`.\n * It clears all event listeners on the old WebSocket and initiates a new connection.\n */\n private scheduleReconnect() {\n this.reconnectAttempts++;\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n\n // Check if we should try the next service\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.shouldTryNextService()) {\n this.moveToNextService();\n return; // Try next service immediately\n } else {\n Logger.error(\"All services exhausted after maximum cycles\", {\n totalServices: Array.isArray(this.service) ? this.service.length : 1,\n maxServiceCycles: this.maxServiceCycles,\n serviceCycles: this.serviceCycles,\n });\n return; // Give up entirely\n }\n }\n\n const delay = Math.min(\n this.reconnectInterval * Math.pow(this.backoffFactor, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n Logger.info(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} for service`,\n {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n delay: `${delay}ms`,\n }\n );\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n }\n\n this.reconnectTimeout = setTimeout(() => {\n this.cleanup();\n this.run();\n }, delay);\n }\n\n /**\n * Check if we should try the next service in the array.\n */\n private shouldTryNextService(): boolean {\n if (!Array.isArray(this.service)) {\n return false; // Single service, can't switch\n }\n\n return this.serviceCycles < this.maxServiceCycles;\n }\n\n /**\n * Move to the next service in the array and reset reconnection attempts.\n */\n private moveToNextService() {\n if (!Array.isArray(this.service)) {\n return;\n }\n\n const previousIndex = this.serviceIndex;\n this.serviceIndex = (this.serviceIndex + 1) % this.service.length;\n\n // If we've gone through all services once, increment the cycle counter\n if (this.serviceIndex === 0) {\n this.serviceCycles++;\n }\n\n this.reconnectAttempts = 0; // Reset attempts for the new service\n\n Logger.info(\"Switching to next service\", {\n previousService: this.service[previousIndex],\n previousIndex,\n newService: this.getCurrentService(),\n newIndex: this.serviceIndex,\n serviceCycle: this.serviceCycles,\n });\n\n // Try the new service immediately\n this.cleanup();\n this.run();\n }\n\n private cleanup() {\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close();\n }\n this.ws = null;\n }\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n }\n\n /**\n * Starts sending periodic ping messages to the server.\n *\n * This function uses `setInterval` to send a ping at the configured `pingInterval`.\n * If the WebSocket is not open, pings are not sent.\n */\n private startHeartbeat() {\n this.pingTimeout = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, this.pingInterval);\n }\n\n /**\n * Stops sending heartbeat pings by clearing the ping interval.\n */\n private stopHeartbeat() {\n if (this.pingTimeout) {\n clearInterval(this.pingTimeout);\n this.pingTimeout = null;\n }\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n *\n * Override this method in a subclass to implement custom logic on connection.\n */\n protected onOpen() {\n // Custom logic for connection open\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * @param data - The data received from the WebSocket server.\n *\n * Override this method in a subclass to implement custom message handling.\n */\n protected onMessage(_data: WebSocket.Data) {\n // Custom logic for handling received messages\n }\n\n /**\n * Called when a WebSocket error occurs.\n *\n * @param error - The error that occurred.\n *\n * Override this method in a subclass to implement custom error handling.\n * Note: Service switching is now handled in the reconnection logic, not here.\n */\n protected onError(_error: Error) {\n // Custom logic for handling errors - override in subclasses\n // Service switching is handled automatically in scheduleReconnect()\n }\n\n /**\n * Called when the WebSocket connection is closed.\n *\n * Override this method in a subclass to implement custom logic on disconnection.\n */\n protected onClose() {\n // Custom logic for handling connection close\n }\n\n /**\n * Sends data to the connected WebSocket server, if the connection is open.\n *\n * @param data - The data to send.\n */\n public send(data: string | Buffer | ArrayBuffer | Buffer[]) {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(data);\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n */\n public close() {\n this.shouldReconnect = false;\n this.stopHeartbeat();\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.ws) {\n this.ws.close();\n }\n\n // Unregister health check when closing\n healthMonitor.unregisterHealthCheck(this.healthCheckName);\n }\n\n public getConnectionState(): string {\n if (!this.ws) return \"DISCONNECTED\";\n\n switch (this.ws.readyState) {\n case WebSocket.CONNECTING:\n return \"CONNECTING\";\n case WebSocket.OPEN:\n return \"CONNECTED\";\n case WebSocket.CLOSING:\n return \"CLOSING\";\n case WebSocket.CLOSED:\n return \"DISCONNECTED\";\n default:\n return \"UNKNOWN\";\n }\n }\n\n public getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n public getServiceCycles(): number {\n return this.serviceCycles;\n }\n\n public getServiceIndex(): number {\n return this.serviceIndex;\n }\n\n public getAllServices(): string[] {\n return Array.isArray(this.service) ? [...this.service] : [this.service];\n }\n\n public getCurrentService(): string {\n return Array.isArray(this.service) ? this.service[this.serviceIndex] : this.service;\n }\n\n public getMessageCount(): number {\n return this.messageCount;\n }\n\n public getLastMessageTime(): number {\n return this.lastMessageTime;\n }\n\n public getHealthCheckName(): string {\n return this.healthCheckName;\n }\n}\n","import { Logger } from \"./logger\";\n\nexport interface HealthStatus {\n healthy: boolean;\n timestamp: number;\n checks: Record<string, boolean>;\n metrics: Record<string, number>;\n details?: Record<string, unknown>;\n}\n\nexport interface HealthCheckOptions {\n interval?: number; // milliseconds\n timeout?: number; // milliseconds\n retries?: number;\n}\n\n/**\n * Health monitoring system for bot components.\n * Provides health checks and basic metrics collection.\n */\nexport class HealthMonitor {\n private checks = new Map<string, () => Promise<boolean>>();\n private metrics = new Map<string, number>();\n private lastCheckResults = new Map<string, boolean>();\n private checkInterval: NodeJS.Timeout | null = null;\n private options: Required<HealthCheckOptions>;\n\n constructor(options: HealthCheckOptions = {}) {\n this.options = {\n interval: options.interval || 30000, // 30 seconds\n timeout: options.timeout || 5000, // 5 seconds\n retries: options.retries || 2,\n };\n }\n\n /**\n * Register a health check function.\n * @param name - Unique name for the health check\n * @param checkFn - Function that returns true if healthy\n */\n registerHealthCheck(name: string, checkFn: () => Promise<boolean>) {\n this.checks.set(name, checkFn);\n Logger.debug(`Registered health check: ${name}`);\n }\n\n /**\n * Remove a health check.\n * @param name - Name of the health check to remove\n */\n unregisterHealthCheck(name: string) {\n this.checks.delete(name);\n this.lastCheckResults.delete(name);\n Logger.debug(`Unregistered health check: ${name}`);\n }\n\n /**\n * Set a metric value.\n * @param name - Metric name\n * @param value - Metric value\n */\n setMetric(name: string, value: number) {\n this.metrics.set(name, value);\n }\n\n /**\n * Increment a counter metric.\n * @param name - Metric name\n * @param increment - Value to add (default: 1)\n */\n incrementMetric(name: string, increment = 1) {\n const current = this.metrics.get(name) || 0;\n this.metrics.set(name, current + increment);\n }\n\n /**\n * Get current metric value.\n * @param name - Metric name\n * @returns Current value or 0 if not found\n */\n getMetric(name: string): number {\n return this.metrics.get(name) || 0;\n }\n\n /**\n * Get all current metrics.\n * @returns Object with all metrics\n */\n getAllMetrics(): Record<string, number> {\n return Object.fromEntries(this.metrics);\n }\n\n /**\n * Run a single health check with timeout and retries.\n * @private\n */\n private async runHealthCheck(name: string, checkFn: () => Promise<boolean>): Promise<boolean> {\n for (let attempt = 0; attempt <= this.options.retries; attempt++) {\n try {\n const result = await this.withTimeout(checkFn(), this.options.timeout);\n if (result) {\n return true;\n }\n } catch (error) {\n Logger.debug(\n `Health check \"${name}\" failed (attempt ${attempt + 1}/${this.options.retries + 1}):`,\n { error: error.message }\n );\n }\n }\n return false;\n }\n\n /**\n * Wrap a promise with a timeout.\n * @private\n */\n private withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs)\n ),\n ]);\n }\n\n /**\n * Run all health checks and return the current health status.\n */\n async getHealthStatus(): Promise<HealthStatus> {\n const timestamp = Date.now();\n const checkResults: Record<string, boolean> = {};\n const details: Record<string, unknown> = {};\n\n // Run all health checks\n const checkPromises = Array.from(this.checks.entries()).map(async ([name, checkFn]) => {\n const result = await this.runHealthCheck(name, checkFn);\n checkResults[name] = result;\n this.lastCheckResults.set(name, result);\n\n if (!result) {\n details[`${name}_last_failure`] = new Date().toISOString();\n }\n\n return result;\n });\n\n await Promise.allSettled(checkPromises);\n\n // Determine overall health\n const healthy = Object.values(checkResults).every(result => result);\n\n // Get current metrics\n const metrics = this.getAllMetrics();\n\n return {\n healthy,\n timestamp,\n checks: checkResults,\n metrics,\n details,\n };\n }\n\n /**\n * Start periodic health monitoring.\n */\n start() {\n if (this.checkInterval) {\n this.stop();\n }\n\n Logger.info(`Starting health monitor with ${this.options.interval}ms interval`);\n\n this.checkInterval = setInterval(async () => {\n try {\n const status = await this.getHealthStatus();\n\n if (!status.healthy) {\n const failedChecks = Object.entries(status.checks)\n .filter(([, healthy]) => !healthy)\n .map(([name]) => name);\n\n Logger.warn(`Health check failed`, {\n operation: \"health_check\",\n failed_checks: failedChecks,\n metrics: status.metrics,\n });\n } else {\n Logger.debug(\"Health check passed\", {\n operation: \"health_check\",\n metrics: status.metrics,\n });\n }\n } catch (error) {\n Logger.error(\"Error during health check:\", { error: error.message });\n }\n }, this.options.interval);\n }\n\n /**\n * Stop periodic health monitoring.\n */\n stop() {\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n Logger.info(\"Stopped health monitor\");\n }\n }\n\n /**\n * Get a summary of the last health check results.\n */\n getLastCheckSummary(): Record<string, boolean> {\n return Object.fromEntries(this.lastCheckResults);\n }\n}\n\n// Global health monitor instance\nexport const healthMonitor = new HealthMonitor();\n","import WebSocket from \"ws\";\nimport { WebSocketClient } from \"./websocketClient\";\nimport { Logger } from \"./logger\";\n\n/**\n * Represents a subscription to a Jetstream feed over WebSocket.\n *\n * This class extends `WebSocketClient` to automatically handle reconnections and heartbeats.\n * It invokes a provided callback function whenever a message is received from the Jetstream server.\n */\nexport class JetstreamSubscription extends WebSocketClient {\n /**\n * Creates a new `JetstreamSubscription`.\n *\n * @param service - The URL(-Array) of the Jetstream server(s) to connect to.\n * @param interval - The interval (in milliseconds) for reconnect attempts.\n * @param onMessageCallback - An optional callback function that is invoked whenever a message is received from the server.\n */\n constructor(\n service: string | string[],\n public interval: number,\n private onMessageCallback?: (data: WebSocket.Data) => void\n ) {\n super({ service, reconnectInterval: interval });\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n * Logs a message indicating that the connection to the Jetstream server has been established.\n */\n protected onOpen() {\n Logger.info(\"Connected to Jetstream server.\");\n super.onOpen();\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * If an `onMessageCallback` was provided, it is invoked with the received data.\n *\n * @param data - The data received from the Jetstream server.\n */\n protected onMessage(data: WebSocket.Data) {\n if (this.onMessageCallback) {\n this.onMessageCallback(data);\n }\n }\n\n /**\n * Called when a WebSocket error occurs.\n * Logs the error message indicating that Jetstream encountered an error.\n *\n * @param error - The error that occurred.\n */\n protected onError(error: Error) {\n Logger.error(\"Jetstream encountered an error:\", error);\n super.onError(error);\n }\n\n /**\n * Called when the WebSocket connection is closed.\n * Logs a message indicating that the Jetstream connection has closed.\n */\n protected onClose() {\n Logger.info(\"Jetstream connection closed.\");\n super.onClose();\n }\n}\n","/**\n * Returns the given string if it is defined; otherwise returns `undefined`.\n *\n * @param val - The optional string value to check.\n * @returns The given string if defined, or `undefined` if `val` is falsy.\n */\nexport const maybeStr = (val?: string): string | undefined => {\n if (!val) return undefined;\n return val;\n};\n\n/**\n * Parses the given string as an integer if it is defined and a valid integer; otherwise returns `undefined`.\n *\n * @param val - The optional string value to parse.\n * @returns The parsed integer if successful, or `undefined` if the string is falsy or not a valid integer.\n */\nexport const maybeInt = (val?: string): number | undefined => {\n if (!val) return undefined;\n const int = parseInt(val, 10);\n if (isNaN(int)) return undefined;\n return int;\n};\n","import WebSocket from \"ws\";\nimport { Post } from \"../types/post\";\nimport { WebsocketMessage } from \"../types/message\";\n/**\n * Converts a raw WebSocket message into a `FeedEntry` object, if possible.\n *\n * This function checks if the incoming WebSocket data is structured like a feed commit message\n * with the required properties for a created post. If the data matches the expected shape,\n * it extracts and returns a `FeedEntry` object. Otherwise, it returns `null`.\n *\n * @param data - The raw WebSocket data.\n * @returns A `FeedEntry` object if the data represents a newly created post, otherwise `null`.\n */\nexport function websocketToFeedEntry(data: WebSocket.Data): Post | null {\n const message = data as WebsocketMessage;\n if (\n !message.commit ||\n !message.commit.record ||\n !message.commit.record[\"$type\"] ||\n !message.did ||\n !message.commit.cid ||\n !message.commit.rkey ||\n message.commit.operation !== \"create\"\n ) {\n return null;\n }\n const messageUri = `at://${message.did}/${message.commit.record[\"$type\"]}/${message.commit.rkey}`;\n return {\n cid: message.commit.cid,\n uri: messageUri,\n authorDid: message.did,\n text: message.commit.record.text,\n rootCid: message.commit.record.reply?.root.cid ?? message.commit.cid,\n rootUri: message.commit.record.reply?.root.uri ?? messageUri,\n };\n}\n"],"mappings":"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","names":["LogLevel"]}
1
+
{"version":3,"sources":["../src/utils/logger.ts","../src/bots/baseBotAgent.ts","../src/bots/actionBot.ts","../src/bots/cronBot.ts","../src/bots/keywordBot.ts","../src/utils/websocketClient.ts","../src/utils/healthCheck.ts","../src/utils/jetstreamSubscription.ts","../src/utils/strings.ts","../src/utils/wsToFeed.ts"],"sourcesContent":["export enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n correlationId?: string;\n botId?: string;\n operation?: string;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * A performance-optimized logging utility class providing static methods for various log levels.\n * Each log message is prefixed with a timestamp and log level.\n * Supports conditional logging based on log levels and configurable timezone.\n */\nexport class Logger {\n private static logLevel: LogLevel = LogLevel.INFO;\n private static timezone: string = \"Europe/Vienna\";\n private static correlationId: string | null = null;\n\n /**\n * Generate a new correlation ID for tracking related operations.\n */\n static generateCorrelationId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Set the correlation ID for subsequent log entries.\n * @param id - The correlation ID to use, or null to generate a new one\n */\n static setCorrelationId(id?: string | null) {\n this.correlationId = id || this.generateCorrelationId();\n }\n\n /**\n * Get the current correlation ID.\n */\n static getCorrelationId(): string | null {\n return this.correlationId;\n }\n\n /**\n * Clear the current correlation ID.\n */\n static clearCorrelationId() {\n this.correlationId = null;\n }\n\n /**\n * Set the minimum log level. Messages below this level will not be logged.\n * @param level - The minimum log level\n */\n static setLogLevel(level: LogLevel) {\n this.logLevel = level;\n }\n\n /**\n * Set the timezone for log timestamps.\n * @param timezone - The timezone string (e.g., \"Europe/Vienna\", \"UTC\")\n */\n static setTimezone(timezone: string) {\n this.timezone = timezone;\n }\n\n /**\n * Get the current log level.\n */\n static getLogLevel(): LogLevel {\n return this.logLevel;\n }\n\n /**\n * Generate a formatted timestamp string.\n * @private\n */\n private static getTimestamp(): string {\n return new Date().toLocaleString(\"de-DE\", { timeZone: this.timezone });\n }\n\n /**\n * Internal logging method that checks log level before processing.\n * @private\n */\n private static log(\n level: LogLevel,\n levelName: string,\n message: string,\n context?: LogContext | object | string,\n logFn = console.log\n ) {\n if (level < this.logLevel) {\n return; // Skip logging if below threshold\n }\n\n const timestamp = this.getTimestamp();\n let formattedMessage = `${timestamp} [${levelName}]`;\n\n // Add correlation ID if available\n if (this.correlationId) {\n formattedMessage += ` [${this.correlationId}]`;\n }\n\n // Add context correlation ID if provided and different from global one\n if (\n context &&\n typeof context === \"object\" &&\n \"correlationId\" in context &&\n context.correlationId &&\n context.correlationId !== this.correlationId\n ) {\n formattedMessage += ` [${context.correlationId}]`;\n }\n\n formattedMessage += `: ${message}`;\n\n if (context) {\n // Create structured log entry for objects\n if (typeof context === \"object\") {\n const logEntry = {\n timestamp: new Date().toISOString(),\n level: levelName,\n message,\n correlationId: this.correlationId,\n ...context,\n };\n logFn(formattedMessage, logEntry);\n } else {\n logFn(formattedMessage, context);\n }\n } else {\n logFn(formattedMessage);\n }\n }\n /**\n * Logs an informational message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static info(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.INFO, \"INFO\", message, context, console.info);\n }\n\n /**\n * Logs a warning message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static warn(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.WARN, \"WARNING\", message, context, console.warn);\n }\n\n /**\n * Logs an error message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static error(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.ERROR, \"ERROR\", message, context, console.error);\n }\n\n /**\n * Logs a debug message to the console.\n *\n * @param message - The message to be logged.\n * @param context - Optional additional context (LogContext, object or string) to log alongside the message.\n */\n static debug(message: string, context?: LogContext | object | string) {\n this.log(LogLevel.DEBUG, \"DEBUG\", message, context, console.debug);\n }\n\n /**\n * Log operation start with timing.\n * @param operation - The operation name\n * @param context - Additional context\n */\n static startOperation(operation: string, context?: LogContext): string {\n const correlationId = context?.correlationId || this.generateCorrelationId();\n this.setCorrelationId(correlationId);\n\n this.info(`Starting operation: ${operation}`, {\n operation,\n correlationId,\n ...context,\n });\n\n return correlationId;\n }\n\n /**\n * Log operation completion with timing.\n * @param operation - The operation name\n * @param startTime - The start time from Date.now()\n * @param context - Additional context\n */\n static endOperation(operation: string, startTime: number, context?: LogContext) {\n const duration = Date.now() - startTime;\n\n this.info(`Completed operation: ${operation}`, {\n operation,\n duration: `${duration}ms`,\n ...context,\n });\n }\n}\n","import { AtpAgent, AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { Bot } from \"../types/bot\";\n\n/**\n * Base class for all bot agents with common functionality.\n * Provides correlation tracking and structured logging capabilities.\n */\nexport abstract class BotAgent extends AtpAgent {\n protected currentCorrelationId: string | null = null;\n protected operationStartTime: number | null = null;\n\n constructor(\n public opts: AtpAgentOptions,\n protected bot: Bot\n ) {\n super(opts);\n }\n\n /**\n * Start tracking an operation with correlation ID and timing.\n * @protected\n */\n protected startOperationTracking(): void {\n this.currentCorrelationId = Logger.generateCorrelationId();\n this.operationStartTime = Date.now();\n }\n\n /**\n * Clear operation tracking state.\n * @protected\n */\n protected clearOperationTracking(): void {\n this.currentCorrelationId = null;\n this.operationStartTime = null;\n }\n\n /**\n * Get the bot identifier for logging purposes.\n * @protected\n */\n protected getBotId(): string {\n return this.bot.username || this.bot.identifier;\n }\n\n /**\n * Log a message with correlation ID during bot execution.\n * Call this from within your bot methods to log with proper correlation tracking.\n */\n logAction(\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n additionalContext?: Record<string, unknown>\n ): void {\n const logContext: Record<string, unknown> = {\n botId: this.getBotId(),\n ...additionalContext,\n };\n\n if (this.currentCorrelationId && this.operationStartTime) {\n logContext.correlationId = this.currentCorrelationId;\n logContext.operation = this.getOperationName();\n logContext.duration = `${Date.now() - this.operationStartTime}ms`;\n }\n\n switch (level) {\n case \"info\":\n Logger.info(message, logContext);\n break;\n case \"warn\":\n Logger.warn(message, logContext);\n break;\n case \"error\":\n Logger.error(message, logContext);\n break;\n }\n }\n\n /**\n * Get the operation name for logging. Override in subclasses.\n * @protected\n */\n protected abstract getOperationName(): string;\n}\n\n/**\n * Generic bot initialization function that handles common setup.\n */\nexport async function initializeBotAgent<T extends BotAgent>(\n botType: string,\n bot: Bot,\n createAgent: (opts: AtpAgentOptions, bot: Bot) => T\n): Promise<T | null> {\n const botId = bot.username ?? bot.identifier;\n const correlationId = Logger.startOperation(`initialize${botType}`, { botId });\n const startTime = Date.now();\n\n const agent = createAgent({ service: bot.service }, bot);\n\n try {\n Logger.info(`Initializing ${botType.toLowerCase()}`, { correlationId, botId });\n\n const login = await agent.login({\n identifier: bot.identifier,\n password: bot.password!,\n });\n\n if (!login.success) {\n Logger.warn(`${botType} login failed`, { correlationId, botId });\n return null;\n }\n\n Logger.endOperation(`initialize${botType}`, startTime, { correlationId, botId });\n return agent;\n } catch (error) {\n Logger.error(`Failed to initialize ${botType.toLowerCase()}`, {\n correlationId,\n botId,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n });\n return null;\n }\n}\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { Logger } from \"../utils/logger\";\nimport type { ActionBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class ActionBotAgent extends BotAgent {\n public actionBot: ActionBot;\n\n constructor(opts: AtpAgentOptions, actionBot: ActionBot) {\n super(opts, actionBot);\n this.actionBot = actionBot;\n }\n\n async doAction(params?: unknown): Promise<void> {\n // Start operation tracking but don't log yet\n this.startOperationTracking();\n\n try {\n await this.actionBot.action(this, params);\n } catch (error) {\n Logger.error(\"Action bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"actionBot.doAction\";\n }\n}\n\nexport const useActionBotAgent = async (actionBot: ActionBot): Promise<ActionBotAgent | null> => {\n return initializeBotAgent(\n \"ActionBot\",\n actionBot,\n (opts, bot) => new ActionBotAgent(opts, bot as ActionBot)\n );\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport { CronJob } from \"cron\";\nimport { Logger } from \"../utils/logger\";\nimport type { CronBot } from \"../types/bot\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class CronBotAgent extends BotAgent {\n public job: CronJob;\n public cronBot: CronBot;\n\n constructor(opts: AtpAgentOptions, cronBot: CronBot) {\n super(opts, cronBot);\n this.cronBot = cronBot;\n\n this.job = new CronJob(\n cronBot.cronJob.scheduleExpression,\n async () => {\n // Start operation tracking for cron execution\n this.startOperationTracking();\n\n try {\n await cronBot.action(this);\n } catch (error) {\n Logger.error(\"Cron bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"cronBot.action\",\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n },\n cronBot.cronJob.callback,\n false,\n cronBot.cronJob.timeZone\n );\n }\n\n protected getOperationName(): string {\n return \"cronBot.action\";\n }\n}\n\nexport const useCronBotAgent = async (cronBot: CronBot): Promise<CronBotAgent | null> => {\n const agent = await initializeBotAgent(\n \"CronBot\",\n cronBot,\n (opts, bot) => new CronBotAgent(opts, bot as CronBot)\n );\n\n // Start the cron job after successful initialization\n if (agent) {\n agent.job.start();\n }\n\n return agent;\n};\n","import { AtpAgentOptions } from \"@atproto/api\";\nimport type { BotReply, KeywordBot } from \"../types/bot\";\nimport type { Post, UriCid } from \"../types/post\";\nimport { Logger } from \"../utils/logger\";\nimport { BotAgent, initializeBotAgent } from \"./baseBotAgent\";\n\nexport class KeywordBotAgent extends BotAgent {\n public keywordBot: KeywordBot;\n\n constructor(opts: AtpAgentOptions, keywordBot: KeywordBot) {\n super(opts, keywordBot);\n this.keywordBot = keywordBot;\n }\n\n async likeAndReplyIfFollower(post: Post): Promise<void> {\n if (post.authorDid === this.assertDid) {\n return;\n }\n\n const replies = filterBotReplies(post.text, this.keywordBot.replies);\n if (replies.length < 1) {\n return;\n }\n\n // Start operation tracking when actual work begins\n this.startOperationTracking();\n\n try {\n const actorProfile = await this.getProfile({ actor: post.authorDid });\n\n if (actorProfile.success) {\n if (!actorProfile.data.viewer?.followedBy) {\n return;\n }\n\n const replyCfg = replies[Math.floor(Math.random() * replies.length)];\n const message = replyCfg.messages[Math.floor(Math.random() * replyCfg.messages.length)];\n const reply = buildReplyToPost(\n { uri: post.rootUri, cid: post.rootCid },\n { uri: post.uri, cid: post.cid },\n message\n );\n\n await Promise.all([this.like(post.uri, post.cid), this.post(reply)]);\n\n this.logAction(\"info\", `Replied to post: ${post.uri}`, {\n postUri: post.uri,\n authorDid: post.authorDid,\n keyword: replyCfg.keyword,\n message: message,\n });\n }\n } catch (error) {\n Logger.error(\"Keyword bot execution failed\", {\n correlationId: this.currentCorrelationId,\n botId: this.getBotId(),\n operation: \"keywordBot.likeAndReplyIfFollower\",\n error: error instanceof Error ? error.message : String(error),\n postUri: post.uri,\n authorDid: post.authorDid,\n });\n } finally {\n // Clean up tracking state\n this.clearOperationTracking();\n }\n }\n\n protected getOperationName(): string {\n return \"keywordBot.likeAndReplyIfFollower\";\n }\n}\n\nexport function buildReplyToPost(root: UriCid, parent: UriCid, message: string) {\n return {\n $type: \"app.bsky.feed.post\" as const,\n text: message,\n reply: {\n root: root,\n parent: parent,\n },\n };\n}\n\nexport function filterBotReplies(text: string, botReplies: BotReply[]) {\n // Cache the lowercased text to avoid multiple toLowerCase() calls\n const lowerText = text.toLowerCase();\n\n return botReplies.filter(reply => {\n // Use cached lowercase comparison\n const keyword = reply.keyword.toLowerCase();\n if (!lowerText.includes(keyword)) {\n return false;\n }\n\n // Early return if no exclusions\n if (!Array.isArray(reply.exclude) || reply.exclude.length === 0) {\n return true;\n }\n\n // Use some() for early exit on first match\n const hasExcludedWord = reply.exclude.some(excludeWord =>\n lowerText.includes(excludeWord.toLowerCase())\n );\n\n return !hasExcludedWord;\n });\n}\n\nexport const useKeywordBotAgent = async (\n keywordBot: KeywordBot\n): Promise<KeywordBotAgent | null> => {\n return initializeBotAgent(\n \"KeywordBot\",\n keywordBot,\n (opts, bot) => new KeywordBotAgent(opts, bot as KeywordBot)\n );\n};\n","import WebSocket from \"ws\";\nimport { Logger } from \"./logger\";\nimport { healthMonitor } from \"./healthCheck\";\n\ninterface WebSocketClientOptions {\n /** The URL of the WebSocket server to connect to. */\n service: string | string[];\n /** The interval in milliseconds to wait before attempting to reconnect when the connection closes. Default is 5000ms. */\n reconnectInterval?: number;\n /** The interval in milliseconds for sending ping messages (heartbeats) to keep the connection alive. Default is 10000ms. */\n pingInterval?: number;\n /** Maximum number of consecutive reconnection attempts per service. Default is 3. */\n maxReconnectAttempts?: number;\n /** Maximum delay between reconnection attempts in milliseconds. Default is 30000ms (30 seconds). */\n maxReconnectDelay?: number;\n /** Exponential backoff factor for reconnection delays. Default is 1.5. */\n backoffFactor?: number;\n /** Maximum number of attempts to cycle through all services before giving up. Default is 2. */\n maxServiceCycles?: number;\n}\n\n/**\n * A WebSocket client that automatically attempts to reconnect upon disconnection\n * and periodically sends ping messages (heartbeats) to ensure the connection remains alive.\n *\n * Extend this class and override the protected `onOpen`, `onMessage`, `onError`, and `onClose` methods\n * to implement custom handling of WebSocket events.\n */\nexport class WebSocketClient {\n private service: string | string[];\n private reconnectInterval: number;\n private pingInterval: number;\n private ws: WebSocket | null = null;\n private pingTimeout: NodeJS.Timeout | null = null;\n private serviceIndex = 0;\n private reconnectAttempts = 0;\n private serviceCycles = 0;\n private maxReconnectAttempts: number;\n private maxServiceCycles: number;\n private maxReconnectDelay: number;\n private backoffFactor: number;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n private isConnecting = false;\n private shouldReconnect = true;\n private messageCount = 0;\n private lastMessageTime = 0;\n private healthCheckName: string;\n\n /**\n * Creates a new instance of `WebSocketClient`.\n *\n * @param options - Configuration options for the WebSocket client, including URL, reconnect interval, and ping interval.\n */\n constructor(options: WebSocketClientOptions) {\n this.service = options.service;\n this.reconnectInterval = options.reconnectInterval || 5000;\n this.pingInterval = options.pingInterval || 10000;\n this.maxReconnectAttempts = options.maxReconnectAttempts || 3;\n this.maxServiceCycles = options.maxServiceCycles || 2;\n this.maxReconnectDelay = options.maxReconnectDelay || 30000;\n this.backoffFactor = options.backoffFactor || 1.5;\n\n // Generate unique health check name\n this.healthCheckName = `websocket_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;\n\n // Register health check\n healthMonitor.registerHealthCheck(this.healthCheckName, async () => {\n return this.getConnectionState() === \"CONNECTED\";\n });\n\n // Initialize metrics\n healthMonitor.setMetric(`${this.healthCheckName}_messages_received`, 0);\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, 0);\n\n this.run();\n }\n\n /**\n * Initiates a WebSocket connection to the specified URL.\n *\n * This method sets up event listeners for `open`, `message`, `error`, and `close` events.\n * When the connection opens, it starts the heartbeat mechanism.\n * On close, it attempts to reconnect after a specified interval.\n */\n private run() {\n if (this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n const currentService = Array.isArray(this.service)\n ? this.service[this.serviceIndex]\n : this.service;\n\n Logger.info(`Attempting to connect to WebSocket: ${currentService}`);\n this.ws = new WebSocket(currentService);\n\n this.ws.on(\"open\", () => {\n Logger.info(\"WebSocket connected successfully\", {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n });\n this.isConnecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n this.serviceCycles = 0; // Reset cycles on successful connection\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n this.startHeartbeat();\n this.onOpen();\n });\n\n this.ws.on(\"message\", (data: WebSocket.Data) => {\n this.messageCount++;\n this.lastMessageTime = Date.now();\n healthMonitor.incrementMetric(`${this.healthCheckName}_messages_received`);\n this.onMessage(data);\n });\n\n this.ws.on(\"error\", error => {\n Logger.error(\"WebSocket error:\", error);\n this.isConnecting = false;\n this.onError(error);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n Logger.info(`WebSocket disconnected. Code: ${code}, Reason: ${reason.toString()}`);\n this.isConnecting = false;\n this.stopHeartbeat();\n this.onClose();\n\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n }\n\n /**\n * Attempts to reconnect to the WebSocket server after the specified `reconnectInterval`.\n * It clears all event listeners on the old WebSocket and initiates a new connection.\n */\n private scheduleReconnect() {\n this.reconnectAttempts++;\n healthMonitor.setMetric(`${this.healthCheckName}_reconnect_attempts`, this.reconnectAttempts);\n\n // Check if we should try the next service\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.shouldTryNextService()) {\n this.moveToNextService();\n return; // Try next service immediately\n } else {\n Logger.error(\"All services exhausted after maximum cycles\", {\n totalServices: Array.isArray(this.service) ? this.service.length : 1,\n maxServiceCycles: this.maxServiceCycles,\n serviceCycles: this.serviceCycles,\n });\n return; // Give up entirely\n }\n }\n\n const delay = Math.min(\n this.reconnectInterval * Math.pow(this.backoffFactor, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n Logger.info(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} for service`,\n {\n service: this.getCurrentService(),\n serviceIndex: this.serviceIndex,\n delay: `${delay}ms`,\n }\n );\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n }\n\n this.reconnectTimeout = setTimeout(() => {\n this.cleanup();\n this.run();\n }, delay);\n }\n\n /**\n * Check if we should try the next service in the array.\n */\n private shouldTryNextService(): boolean {\n if (!Array.isArray(this.service)) {\n return false; // Single service, can't switch\n }\n\n return this.serviceCycles < this.maxServiceCycles;\n }\n\n /**\n * Move to the next service in the array and reset reconnection attempts.\n */\n private moveToNextService() {\n if (!Array.isArray(this.service)) {\n return;\n }\n\n const previousIndex = this.serviceIndex;\n this.serviceIndex = (this.serviceIndex + 1) % this.service.length;\n\n // If we've gone through all services once, increment the cycle counter\n if (this.serviceIndex === 0) {\n this.serviceCycles++;\n }\n\n this.reconnectAttempts = 0; // Reset attempts for the new service\n\n Logger.info(\"Switching to next service\", {\n previousService: this.service[previousIndex],\n previousIndex,\n newService: this.getCurrentService(),\n newIndex: this.serviceIndex,\n serviceCycle: this.serviceCycles,\n });\n\n // Try the new service immediately\n this.cleanup();\n this.run();\n }\n\n private cleanup() {\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.close();\n }\n this.ws = null;\n }\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n }\n\n /**\n * Starts sending periodic ping messages to the server.\n *\n * This function uses `setInterval` to send a ping at the configured `pingInterval`.\n * If the WebSocket is not open, pings are not sent.\n */\n private startHeartbeat() {\n this.pingTimeout = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, this.pingInterval);\n }\n\n /**\n * Stops sending heartbeat pings by clearing the ping interval.\n */\n private stopHeartbeat() {\n if (this.pingTimeout) {\n clearInterval(this.pingTimeout);\n this.pingTimeout = null;\n }\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n *\n * Override this method in a subclass to implement custom logic on connection.\n */\n protected onOpen() {\n // Custom logic for connection open\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * @param data - The data received from the WebSocket server.\n *\n * Override this method in a subclass to implement custom message handling.\n */\n protected onMessage(_data: WebSocket.Data) {\n // Custom logic for handling received messages\n }\n\n /**\n * Called when a WebSocket error occurs.\n *\n * @param error - The error that occurred.\n *\n * Override this method in a subclass to implement custom error handling.\n * Note: Service switching is now handled in the reconnection logic, not here.\n */\n protected onError(_error: Error) {\n // Custom logic for handling errors - override in subclasses\n // Service switching is handled automatically in scheduleReconnect()\n }\n\n /**\n * Called when the WebSocket connection is closed.\n *\n * Override this method in a subclass to implement custom logic on disconnection.\n */\n protected onClose() {\n // Custom logic for handling connection close\n }\n\n /**\n * Sends data to the connected WebSocket server, if the connection is open.\n *\n * @param data - The data to send.\n */\n public send(data: string | Buffer | ArrayBuffer | Buffer[]) {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(data);\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n */\n public close() {\n this.shouldReconnect = false;\n this.stopHeartbeat();\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.ws) {\n this.ws.close();\n }\n\n // Unregister health check when closing\n healthMonitor.unregisterHealthCheck(this.healthCheckName);\n }\n\n public getConnectionState(): string {\n if (!this.ws) return \"DISCONNECTED\";\n\n switch (this.ws.readyState) {\n case WebSocket.CONNECTING:\n return \"CONNECTING\";\n case WebSocket.OPEN:\n return \"CONNECTED\";\n case WebSocket.CLOSING:\n return \"CLOSING\";\n case WebSocket.CLOSED:\n return \"DISCONNECTED\";\n default:\n return \"UNKNOWN\";\n }\n }\n\n public getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n public getServiceCycles(): number {\n return this.serviceCycles;\n }\n\n public getServiceIndex(): number {\n return this.serviceIndex;\n }\n\n public getAllServices(): string[] {\n return Array.isArray(this.service) ? [...this.service] : [this.service];\n }\n\n public getCurrentService(): string {\n return Array.isArray(this.service) ? this.service[this.serviceIndex] : this.service;\n }\n\n public getMessageCount(): number {\n return this.messageCount;\n }\n\n public getLastMessageTime(): number {\n return this.lastMessageTime;\n }\n\n public getHealthCheckName(): string {\n return this.healthCheckName;\n }\n}\n","import { Logger } from \"./logger\";\n\nexport interface HealthStatus {\n healthy: boolean;\n timestamp: number;\n checks: Record<string, boolean>;\n metrics: Record<string, number>;\n details?: Record<string, unknown>;\n}\n\nexport interface HealthCheckOptions {\n interval?: number; // milliseconds\n timeout?: number; // milliseconds\n retries?: number;\n}\n\n/**\n * Health monitoring system for bot components.\n * Provides health checks and basic metrics collection.\n */\nexport class HealthMonitor {\n private checks = new Map<string, () => Promise<boolean>>();\n private metrics = new Map<string, number>();\n private lastCheckResults = new Map<string, boolean>();\n private checkInterval: NodeJS.Timeout | null = null;\n private options: Required<HealthCheckOptions>;\n\n constructor(options: HealthCheckOptions = {}) {\n this.options = {\n interval: options.interval || 30000, // 30 seconds\n timeout: options.timeout || 5000, // 5 seconds\n retries: options.retries || 2,\n };\n }\n\n /**\n * Register a health check function.\n * @param name - Unique name for the health check\n * @param checkFn - Function that returns true if healthy\n */\n registerHealthCheck(name: string, checkFn: () => Promise<boolean>) {\n this.checks.set(name, checkFn);\n Logger.debug(`Registered health check: ${name}`);\n }\n\n /**\n * Remove a health check.\n * @param name - Name of the health check to remove\n */\n unregisterHealthCheck(name: string) {\n this.checks.delete(name);\n this.lastCheckResults.delete(name);\n Logger.debug(`Unregistered health check: ${name}`);\n }\n\n /**\n * Set a metric value.\n * @param name - Metric name\n * @param value - Metric value\n */\n setMetric(name: string, value: number) {\n this.metrics.set(name, value);\n }\n\n /**\n * Increment a counter metric.\n * @param name - Metric name\n * @param increment - Value to add (default: 1)\n */\n incrementMetric(name: string, increment = 1) {\n const current = this.metrics.get(name) || 0;\n this.metrics.set(name, current + increment);\n }\n\n /**\n * Get current metric value.\n * @param name - Metric name\n * @returns Current value or 0 if not found\n */\n getMetric(name: string): number {\n return this.metrics.get(name) || 0;\n }\n\n /**\n * Get all current metrics.\n * @returns Object with all metrics\n */\n getAllMetrics(): Record<string, number> {\n return Object.fromEntries(this.metrics);\n }\n\n /**\n * Run a single health check with timeout and retries.\n * @private\n */\n private async runHealthCheck(name: string, checkFn: () => Promise<boolean>): Promise<boolean> {\n for (let attempt = 0; attempt <= this.options.retries; attempt++) {\n try {\n const result = await this.withTimeout(checkFn(), this.options.timeout);\n if (result) {\n return true;\n }\n } catch (error) {\n Logger.debug(\n `Health check \"${name}\" failed (attempt ${attempt + 1}/${this.options.retries + 1}):`,\n { error: error.message }\n );\n }\n }\n return false;\n }\n\n /**\n * Wrap a promise with a timeout.\n * @private\n */\n private withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs)\n ),\n ]);\n }\n\n /**\n * Run all health checks and return the current health status.\n */\n async getHealthStatus(): Promise<HealthStatus> {\n const timestamp = Date.now();\n const checkResults: Record<string, boolean> = {};\n const details: Record<string, unknown> = {};\n\n // Run all health checks\n const checkPromises = Array.from(this.checks.entries()).map(async ([name, checkFn]) => {\n const result = await this.runHealthCheck(name, checkFn);\n checkResults[name] = result;\n this.lastCheckResults.set(name, result);\n\n if (!result) {\n details[`${name}_last_failure`] = new Date().toISOString();\n }\n\n return result;\n });\n\n await Promise.allSettled(checkPromises);\n\n // Determine overall health\n const healthy = Object.values(checkResults).every(result => result);\n\n // Get current metrics\n const metrics = this.getAllMetrics();\n\n return {\n healthy,\n timestamp,\n checks: checkResults,\n metrics,\n details,\n };\n }\n\n /**\n * Start periodic health monitoring.\n */\n start() {\n if (this.checkInterval) {\n this.stop();\n }\n\n Logger.info(`Starting health monitor with ${this.options.interval}ms interval`);\n\n this.checkInterval = setInterval(async () => {\n try {\n const status = await this.getHealthStatus();\n\n if (!status.healthy) {\n const failedChecks = Object.entries(status.checks)\n .filter(([, healthy]) => !healthy)\n .map(([name]) => name);\n\n Logger.warn(`Health check failed`, {\n operation: \"health_check\",\n failed_checks: failedChecks,\n metrics: status.metrics,\n });\n } else {\n Logger.debug(\"Health check passed\", {\n operation: \"health_check\",\n metrics: status.metrics,\n });\n }\n } catch (error) {\n Logger.error(\"Error during health check:\", { error: error.message });\n }\n }, this.options.interval);\n }\n\n /**\n * Stop periodic health monitoring.\n */\n stop() {\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n Logger.info(\"Stopped health monitor\");\n }\n }\n\n /**\n * Get a summary of the last health check results.\n */\n getLastCheckSummary(): Record<string, boolean> {\n return Object.fromEntries(this.lastCheckResults);\n }\n}\n\n// Global health monitor instance\nexport const healthMonitor = new HealthMonitor();\n","import WebSocket from \"ws\";\nimport { WebSocketClient } from \"./websocketClient\";\nimport { Logger } from \"./logger\";\n\n/**\n * Represents a subscription to a Jetstream feed over WebSocket.\n *\n * This class extends `WebSocketClient` to automatically handle reconnections and heartbeats.\n * It invokes a provided callback function whenever a message is received from the Jetstream server.\n */\nexport class JetstreamSubscription extends WebSocketClient {\n /**\n * Creates a new `JetstreamSubscription`.\n *\n * @param service - The URL(-Array) of the Jetstream server(s) to connect to.\n * @param interval - The interval (in milliseconds) for reconnect attempts.\n * @param onMessageCallback - An optional callback function that is invoked whenever a message is received from the server.\n */\n constructor(\n service: string | string[],\n public interval: number,\n private onMessageCallback?: (data: WebSocket.Data) => void\n ) {\n super({ service, reconnectInterval: interval });\n }\n\n /**\n * Called when the WebSocket connection is successfully opened.\n * Logs a message indicating that the connection to the Jetstream server has been established.\n */\n protected onOpen() {\n Logger.info(\"Connected to Jetstream server.\");\n super.onOpen();\n }\n\n /**\n * Called when a WebSocket message is received.\n *\n * If an `onMessageCallback` was provided, it is invoked with the received data.\n *\n * @param data - The data received from the Jetstream server.\n */\n protected onMessage(data: WebSocket.Data) {\n if (this.onMessageCallback) {\n this.onMessageCallback(data);\n }\n }\n\n /**\n * Called when a WebSocket error occurs.\n * Logs the error message indicating that Jetstream encountered an error.\n *\n * @param error - The error that occurred.\n */\n protected onError(error: Error) {\n Logger.error(\"Jetstream encountered an error:\", error);\n super.onError(error);\n }\n\n /**\n * Called when the WebSocket connection is closed.\n * Logs a message indicating that the Jetstream connection has closed.\n */\n protected onClose() {\n Logger.info(\"Jetstream connection closed.\");\n super.onClose();\n }\n}\n","/**\n * Returns the given string if it is defined; otherwise returns `undefined`.\n *\n * @param val - The optional string value to check.\n * @returns The given string if defined, or `undefined` if `val` is falsy.\n */\nexport const maybeStr = (val?: string): string | undefined => {\n if (!val) return undefined;\n return val;\n};\n\n/**\n * Parses the given string as an integer if it is defined and a valid integer; otherwise returns `undefined`.\n *\n * @param val - The optional string value to parse.\n * @returns The parsed integer if successful, or `undefined` if the string is falsy or not a valid integer.\n */\nexport const maybeInt = (val?: string): number | undefined => {\n if (!val) return undefined;\n const int = parseInt(val, 10);\n if (isNaN(int)) return undefined;\n return int;\n};\n","import WebSocket from \"ws\";\nimport { Post } from \"../types/post\";\nimport { WebsocketMessage } from \"../types/message\";\n/**\n * Converts a raw WebSocket message into a `FeedEntry` object, if possible.\n *\n * This function checks if the incoming WebSocket data is structured like a feed commit message\n * with the required properties for a created post. If the data matches the expected shape,\n * it extracts and returns a `FeedEntry` object. Otherwise, it returns `null`.\n *\n * @param data - The raw WebSocket data.\n * @returns A `FeedEntry` object if the data represents a newly created post, otherwise `null`.\n */\nexport function websocketToFeedEntry(data: WebSocket.Data): Post | null {\n const message = JSON.parse(data.toString()) as WebsocketMessage;\n if (\n !message.commit ||\n !message.commit.record ||\n !message.commit.record[\"$type\"] ||\n !message.did ||\n !message.commit.cid ||\n !message.commit.rkey ||\n message.commit.operation !== \"create\"\n ) {\n return null;\n }\n const messageUri = `at://${message.did}/${message.commit.record[\"$type\"]}/${message.commit.rkey}`;\n return {\n cid: message.commit.cid,\n uri: messageUri,\n authorDid: message.did,\n text: message.commit.record.text,\n rootCid: message.commit.record.reply?.root.cid ?? message.commit.cid,\n rootUri: message.commit.record.reply?.root.uri ?? messageUri,\n };\n}\n"],"mappings":"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","names":["LogLevel"]}
+1
-1
package.json
+1
-1
package.json
+1
-1
src/utils/wsToFeed.ts
+1
-1
src/utils/wsToFeed.ts
···
12
12
* @returns A `FeedEntry` object if the data represents a newly created post, otherwise `null`.
13
13
*/
14
14
export function websocketToFeedEntry(data: WebSocket.Data): Post | null {
15
-
const message = data as WebsocketMessage;
15
+
const message = JSON.parse(data.toString()) as WebsocketMessage;
16
16
if (
17
17
!message.commit ||
18
18
!message.commit.record ||