Skip to content

Commit bbc5903

Browse files
authored
Merge pull request #17 from elizaos-plugins/feat/telegram-use-message-service
feat: migrate to runtime.handleMessage()
2 parents 07d8cd6 + 0c887c5 commit bbc5903

2 files changed

Lines changed: 76 additions & 61 deletions

File tree

src/environment.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ export type TelegramConfig = z.infer<typeof telegramEnvSchema>;
1717
* @param {IAgentRuntime} runtime - The agent runtime used to get the setting.
1818
* @returns {Promise<TelegramConfig | null>} A promise that resolves with the validated Telegram configuration or null if invalid.
1919
*/
20-
export async function validateTelegramConfig(runtime: IAgentRuntime): Promise<TelegramConfig | null> {
20+
export async function validateTelegramConfig(
21+
runtime: IAgentRuntime
22+
): Promise<TelegramConfig | null> {
2123
try {
2224
const config = {
2325
TELEGRAM_BOT_TOKEN:
@@ -27,7 +29,7 @@ export async function validateTelegramConfig(runtime: IAgentRuntime): Promise<Te
2729
return telegramEnvSchema.parse(config);
2830
} catch (error) {
2931
if (error instanceof z.ZodError) {
30-
const errorMessages = error.errors
32+
const errorMessages = error.issues
3133
.map((err) => `${err.path.join('.')}: ${err.message}`)
3234
.join('\n');
3335
console.warn(`Telegram configuration validation failed:\n${errorMessages}`);

src/messageManager.ts

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { Markup } from 'telegraf';
1818
import {
1919
type TelegramContent,
2020
TelegramEventTypes,
21-
type TelegramMessageReceivedPayload,
2221
type TelegramMessageSentPayload,
2322
type TelegramReactionReceivedPayload,
2423
} from './types';
@@ -102,7 +101,11 @@ export class MessageManager {
102101
const photo = message.photo[message.photo.length - 1];
103102
const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
104103
imageUrl = fileLink.toString();
105-
} else if ('document' in message && message.document?.mime_type?.startsWith('image/') && !message.document?.mime_type?.startsWith('application/pdf')) {
104+
} else if (
105+
'document' in message &&
106+
message.document?.mime_type?.startsWith('image/') &&
107+
!message.document?.mime_type?.startsWith('application/pdf')
108+
) {
106109
const fileLink = await this.bot.telegram.getFileLink(message.document.file_id);
107110
imageUrl = fileLink.toString();
108111
}
@@ -128,19 +131,19 @@ export class MessageManager {
128131
* @param {Message} message - The Telegram message object containing the document.
129132
* @returns {Promise<{ description: string } | null>} The description of the processed document or null if no document found.
130133
*/
131-
async processDocument(
132-
message: Message,
133-
): Promise<DocumentProcessingResult | null> {
134+
async processDocument(message: Message): Promise<DocumentProcessingResult | null> {
134135
try {
135-
if (!("document" in message) || !message.document) {
136+
if (!('document' in message) || !message.document) {
136137
return null;
137138
}
138139

139140
const document = message.document;
140141
const fileLink = await this.bot.telegram.getFileLink(document.file_id);
141142
const documentUrl = fileLink.toString();
142143

143-
logger.info(`Processing document: ${document.file_name} (${document.mime_type}, ${document.file_size} bytes)`);
144+
logger.info(
145+
`Processing document: ${document.file_name} (${document.mime_type}, ${document.file_size} bytes)`
146+
);
144147

145148
// Centralized document processing based on MIME type
146149
const documentProcessor = this.getDocumentProcessor(document.mime_type);
@@ -151,29 +154,33 @@ export class MessageManager {
151154
// Generic fallback for unsupported types
152155
return {
153156
title: `Document: ${document.file_name || 'Unknown Document'}`,
154-
fullText: "",
157+
fullText: '',
155158
formattedDescription: `[Document: ${document.file_name || 'Unknown Document'}\nType: ${document.mime_type || 'unknown'}\nSize: ${document.file_size || 0} bytes]`,
156159
fileName: document.file_name || 'Unknown Document',
157160
mimeType: document.mime_type,
158161
fileSize: document.file_size,
159162
};
160-
161163
} catch (error) {
162-
logger.error("Error processing document:", error);
164+
logger.error(
165+
'Error processing document:',
166+
error instanceof Error ? error.message : String(error)
167+
);
163168
return null;
164169
}
165170
}
166171

167172
/**
168173
* Get the appropriate document processor based on MIME type.
169174
*/
170-
private getDocumentProcessor(mimeType?: string): ((document: Document, url: string) => Promise<DocumentProcessingResult>) | null {
175+
private getDocumentProcessor(
176+
mimeType?: string
177+
): ((document: Document, url: string) => Promise<DocumentProcessingResult>) | null {
171178
if (!mimeType) return null;
172179

173180
const processors = {
174-
"application/pdf": this.processPdfDocument.bind(this),
175-
"text/": this.processTextDocument.bind(this), // covers text/plain, text/csv, text/markdown, etc.
176-
"application/json": this.processTextDocument.bind(this),
181+
'application/pdf': this.processPdfDocument.bind(this),
182+
'text/': this.processTextDocument.bind(this), // covers text/plain, text/csv, text/markdown, etc.
183+
'application/json': this.processTextDocument.bind(this),
177184
};
178185

179186
for (const [pattern, processor] of Object.entries(processors)) {
@@ -188,14 +195,17 @@ export class MessageManager {
188195
/**
189196
* Process PDF documents by converting them to text.
190197
*/
191-
private async processPdfDocument(document: Document, documentUrl: string): Promise<DocumentProcessingResult> {
198+
private async processPdfDocument(
199+
document: Document,
200+
documentUrl: string
201+
): Promise<DocumentProcessingResult> {
192202
try {
193203
const pdfService = this.runtime.getService(ServiceType.PDF) as any;
194204
if (!pdfService) {
195-
logger.warn("PDF service not available, using fallback");
205+
logger.warn('PDF service not available, using fallback');
196206
return {
197207
title: `PDF Document: ${document.file_name || 'Unknown Document'}`,
198-
fullText: "",
208+
fullText: '',
199209
formattedDescription: `[PDF Document: ${document.file_name || 'Unknown Document'}\nSize: ${document.file_size || 0} bytes\nUnable to extract text content]`,
200210
fileName: document.file_name || 'Unknown Document',
201211
mimeType: document.mime_type,
@@ -211,7 +221,6 @@ export class MessageManager {
211221
const pdfBuffer = await response.arrayBuffer();
212222
const text = await pdfService.convertPdfToText(Buffer.from(pdfBuffer));
213223

214-
215224
logger.info(`PDF processed successfully: ${text.length} characters extracted`);
216225
return {
217226
title: document.file_name || 'Unknown Document',
@@ -221,12 +230,14 @@ export class MessageManager {
221230
mimeType: document.mime_type,
222231
fileSize: document.file_size,
223232
};
224-
225233
} catch (error) {
226-
logger.error("Error processing PDF document:", error);
234+
logger.error(
235+
'Error processing PDF document:',
236+
error instanceof Error ? error.message : String(error)
237+
);
227238
return {
228239
title: `PDF Document: ${document.file_name || 'Unknown Document'}`,
229-
fullText: "",
240+
fullText: '',
230241
formattedDescription: `[PDF Document: ${document.file_name || 'Unknown Document'}\nSize: ${document.file_size || 0} bytes\nError: Unable to extract text content]`,
231242
fileName: document.file_name || 'Unknown Document',
232243
mimeType: document.mime_type,
@@ -236,9 +247,12 @@ export class MessageManager {
236247
}
237248

238249
/**
239-
* Process text documents by fetching their content.
240-
*/
241-
private async processTextDocument(document: Document, documentUrl: string): Promise<DocumentProcessingResult> {
250+
* Process text documents by fetching their content.
251+
*/
252+
private async processTextDocument(
253+
document: Document,
254+
documentUrl: string
255+
): Promise<DocumentProcessingResult> {
242256
try {
243257
const response = await fetch(documentUrl);
244258
if (!response.ok) {
@@ -256,12 +270,14 @@ export class MessageManager {
256270
mimeType: document.mime_type,
257271
fileSize: document.file_size,
258272
};
259-
260273
} catch (error) {
261-
logger.error("Error processing text document:", error);
274+
logger.error(
275+
'Error processing text document:',
276+
error instanceof Error ? error.message : String(error)
277+
);
262278
return {
263279
title: `Text Document: ${document.file_name || 'Unknown Document'}`,
264-
fullText: "",
280+
fullText: '',
265281
formattedDescription: `[Text Document: ${document.file_name || 'Unknown Document'}\nSize: ${document.file_size || 0} bytes\nError: Unable to read content]`,
266282
fileName: document.file_name || 'Unknown Document',
267283
mimeType: document.mime_type,
@@ -280,18 +296,18 @@ export class MessageManager {
280296
async processMessage(
281297
message: Message
282298
): Promise<{ processedContent: string; attachments: Media[] }> {
283-
let processedContent = "";
299+
let processedContent = '';
284300
let attachments: Media[] = [];
285301

286302
// Get message text
287-
if ("text" in message && message.text) {
303+
if ('text' in message && message.text) {
288304
processedContent = message.text;
289-
} else if ("caption" in message && message.caption) {
305+
} else if ('caption' in message && message.caption) {
290306
processedContent = message.caption as string;
291307
}
292308

293309
// Process documents
294-
if ("document" in message && message.document) {
310+
if ('document' in message && message.document) {
295311
const document = message.document;
296312
const documentInfo = await this.processDocument(message);
297313

@@ -313,19 +329,22 @@ export class MessageManager {
313329
id: document.file_id,
314330
url: fileLink.toString(),
315331
title: title,
316-
source: document.mime_type?.startsWith("application/pdf") ? "PDF" : "Document",
332+
source: document.mime_type?.startsWith('application/pdf') ? 'PDF' : 'Document',
317333
description: documentInfo.formattedDescription,
318334
text: fullText,
319335
});
320336
logger.info(`Document processed successfully: ${documentInfo.fileName}`);
321337
} catch (error) {
322-
logger.error(`Error processing document ${documentInfo.fileName}:`, error);
338+
logger.error(
339+
`Error processing document ${documentInfo.fileName}:`,
340+
error instanceof Error ? error.message : String(error)
341+
);
323342
// Add a fallback attachment even if processing failed
324343
attachments.push({
325344
id: document.file_id,
326-
url: "",
345+
url: '',
327346
title: `Document: ${documentInfo.fileName}`,
328-
source: "Document",
347+
source: 'Document',
329348
description: `Document processing failed: ${documentInfo.fileName}`,
330349
text: `Document: ${documentInfo.fileName}\nSize: ${documentInfo.fileSize || 0} bytes\nType: ${documentInfo.mimeType || 'unknown'}`,
331350
});
@@ -334,33 +353,35 @@ export class MessageManager {
334353
// Add a basic attachment even if documentInfo is null
335354
attachments.push({
336355
id: document.file_id,
337-
url: "",
356+
url: '',
338357
title: `Document: ${document.file_name || 'Unknown Document'}`,
339-
source: "Document",
358+
source: 'Document',
340359
description: `Document: ${document.file_name || 'Unknown Document'}`,
341360
text: `Document: ${document.file_name || 'Unknown Document'}\nSize: ${document.file_size || 0} bytes\nType: ${document.mime_type || 'unknown'}`,
342361
});
343362
}
344363
}
345364

346365
// Process images
347-
if ("photo" in message && message.photo?.length > 0) {
366+
if ('photo' in message && message.photo?.length > 0) {
348367
const imageInfo = await this.processImage(message);
349368
if (imageInfo) {
350369
const photo = message.photo[message.photo.length - 1];
351370
const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
352371
attachments.push({
353372
id: photo.file_id,
354373
url: fileLink.toString(),
355-
title: "Image Attachment",
356-
source: "Image",
374+
title: 'Image Attachment',
375+
source: 'Image',
357376
description: imageInfo.description,
358377
text: imageInfo.description,
359378
});
360379
}
361380
}
362381

363-
logger.info(`Message processed - Content: ${processedContent ? 'yes' : 'no'}, Attachments: ${attachments.length}`);
382+
logger.info(
383+
`Message processed - Content: ${processedContent ? 'yes' : 'no'}, Attachments: ${attachments.length}`
384+
);
364385

365386
return { processedContent, attachments };
366387
}
@@ -570,7 +591,7 @@ export class MessageManager {
570591

571592
// Clean processedContent and attachments to avoid NULL characters
572593
const cleanedContent = cleanText(processedContent);
573-
const cleanedAttachments = attachments.map(att => ({
594+
const cleanedAttachments = attachments.map((att) => ({
574595
...att,
575596
text: cleanText(att.text),
576597
description: cleanText(att.description),
@@ -683,23 +704,15 @@ export class MessageManager {
683704
}
684705
};
685706

686-
// Let the bootstrap plugin handle the message
687-
this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
688-
runtime: this.runtime,
689-
message: memory,
690-
callback,
691-
source: 'telegram',
692-
});
693-
694-
// Also emit the platform-specific event
695-
this.runtime.emitEvent(TelegramEventTypes.MESSAGE_RECEIVED, {
696-
runtime: this.runtime,
697-
message: memory,
698-
callback,
699-
source: 'telegram',
700-
ctx,
701-
originalMessage: message,
702-
} as TelegramMessageReceivedPayload);
707+
// Call the message handler directly instead of emitting events
708+
// This provides a clearer, more traceable flow for message processing
709+
if (!this.runtime.messageService) {
710+
logger.error('Message service is not available');
711+
throw new Error(
712+
'Message service is not initialized. Ensure the message service is properly configured.'
713+
);
714+
}
715+
await this.runtime.messageService.handleMessage(this.runtime, memory, callback);
703716
} catch (error) {
704717
logger.error(
705718
{

0 commit comments

Comments
 (0)