Fix Bind Topic Error by making callback handling more robust

- Wrap answerCallbackQuery and editMessageText in try-catch to handle expired queries
- Answer callback queries as early as possible
- Add setup_tracking to allowed pending action types for better type safety
- Ignore 'query is too old' and 'message is not modified' errors gracefully
This commit is contained in:
2026-03-15 02:50:32 +04:00
parent 5e39cdf455
commit 07c5ffb82d
3 changed files with 116 additions and 69 deletions

View File

@@ -945,16 +945,22 @@ export function registerHouseholdSetupCommands(options: {
}) })
if (result.status === 'rejected') { if (result.status === 'rejected') {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: adminRejectionMessage(locale, result.reason), text: adminRejectionMessage(locale, result.reason),
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
try {
await ctx.answerCallbackQuery({ await ctx.answerCallbackQuery({
text: t.setup.approvedMemberToast(result.member.displayName) text: t.setup.approvedMemberToast(result.member.displayName)
}) })
} catch {
// Ignore stale query
}
if (ctx.msg) { if (ctx.msg) {
const refreshed = await options.householdAdminService.listPendingMembers({ const refreshed = await options.householdAdminService.listPendingMembers({
@@ -963,6 +969,7 @@ export function registerHouseholdSetupCommands(options: {
}) })
if (refreshed.status === 'ok') { if (refreshed.status === 'ok') {
try {
if (refreshed.members.length === 0) { if (refreshed.members.length === 0) {
await ctx.editMessageText(t.setup.pendingMembersEmpty(refreshed.householdName)) await ctx.editMessageText(t.setup.pendingMembersEmpty(refreshed.householdName))
} else { } else {
@@ -971,6 +978,9 @@ export function registerHouseholdSetupCommands(options: {
reply_markup: reply.reply_markup reply_markup: reply.reply_markup
}) })
} }
} catch {
// Ignore message edit errors
}
} }
} }
@@ -989,10 +999,12 @@ export function registerHouseholdSetupCommands(options: {
const t = getBotTranslations(locale).setup const t = getBotTranslations(locale).setup
if (!isGroupChat(ctx)) { if (!isGroupChat(ctx)) {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: t.useButtonInGroup, text: t.useButtonInGroup,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
@@ -1004,18 +1016,22 @@ export function registerHouseholdSetupCommands(options: {
: null : null
if (!actorIsAdmin) { if (!actorIsAdmin) {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: t.onlyTelegramAdminsBindTopics, text: t.onlyTelegramAdminsBindTopics,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
if (!household) { if (!household) {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: t.householdNotConfigured, text: t.householdNotConfigured,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
@@ -1031,13 +1047,15 @@ export function registerHouseholdSetupCommands(options: {
}) })
if (result.status === 'rejected') { if (result.status === 'rejected') {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: text:
result.reason === 'not_admin' result.reason === 'not_admin'
? t.onlyTelegramAdminsBindTopics ? t.onlyTelegramAdminsBindTopics
: t.householdNotConfigured, : t.householdNotConfigured,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
@@ -1050,15 +1068,23 @@ export function registerHouseholdSetupCommands(options: {
botUsername: ctx.me.username botUsername: ctx.me.username
}) })
try {
await ctx.answerCallbackQuery({ await ctx.answerCallbackQuery({
text: t.setupTopicCreated(setupTopicRoleLabel(locale, role), topicName) text: t.setupTopicCreated(setupTopicRoleLabel(locale, role), topicName)
}) })
} catch {
// Ignore stale query
}
if (ctx.msg) { if (ctx.msg) {
try {
await ctx.editMessageText( await ctx.editMessageText(
reply.text, reply.text,
'reply_markup' in reply ? { reply_markup: reply.reply_markup } : {} 'reply_markup' in reply ? { reply_markup: reply.reply_markup } : {}
) )
} catch {
// Ignore message edit errors
}
} }
} catch (error) { } catch (error) {
const message = const message =
@@ -1067,10 +1093,12 @@ export function registerHouseholdSetupCommands(options: {
? t.setupTopicCreateForbidden ? t.setupTopicCreateForbidden
: t.setupTopicCreateFailed : t.setupTopicCreateFailed
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: message, text: message,
show_alert: true show_alert: true
}) })
.catch(() => {})
} }
} }
) )
@@ -1085,10 +1113,12 @@ export function registerHouseholdSetupCommands(options: {
const t = getBotTranslations(locale).setup const t = getBotTranslations(locale).setup
if (!isGroupChat(ctx)) { if (!isGroupChat(ctx)) {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: t.useButtonInGroup, text: t.useButtonInGroup,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
@@ -1098,10 +1128,12 @@ export function registerHouseholdSetupCommands(options: {
const actorIsAdmin = await isGroupAdmin(ctx) const actorIsAdmin = await isGroupAdmin(ctx)
if (!actorIsAdmin) { if (!actorIsAdmin) {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: t.onlyTelegramAdminsBindTopics, text: t.onlyTelegramAdminsBindTopics,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
@@ -1113,27 +1145,37 @@ export function registerHouseholdSetupCommands(options: {
}) })
if (result.status === 'rejected') { if (result.status === 'rejected') {
await ctx.answerCallbackQuery({ await ctx
.answerCallbackQuery({
text: text:
result.reason === 'not_admin' result.reason === 'not_admin'
? t.onlyTelegramAdminsBindTopics ? t.onlyTelegramAdminsBindTopics
: t.householdNotConfigured, : t.householdNotConfigured,
show_alert: true show_alert: true
}) })
.catch(() => {})
return return
} }
try {
await ctx.answerCallbackQuery({ await ctx.answerCallbackQuery({
text: t.topicBoundSuccess( text: t.topicBoundSuccess(
setupTopicRoleLabel(locale, role), setupTopicRoleLabel(locale, role),
result.household.householdName result.household.householdName
) )
}) })
} catch {
// Ignore stale query
}
if (ctx.msg) { if (ctx.msg) {
try {
await ctx.editMessageText( await ctx.editMessageText(
t.topicBoundSuccess(setupTopicRoleLabel(locale, role), result.household.householdName) t.topicBoundSuccess(setupTopicRoleLabel(locale, role), result.household.householdName)
) )
} catch {
// Ignore message edit errors
}
} }
// Try to update the main /setup checklist if it exists // Try to update the main /setup checklist if it exists

View File

@@ -37,6 +37,10 @@ function parsePendingActionType(raw: string): TelegramPendingActionType {
return raw return raw
} }
if (raw === 'setup_tracking') {
return raw
}
throw new Error(`Unexpected telegram pending action type: ${raw}`) throw new Error(`Unexpected telegram pending action type: ${raw}`)
} }

View File

@@ -7,7 +7,8 @@ export const TELEGRAM_PENDING_ACTION_TYPES = [
'payment_topic_clarification', 'payment_topic_clarification',
'payment_topic_confirmation', 'payment_topic_confirmation',
'reminder_utility_entry', 'reminder_utility_entry',
'setup_topic_binding' 'setup_topic_binding',
'setup_tracking'
] as const ] as const
export type TelegramPendingActionType = (typeof TELEGRAM_PENDING_ACTION_TYPES)[number] export type TelegramPendingActionType = (typeof TELEGRAM_PENDING_ACTION_TYPES)[number]