Skip to content

Commit afaa80c

Browse files
authored
Merge pull request #7 from elizaos-plugins/fix/telegram-graceful-token-handling
2 parents db2f2eb + b8398e5 commit afaa80c

6 files changed

Lines changed: 91 additions & 64 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elizaos/plugin-telegram",
3-
"version": "1.0.5",
3+
"version": "1.0.6",
44
"type": "module",
55
"main": "dist/index.js",
66
"module": "dist/index.js",
@@ -18,7 +18,7 @@
1818
"dist"
1919
],
2020
"dependencies": {
21-
"@elizaos/core": "^1.0.0",
21+
"@elizaos/core": "^1.0.19",
2222
"@telegraf/types": "7.1.0",
2323
"strip-literal": "^3.0.0",
2424
"telegraf": "4.16.3",

src/environment.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ export type TelegramConfig = z.infer<typeof telegramEnvSchema>;
1212

1313
/**
1414
* Validates the Telegram configuration by retrieving the Telegram bot token from the runtime settings or environment variables.
15+
* Returns null if validation fails instead of throwing an error.
1516
*
1617
* @param {IAgentRuntime} runtime - The agent runtime used to get the setting.
17-
* @returns {Promise<TelegramConfig>} A promise that resolves with the validated Telegram configuration.
18+
* @returns {Promise<TelegramConfig | null>} A promise that resolves with the validated Telegram configuration or null if invalid.
1819
*/
19-
export async function validateTelegramConfig(runtime: IAgentRuntime): Promise<TelegramConfig> {
20+
export async function validateTelegramConfig(runtime: IAgentRuntime): Promise<TelegramConfig | null> {
2021
try {
2122
const config = {
2223
TELEGRAM_BOT_TOKEN:
@@ -29,8 +30,8 @@ export async function validateTelegramConfig(runtime: IAgentRuntime): Promise<Te
2930
const errorMessages = error.errors
3031
.map((err) => `${err.path.join('.')}: ${err.message}`)
3132
.join('\n');
32-
throw new Error(`Telegram configuration validation failed:\n${errorMessages}`);
33+
console.warn(`Telegram configuration validation failed:\n${errorMessages}`);
3334
}
34-
throw error;
35+
return null;
3536
}
3637
}

src/service.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
import { type Context, Telegraf } from 'telegraf';
1717
import { type ChatMemberOwner, type ChatMemberAdministrator, type User } from 'telegraf/types';
1818
import { TELEGRAM_SERVICE_NAME } from './constants';
19-
import { validateTelegramConfig } from './environment';
2019
import { MessageManager } from './messageManager';
2120
import { TelegramEventTypes, type TelegramWorldPayload } from './types';
2221

@@ -34,8 +33,8 @@ import { TelegramEventTypes, type TelegramWorldPayload } from './types';
3433
export class TelegramService extends Service {
3534
static serviceType = TELEGRAM_SERVICE_NAME;
3635
capabilityDescription = 'The agent is able to send and receive messages on telegram';
37-
private bot: Telegraf<Context>;
38-
public messageManager: MessageManager;
36+
private bot: Telegraf<Context> | null;
37+
public messageManager: MessageManager | null;
3938
private options;
4039
private knownChats: Map<string, any> = new Map();
4140
private syncedEntityIds: Set<string> = new Set<string>();
@@ -47,6 +46,16 @@ export class TelegramService extends Service {
4746
constructor(runtime: IAgentRuntime) {
4847
super(runtime);
4948
logger.log('📱 Constructing new TelegramService...');
49+
50+
// Check if Telegram bot token is available and valid
51+
const botToken = runtime.getSetting('TELEGRAM_BOT_TOKEN') as string;
52+
if (!botToken || botToken.trim() === '') {
53+
logger.warn('Telegram Bot Token not provided - Telegram functionality will be unavailable');
54+
this.bot = null;
55+
this.messageManager = null;
56+
return;
57+
}
58+
5059
this.options = {
5160
telegram: {
5261
apiRoot:
@@ -55,10 +64,16 @@ export class TelegramService extends Service {
5564
'https://api.telegram.org',
5665
},
5766
};
58-
const botToken = runtime.getSetting('TELEGRAM_BOT_TOKEN');
59-
this.bot = new Telegraf(botToken, this.options);
60-
this.messageManager = new MessageManager(this.bot, this.runtime);
61-
logger.log('✅ TelegramService constructor completed');
67+
68+
try {
69+
this.bot = new Telegraf(botToken, this.options);
70+
this.messageManager = new MessageManager(this.bot, this.runtime);
71+
logger.log('✅ TelegramService constructor completed');
72+
} catch (error) {
73+
logger.error(`Error initializing Telegram bot: ${error instanceof Error ? error.message : String(error)}`);
74+
this.bot = null;
75+
this.messageManager = null;
76+
}
6277
}
6378

6479
/**
@@ -68,16 +83,22 @@ export class TelegramService extends Service {
6883
* @returns {Promise<TelegramService>} A promise that resolves with the initialized TelegramService.
6984
*/
7085
static async start(runtime: IAgentRuntime): Promise<TelegramService> {
71-
await validateTelegramConfig(runtime);
86+
// Remove validateTelegramConfig call to allow service to start without token
87+
88+
const service = new TelegramService(runtime);
89+
90+
// If bot is not initialized (no token), return the service without further initialization
91+
if (!service.bot) {
92+
logger.warn('Telegram service started without bot functionality - no bot token provided');
93+
return service;
94+
}
7295

7396
const maxRetries = 5;
7497
let retryCount = 0;
7598
let lastError: Error | null = null;
7699

77100
while (retryCount < maxRetries) {
78101
try {
79-
const service = new TelegramService(runtime);
80-
81102
logger.success(
82103
`✅ Telegram client successfully started for character ${runtime.character.name}`
83104
);
@@ -92,7 +113,7 @@ export class TelegramService extends Service {
92113
service.setupMessageHandlers();
93114

94115
// Wait for bot to be ready by testing getMe()
95-
await service.bot.telegram.getMe();
116+
await service.bot!.telegram.getMe();
96117

97118
return service;
98119
} catch (error) {
@@ -110,9 +131,12 @@ export class TelegramService extends Service {
110131
}
111132
}
112133

113-
throw new Error(
114-
`Telegram initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}`
134+
logger.error(
135+
`Telegram initialization failed after ${maxRetries} attempts. Last error: ${lastError?.message}. Service will continue without Telegram functionality.`
115136
);
137+
138+
// Return the service even if initialization failed, to prevent server crash
139+
return service;
116140
}
117141

118142
/**
@@ -133,32 +157,32 @@ export class TelegramService extends Service {
133157
* @returns A Promise that resolves once the bot has stopped.
134158
*/
135159
async stop(): Promise<void> {
136-
this.bot.stop();
160+
this.bot?.stop();
137161
}
138162

139163
/**
140164
* Initializes the Telegram bot by launching it, getting bot info, and setting up message manager.
141165
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
142166
*/
143167
private async initializeBot(): Promise<void> {
144-
this.bot.start(ctx => {
168+
this.bot?.start(ctx => {
145169
this.runtime.emitEvent([TelegramEventTypes.SLASH_START], {
146170
// we don't need this
147171
ctx,
148172
});
149173
});
150-
this.bot.launch({
174+
this.bot?.launch({
151175
dropPendingUpdates: true,
152176
allowedUpdates: ['message', 'message_reaction'],
153177
});
154178

155179
// Get bot info for identification purposes
156-
const botInfo = await this.bot.telegram.getMe();
180+
const botInfo = await this.bot!.telegram.getMe();
157181
logger.log(`Bot info: ${JSON.stringify(botInfo)}`);
158182

159183
// Handle sigint and sigterm signals to gracefully stop the bot
160-
process.once('SIGINT', () => this.bot.stop('SIGINT'));
161-
process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
184+
process.once('SIGINT', () => this.bot?.stop('SIGINT'));
185+
process.once('SIGTERM', () => this.bot?.stop('SIGTERM'));
162186
}
163187

164188
/**
@@ -178,10 +202,10 @@ export class TelegramService extends Service {
178202
*/
179203
private setupMiddlewares(): void {
180204
// Register the authorization middleware
181-
this.bot.use(this.authorizationMiddleware.bind(this));
205+
this.bot?.use(this.authorizationMiddleware.bind(this));
182206

183207
// Register the chat and entity management middleware
184-
this.bot.use(this.chatAndEntityMiddleware.bind(this));
208+
this.bot?.use(this.chatAndEntityMiddleware.bind(this));
185209
}
186210

187211
/**
@@ -267,19 +291,19 @@ export class TelegramService extends Service {
267291
*/
268292
private setupMessageHandlers(): void {
269293
// Regular message handler
270-
this.bot.on('message', async (ctx) => {
294+
this.bot?.on('message', async (ctx) => {
271295
try {
272296
// Message handling is now simplified since all preprocessing is done by middleware
273-
await this.messageManager.handleMessage(ctx);
297+
await this.messageManager!.handleMessage(ctx);
274298
} catch (error) {
275299
logger.error('Error handling message:', error);
276300
}
277301
});
278302

279303
// Reaction handler
280-
this.bot.on('message_reaction', async (ctx) => {
304+
this.bot?.on('message_reaction', async (ctx) => {
281305
try {
282-
await this.messageManager.handleReaction(ctx);
306+
await this.messageManager!.handleReaction(ctx);
283307
} catch (error) {
284308
logger.error('Error handling reaction:', error);
285309
}
@@ -634,7 +658,7 @@ export class TelegramService extends Service {
634658
entities,
635659
source: 'telegram',
636660
chat,
637-
botUsername: this.bot.botInfo!.username,
661+
botUsername: this.bot?.botInfo?.username,
638662
};
639663

640664
// Emit telegram-specific world joined event
@@ -795,7 +819,7 @@ export class TelegramService extends Service {
795819
// For groups and supergroups, try to get member information
796820
try {
797821
// Get chat administrators (this is what's available through the Bot API)
798-
const admins = await this.bot.telegram.getChatAdministrators(chat.id);
822+
const admins = await this.bot?.telegram.getChatAdministrators(chat.id);
799823

800824
if (admins && admins.length > 0) {
801825
for (const admin of admins) {
@@ -912,12 +936,14 @@ export class TelegramService extends Service {
912936
}
913937

914938
static registerSendHandlers(runtime: IAgentRuntime, serviceInstance: TelegramService) {
915-
if (serviceInstance) {
939+
if (serviceInstance && serviceInstance.bot) {
916940
runtime.registerSendHandler(
917941
'telegram',
918942
serviceInstance.handleSendMessage.bind(serviceInstance)
919943
);
920944
logger.info('[Telegram] Registered send handler.');
945+
} else {
946+
logger.warn('[Telegram] Cannot register send handler - bot not initialized.');
921947
}
922948
}
923949

@@ -926,6 +952,12 @@ export class TelegramService extends Service {
926952
target: TargetInfo,
927953
content: Content
928954
): Promise<void> {
955+
// Check if bot and messageManager are available
956+
if (!this.bot || !this.messageManager) {
957+
logger.error('[Telegram SendHandler] Bot not initialized - cannot send messages.');
958+
throw new Error('Telegram bot is not initialized. Please provide TELEGRAM_BOT_TOKEN.');
959+
}
960+
929961
let chatId: number | string | undefined;
930962

931963
// Determine the target chat ID

src/tests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ export class TelegramTestSuite implements TestSuite {
101101

102102
async testCreatingTelegramBot(runtime: IAgentRuntime) {
103103
this.telegramClient = runtime.getService('telegram') as TelegramService;
104+
if (!this.telegramClient || !this.telegramClient.messageManager) {
105+
throw new Error('Telegram service or message manager not initialized - check TELEGRAM_BOT_TOKEN');
106+
}
104107
this.bot = this.telegramClient.messageManager.bot;
105108
this.messageManager = this.telegramClient.messageManager;
106109
logger.debug('Telegram bot initialized successfully.');

src/types.ts

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,6 @@ export enum TelegramEventTypes {
3131
WORLD_CONNECTED = 'TELEGRAM_WORLD_CONNECTED',
3232
WORLD_LEFT = 'TELEGRAM_WORLD_LEFT',
3333

34-
export interface TelegramEventPayloadMap {
35-
[TelegramEventTypes.MESSAGE_RECEIVED]: TelegramMessageReceivedPayload;
36-
[TelegramEventTypes.MESSAGE_SENT]: TelegramMessageSentPayload;
37-
[TelegramEventTypes.REACTION_RECEIVED]: TelegramReactionReceivedPayload;
38-
[TelegramEventTypes.WORLD_JOINED]: TelegramWorldPayload;
39-
[TelegramEventTypes.WORLD_CONNECTED]: TelegramWorldPayload;
40-
[TelegramEventTypes.WORLD_LEFT]: TelegramWorldPayload;
41-
[TelegramEventTypes.SLASH_START]: { ctx: Context };
42-
[TelegramEventTypes.ENTITY_JOINED]: TelegramEntityPayload;
43-
[TelegramEventTypes.ENTITY_LEFT]: TelegramEntityPayload;
44-
[TelegramEventTypes.ENTITY_UPDATED]: TelegramEntityPayload;
45-
[TelegramEventTypes.INTERACTION_RECEIVED]: TelegramReactionReceivedPayload;
46-
}
4734
// Entity events
4835
ENTITY_JOINED = 'TELEGRAM_ENTITY_JOINED',
4936
ENTITY_LEFT = 'TELEGRAM_ENTITY_LEFT',
@@ -56,6 +43,26 @@ export interface TelegramEventPayloadMap {
5643
// Interaction events
5744
REACTION_RECEIVED = 'TELEGRAM_REACTION_RECEIVED',
5845
INTERACTION_RECEIVED = 'TELEGRAM_INTERACTION_RECEIVED',
46+
47+
// Command events
48+
SLASH_START = 'TELEGRAM_SLASH_START',
49+
}
50+
51+
/**
52+
* Telegram-specific event payload map
53+
*/
54+
export interface TelegramEventPayloadMap {
55+
[TelegramEventTypes.MESSAGE_RECEIVED]: TelegramMessageReceivedPayload;
56+
[TelegramEventTypes.MESSAGE_SENT]: TelegramMessageSentPayload;
57+
[TelegramEventTypes.REACTION_RECEIVED]: TelegramReactionReceivedPayload;
58+
[TelegramEventTypes.WORLD_JOINED]: TelegramWorldPayload;
59+
[TelegramEventTypes.WORLD_CONNECTED]: TelegramWorldPayload;
60+
[TelegramEventTypes.WORLD_LEFT]: TelegramWorldPayload;
61+
[TelegramEventTypes.SLASH_START]: { ctx: Context };
62+
[TelegramEventTypes.ENTITY_JOINED]: TelegramEntityPayload;
63+
[TelegramEventTypes.ENTITY_LEFT]: TelegramEntityPayload;
64+
[TelegramEventTypes.ENTITY_UPDATED]: TelegramEntityPayload;
65+
[TelegramEventTypes.INTERACTION_RECEIVED]: TelegramReactionReceivedPayload;
5966
}
6067

6168
/**
@@ -106,19 +113,3 @@ export interface TelegramEntityPayload extends EntityPayload {
106113
first_name?: string;
107114
};
108115
}
109-
110-
/**
111-
* Maps Telegram event types to their corresponding payload types
112-
*/
113-
export interface TelegramEventPayloadMap {
114-
[TelegramEventTypes.MESSAGE_RECEIVED]: TelegramMessageReceivedPayload;
115-
[TelegramEventTypes.MESSAGE_SENT]: TelegramMessageSentPayload;
116-
[TelegramEventTypes.REACTION_RECEIVED]: TelegramReactionReceivedPayload;
117-
[TelegramEventTypes.WORLD_JOINED]: TelegramWorldPayload;
118-
[TelegramEventTypes.WORLD_CONNECTED]: TelegramWorldPayload;
119-
[TelegramEventTypes.WORLD_LEFT]: TelegramWorldPayload;
120-
[TelegramEventTypes.ENTITY_JOINED]: TelegramEntityPayload;
121-
[TelegramEventTypes.ENTITY_LEFT]: TelegramEntityPayload;
122-
[TelegramEventTypes.ENTITY_UPDATED]: TelegramEntityPayload;
123-
[TelegramEventTypes.INTERACTION_RECEIVED]: TelegramReactionReceivedPayload;
124-
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"moduleResolution": "Bundler",
99
"strict": true,
1010
"esModuleInterop": true,
11-
"skipLibCheck": false,
11+
"skipLibCheck": true,
1212
"forceConsistentCasingInFileNames": false,
1313
"allowImportingTsExtensions": true,
1414
"declaration": true,

0 commit comments

Comments
 (0)