telegram-user: harden login and monitor lifecycle

This commit is contained in:
Muhammed Mukhthar CM 2026-01-30 12:16:39 +00:00
parent c434e49907
commit a12e96b3d3
3 changed files with 87 additions and 74 deletions

View File

@ -46,6 +46,10 @@ export async function loginTelegramUser(params: {
let phoneEnv = process.env.TELEGRAM_USER_PHONE?.trim() || undefined;
const codeEnv = process.env.TELEGRAM_USER_CODE?.trim() || undefined;
const passwordPrompt = passwordEnv
? passwordEnv
: async () => await promptText("2FA password: ");
try {
if (!phoneEnv) {
const mode = await promptLoginMode();
@ -53,12 +57,12 @@ export async function loginTelegramUser(params: {
phoneEnv = await promptText("Telegram phone number (E.164): ");
}
}
const user = await client.start(
const user = await client.start(
phoneEnv
? {
phone: phoneEnv,
code: codeEnv ? codeEnv : async () => await promptText("Telegram code: "),
password: passwordEnv ? passwordEnv : async () => await promptText("2FA password: "),
password: passwordPrompt,
codeSentCallback: (code) => {
runtime.log(
`Telegram code sent via ${code.type}. Check your device and enter it here.`,
@ -84,11 +88,13 @@ export async function loginTelegramUser(params: {
runtime.log(`Scan this QR in Telegram (expires ${expires.toLocaleTimeString()}):`);
qrcode.generate(url, { small: true });
},
...(passwordEnv ? { password: passwordEnv } : {}),
password: passwordPrompt,
invalidCodeCallback: async (type) => {
if (type === "password") {
runtime.error?.(
"Telegram 2FA password rejected. Set TELEGRAM_USER_PASSWORD and rerun.",
passwordEnv
? "Telegram 2FA password rejected. Update TELEGRAM_USER_PASSWORD and rerun."
: "Telegram 2FA password rejected. Try again.",
);
}
},

View File

@ -361,8 +361,9 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
const combinedAllowFrom = [...allowFrom, ...storeAllowFrom];
const chatId = msg.chat.type === "chat" ? msg.chat.id : undefined;
const isForum = msg.chat.type === "chat" && msg.chat.isForum === true;
const isTopicMessage = msg.isTopicMessage === true;
const threadId =
isGroup && isForum ? msg.replyToMessage?.threadId ?? undefined : undefined;
isGroup && isForum && isTopicMessage ? msg.replyToMessage?.threadId ?? undefined : undefined;
const { groupConfig, topicConfig } =
isGroup && chatId != null
? resolveTelegramUserGroupConfig(accountConfig, chatId, threadId)
@ -499,7 +500,8 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
...core.channel.mentions.buildMentionRegexes(cfg, route.agentId),
...buildTelegramUserSelfMentionRegexes({ username: self?.username, name: self?.name }),
];
const hasAnyMention = msg.entities.some(
const entities = msg.entities ?? [];
const hasAnyMention = entities.some(
(ent) => ent.kind === "mention" || ent.kind === "text_mention",
);
const hasControlCommandInMessage = core.channel.text.hasControlCommand(text, cfg, {

View File

@ -64,9 +64,11 @@ export async function monitorTelegramUserProvider(opts: MonitorTelegramUserOpts
);
}
const client = await createTelegramUserClient({ apiId, apiHash, storagePath });
setActiveTelegramUserClient(account.accountId, client);
let stopped = false;
const stop = async () => {
if (stopped) return;
stopped = true;
shuttingDown = true;
setActiveTelegramUserClient(account.accountId, null);
await client.destroy().catch(() => undefined);
@ -81,77 +83,80 @@ export async function monitorTelegramUserProvider(opts: MonitorTelegramUserOpts
{ once: true },
);
await client.start();
try {
await client.start();
setActiveTelegramUserClient(account.accountId, client);
const { Dispatcher, filters } = await loadMtcuteDispatcher();
const dispatcher = Dispatcher.for(client);
const self = await client.getMe().catch(() => undefined);
const selfName =
self && typeof (self as unknown as { displayName?: unknown }).displayName === "string"
? (self as unknown as { displayName: string }).displayName
: self && typeof (self as unknown as { firstName?: unknown }).firstName === "string"
? [
(self as unknown as { firstName?: string }).firstName,
typeof (self as unknown as { lastName?: unknown }).lastName === "string"
? (self as unknown as { lastName: string }).lastName
: undefined,
]
.filter((entry): entry is string => Boolean(entry && entry.trim()))
.join(" ")
: undefined;
const handleMessage = createTelegramUserMessageHandler({
client,
cfg,
runtime,
accountId: account.accountId,
accountConfig: account.config,
abortSignal: opts.abortSignal,
self: self
? {
id: self.id,
username: "username" in self ? self.username : undefined,
name: selfName,
const { Dispatcher, filters } = await loadMtcuteDispatcher();
const dispatcher = Dispatcher.for(client);
const self = await client.getMe().catch(() => undefined);
const selfName =
self && typeof (self as unknown as { displayName?: unknown }).displayName === "string"
? (self as unknown as { displayName: string }).displayName
: self && typeof (self as unknown as { firstName?: unknown }).firstName === "string"
? [
(self as unknown as { firstName?: string }).firstName,
typeof (self as unknown as { lastName?: unknown }).lastName === "string"
? (self as unknown as { lastName: string }).lastName
: undefined,
]
.filter((entry): entry is string => Boolean(entry && entry.trim()))
.join(" ")
: undefined;
const handleMessage = createTelegramUserMessageHandler({
client,
cfg,
runtime,
accountId: account.accountId,
accountConfig: account.config,
abortSignal: opts.abortSignal,
self: self
? {
id: self.id,
username: "username" in self ? self.username : undefined,
name: selfName,
}
: undefined,
});
dispatcher.onNewMessage(
filters.or(
filters.chat("user"),
filters.chat("group"),
filters.chat("supergroup"),
filters.chat("gigagroup"),
),
handleMessage,
);
await new Promise<void>((resolve, reject) => {
let settled = false;
const settleResolve = () => {
if (settled) return;
settled = true;
resolve();
};
const settleReject = (err: unknown) => {
if (settled) return;
settled = true;
reject(err);
};
client.onError.add((err) => {
if (shuttingDown || opts.abortSignal?.aborted || isDestroyedClientError(err)) {
settleResolve();
return;
}
: undefined,
});
dispatcher.onNewMessage(
filters.or(
filters.chat("user"),
filters.chat("group"),
filters.chat("supergroup"),
filters.chat("gigagroup"),
),
handleMessage,
);
await new Promise<void>((resolve, reject) => {
let settled = false;
const settleResolve = () => {
if (settled) return;
settled = true;
resolve();
};
const settleReject = (err: unknown) => {
if (settled) return;
settled = true;
reject(err);
};
client.onError.add((err) => {
if (shuttingDown || opts.abortSignal?.aborted || isDestroyedClientError(err)) {
runtime.error?.(`telegram-user client error: ${String(err)}`);
settleReject(err);
});
if (opts.abortSignal?.aborted) {
settleResolve();
return;
}
runtime.error?.(`telegram-user client error: ${String(err)}`);
settleReject(err);
opts.abortSignal?.addEventListener("abort", () => settleResolve(), { once: true });
});
if (opts.abortSignal?.aborted) {
settleResolve();
return;
}
opts.abortSignal?.addEventListener("abort", () => settleResolve(), { once: true });
});
await stop();
} finally {
await stop();
}
}