feat: add chat topic binding for casual conversation

Add ability to bind a dedicated chat topic where normal conversation
happens, separate from functional topics (purchases, payments, etc.)

- Add 'chat' to HOUSEHOLD_TOPIC_ROLES
- Add /bind_chat_topic command for admins
- Update i18n strings for en/ru
- Add command to admin scope in telegram-commands
This commit is contained in:
2026-03-14 23:32:14 +04:00
parent 290b18545e
commit 0af8ea6f48
6 changed files with 51 additions and 7 deletions

View File

@@ -28,6 +28,7 @@ const GROUP_INVITE_TTL_MS = 3 * 24 * 60 * 60 * 1000
const SETUP_BIND_TOPIC_ACTION = 'setup_topic_binding' as const
const SETUP_BIND_TOPIC_TTL_MS = 10 * 60 * 1000
const HOUSEHOLD_TOPIC_ROLE_ORDER: readonly HouseholdTopicRole[] = [
'chat',
'purchase',
'feedback',
'reminders',
@@ -106,11 +107,13 @@ function bindRejectionMessage(
function bindTopicUsageMessage(
locale: BotLocale,
role: 'purchase' | 'feedback' | 'reminders' | 'payments'
role: 'chat' | 'purchase' | 'feedback' | 'reminders' | 'payments'
): string {
const t = getBotTranslations(locale).setup
switch (role) {
case 'chat':
return t.useBindChatTopicInGroup
case 'purchase':
return t.useBindPurchaseTopicInGroup
case 'feedback':
@@ -124,13 +127,15 @@ function bindTopicUsageMessage(
function bindTopicSuccessMessage(
locale: BotLocale,
role: 'purchase' | 'feedback' | 'reminders' | 'payments',
role: 'chat' | 'purchase' | 'feedback' | 'reminders' | 'payments',
householdName: string,
threadId: string
): string {
const t = getBotTranslations(locale).setup
switch (role) {
case 'chat':
return t.chatTopicSaved(householdName, threadId)
case 'purchase':
return t.purchaseTopicSaved(householdName, threadId)
case 'feedback':
@@ -358,7 +363,11 @@ function setupReply(input: {
function isHouseholdTopicRole(value: string): value is HouseholdTopicRole {
return (
value === 'purchase' || value === 'feedback' || value === 'reminders' || value === 'payments'
value === 'chat' ||
value === 'purchase' ||
value === 'feedback' ||
value === 'reminders' ||
value === 'payments'
)
}
@@ -647,7 +656,7 @@ export function registerHouseholdSetupCommands(options: {
async function handleBindTopicCommand(
ctx: Context,
role: 'purchase' | 'feedback' | 'reminders' | 'payments'
role: 'chat' | 'purchase' | 'feedback' | 'reminders' | 'payments'
): Promise<void> {
const locale = await resolveReplyLocale({
ctx,
@@ -1129,6 +1138,10 @@ export function registerHouseholdSetupCommands(options: {
await ctx.reply(t.setup.unsetupComplete(result.household.householdName))
})
options.bot.command('bind_chat_topic', async (ctx) => {
await handleBindTopicCommand(ctx, 'chat')
})
options.bot.command('bind_purchase_topic', async (ctx) => {
await handleBindTopicCommand(ctx, 'purchase')
})

View File

@@ -9,6 +9,7 @@ export const enBotTranslations: BotTranslationCatalog = {
cancel: 'Cancel the current prompt',
setup: 'Register this group as a household',
unsetup: 'Reset topic setup for this group',
bind_chat_topic: 'Bind the current topic for casual conversation',
bind_purchase_topic: 'Bind the current topic as purchases',
bind_feedback_topic: 'Bind the current topic as feedback',
bind_reminders_topic: 'Bind the current topic as reminders',
@@ -76,6 +77,8 @@ export const enBotTranslations: BotTranslationCatalog = {
setupTopicBindNotAvailable: 'That topic-binding action is no longer available.',
setupTopicBindRoleName: (role) => {
switch (role) {
case 'chat':
return 'chat'
case 'purchase':
return 'purchases'
case 'feedback':
@@ -88,6 +91,8 @@ export const enBotTranslations: BotTranslationCatalog = {
},
setupTopicSuggestedName: (role) => {
switch (role) {
case 'chat':
return 'Chat'
case 'purchase':
return 'Shared purchases'
case 'feedback':
@@ -103,6 +108,9 @@ export const enBotTranslations: BotTranslationCatalog = {
unsetupComplete: (householdName) =>
`Setup state reset for ${householdName}. Run /setup again to bind topics from scratch.`,
unsetupNoop: 'Nothing to reset for this group yet. Run /setup when you are ready.',
useBindChatTopicInGroup: 'Use /bind_chat_topic inside the household group topic.',
chatTopicSaved: (householdName, threadId) =>
`Chat topic saved for ${householdName} (thread ${threadId}).`,
useBindPurchaseTopicInGroup: 'Use /bind_purchase_topic inside the household group topic.',
purchaseTopicSaved: (householdName, threadId) =>
`Purchase topic saved for ${householdName} (thread ${threadId}).`,

View File

@@ -9,6 +9,7 @@ export const ruBotTranslations: BotTranslationCatalog = {
cancel: 'Отменить текущий ввод',
setup: 'Подключить эту группу как дом',
unsetup: 'Сбросить настройку топиков для этой группы',
bind_chat_topic: 'Назначить текущий топик для разговоров',
bind_purchase_topic: 'Назначить текущий топик для покупок',
bind_feedback_topic: 'Назначить текущий топик для анонимных сообщений',
bind_reminders_topic: 'Назначить текущий топик для напоминаний',
@@ -78,6 +79,8 @@ export const ruBotTranslations: BotTranslationCatalog = {
setupTopicBindNotAvailable: 'Это действие привязки топика уже недоступно.',
setupTopicBindRoleName: (role) => {
switch (role) {
case 'chat':
return 'разговоров'
case 'purchase':
return 'покупки'
case 'feedback':
@@ -90,6 +93,8 @@ export const ruBotTranslations: BotTranslationCatalog = {
},
setupTopicSuggestedName: (role) => {
switch (role) {
case 'chat':
return 'Разговоры'
case 'purchase':
return 'Общие покупки'
case 'feedback':
@@ -105,6 +110,9 @@ export const ruBotTranslations: BotTranslationCatalog = {
unsetupComplete: (householdName) =>
`Состояние настройки для ${householdName} сброшено. Запустите /setup ещё раз, чтобы заново привязать топики.`,
unsetupNoop: 'Для этой группы пока нечего сбрасывать. Когда будете готовы, запустите /setup.',
useBindChatTopicInGroup: 'Используйте /bind_chat_topic внутри топика группы дома.',
chatTopicSaved: (householdName, threadId) =>
`Топик для разговоров сохранён для ${householdName} (тред ${threadId}).`,
useBindPurchaseTopicInGroup: 'Используйте /bind_purchase_topic внутри топика группы дома.',
purchaseTopicSaved: (householdName, threadId) =>
`Топик покупок сохранён для ${householdName} (тред ${threadId}).`,

View File

@@ -7,6 +7,7 @@ export type TelegramCommandName =
| 'cancel'
| 'setup'
| 'unsetup'
| 'bind_chat_topic'
| 'bind_purchase_topic'
| 'bind_feedback_topic'
| 'bind_reminders_topic'
@@ -23,6 +24,7 @@ export interface BotCommandDescriptions {
cancel: string
setup: string
unsetup: string
bind_chat_topic: string
bind_purchase_topic: string
bind_feedback_topic: string
bind_reminders_topic: string
@@ -90,12 +92,18 @@ export interface BotTranslationCatalog {
setupTopicBindPending: (role: string) => string
setupTopicBindCancelled: string
setupTopicBindNotAvailable: string
setupTopicBindRoleName: (role: 'purchase' | 'feedback' | 'reminders' | 'payments') => string
setupTopicSuggestedName: (role: 'purchase' | 'feedback' | 'reminders' | 'payments') => string
setupTopicBindRoleName: (
role: 'chat' | 'purchase' | 'feedback' | 'reminders' | 'payments'
) => string
setupTopicSuggestedName: (
role: 'chat' | 'purchase' | 'feedback' | 'reminders' | 'payments'
) => string
onlyTelegramAdminsUnsetup: string
useUnsetupInGroup: string
unsetupComplete: (householdName: string) => string
unsetupNoop: string
useBindChatTopicInGroup: string
chatTopicSaved: (householdName: string, threadId: string) => string
useBindPurchaseTopicInGroup: string
purchaseTopicSaved: (householdName: string, threadId: string) => string
useBindFeedbackTopicInGroup: string

View File

@@ -35,6 +35,7 @@ const GROUP_ADMIN_COMMAND_NAMES = [
...GROUP_MEMBER_COMMAND_NAMES,
'setup',
'unsetup',
'bind_chat_topic',
'bind_purchase_topic',
'bind_feedback_topic',
'bind_reminders_topic',