mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 14:04:04 +00:00
feat(bot): implement /app and /keyboard commands, add dashboard links
This commit is contained in:
@@ -67,6 +67,8 @@ function isGroupChat(ctx: Context): boolean {
|
|||||||
export function createFinanceCommandsService(options: {
|
export function createFinanceCommandsService(options: {
|
||||||
householdConfigurationRepository: HouseholdConfigurationRepository
|
householdConfigurationRepository: HouseholdConfigurationRepository
|
||||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||||
|
miniAppUrl?: string
|
||||||
|
botUsername?: string
|
||||||
}): {
|
}): {
|
||||||
register: (bot: Bot) => void
|
register: (bot: Bot) => void
|
||||||
} {
|
} {
|
||||||
@@ -253,7 +255,28 @@ export function createFinanceCommandsService(options: {
|
|||||||
resolved.householdId
|
resolved.householdId
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.reply(formatHouseholdStatus(locale, dashboard, settings.rentDueDay))
|
const webAppUrl =
|
||||||
|
options.miniAppUrl && ctx.me.username
|
||||||
|
? `${options.miniAppUrl}${options.miniAppUrl.includes('?') ? '&' : '?'}bot=${ctx.me.username}`
|
||||||
|
: options.miniAppUrl
|
||||||
|
|
||||||
|
await ctx.reply(
|
||||||
|
formatHouseholdStatus(locale, dashboard, settings.rentDueDay),
|
||||||
|
webAppUrl
|
||||||
|
? {
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: getBotTranslations(locale).setup.openMiniAppButton,
|
||||||
|
web_app: { url: webAppUrl }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await ctx.reply(t.statementFailed((error as Error).message))
|
await ctx.reply(t.statementFailed((error as Error).message))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,8 +147,11 @@ function setupKeyboard(input: {
|
|||||||
locale: BotLocale
|
locale: BotLocale
|
||||||
joinDeepLink: string | null
|
joinDeepLink: string | null
|
||||||
bindings: readonly HouseholdTopicBindingRecord[]
|
bindings: readonly HouseholdTopicBindingRecord[]
|
||||||
|
miniAppUrl: string | undefined
|
||||||
|
botUsername: string | undefined
|
||||||
}) {
|
}) {
|
||||||
const t = getBotTranslations(input.locale).setup
|
const t = getBotTranslations(input.locale).setup
|
||||||
|
const kt = getBotTranslations(input.locale).keyboard
|
||||||
const configuredRoles = new Set(input.bindings.map((binding) => binding.role))
|
const configuredRoles = new Set(input.bindings.map((binding) => binding.role))
|
||||||
const rows: Array<
|
const rows: Array<
|
||||||
Array<
|
Array<
|
||||||
@@ -160,6 +163,10 @@ function setupKeyboard(input: {
|
|||||||
text: string
|
text: string
|
||||||
callback_data: string
|
callback_data: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
text: string
|
||||||
|
web_app: { url: string }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
> = []
|
> = []
|
||||||
|
|
||||||
@@ -189,6 +196,19 @@ function setupKeyboard(input: {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add dashboard button
|
||||||
|
const webAppUrl = buildOpenMiniAppUrl(input.miniAppUrl, input.botUsername)
|
||||||
|
if (webAppUrl) {
|
||||||
|
rows.push([
|
||||||
|
{
|
||||||
|
text: kt.dashboardButton,
|
||||||
|
web_app: {
|
||||||
|
url: webAppUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
return rows.length > 0
|
return rows.length > 0
|
||||||
? {
|
? {
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
@@ -234,6 +254,8 @@ function setupReply(input: {
|
|||||||
created: boolean
|
created: boolean
|
||||||
joinDeepLink: string | null
|
joinDeepLink: string | null
|
||||||
bindings: readonly HouseholdTopicBindingRecord[]
|
bindings: readonly HouseholdTopicBindingRecord[]
|
||||||
|
miniAppUrl: string | undefined
|
||||||
|
botUsername: string | undefined
|
||||||
}) {
|
}) {
|
||||||
const t = getBotTranslations(input.locale).setup
|
const t = getBotTranslations(input.locale).setup
|
||||||
return {
|
return {
|
||||||
@@ -250,7 +272,9 @@ function setupReply(input: {
|
|||||||
...setupKeyboard({
|
...setupKeyboard({
|
||||||
locale: input.locale,
|
locale: input.locale,
|
||||||
joinDeepLink: input.joinDeepLink,
|
joinDeepLink: input.joinDeepLink,
|
||||||
bindings: input.bindings
|
bindings: input.bindings,
|
||||||
|
miniAppUrl: input.miniAppUrl,
|
||||||
|
botUsername: input.botUsername
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,6 +406,8 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
locale: BotLocale
|
locale: BotLocale
|
||||||
household: HouseholdTelegramChatRecord
|
household: HouseholdTelegramChatRecord
|
||||||
created: boolean
|
created: boolean
|
||||||
|
miniAppUrl: string | undefined
|
||||||
|
botUsername: string | undefined
|
||||||
}) {
|
}) {
|
||||||
const joinToken = await options.householdOnboardingService.ensureHouseholdJoinToken({
|
const joinToken = await options.householdOnboardingService.ensureHouseholdJoinToken({
|
||||||
householdId: input.household.householdId,
|
householdId: input.household.householdId,
|
||||||
@@ -407,7 +433,9 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
household: input.household,
|
household: input.household,
|
||||||
created: input.created,
|
created: input.created,
|
||||||
joinDeepLink,
|
joinDeepLink,
|
||||||
bindings
|
bindings,
|
||||||
|
miniAppUrl: input.miniAppUrl,
|
||||||
|
botUsername: input.botUsername
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +591,9 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
ctx,
|
ctx,
|
||||||
locale,
|
locale,
|
||||||
household: result.household,
|
household: result.household,
|
||||||
created: result.status === 'created'
|
created: result.status === 'created',
|
||||||
|
miniAppUrl: options.miniAppUrl,
|
||||||
|
botUsername: ctx.me.username
|
||||||
})
|
})
|
||||||
const sent = await ctx.reply(
|
const sent = await ctx.reply(
|
||||||
reply.text,
|
reply.text,
|
||||||
@@ -966,7 +996,9 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
ctx,
|
ctx,
|
||||||
locale,
|
locale,
|
||||||
household: result.household,
|
household: result.household,
|
||||||
created: false
|
created: false,
|
||||||
|
miniAppUrl: options.miniAppUrl,
|
||||||
|
botUsername: ctx.me.username
|
||||||
})
|
})
|
||||||
|
|
||||||
await ctx.answerCallbackQuery({
|
await ctx.answerCallbackQuery({
|
||||||
@@ -1068,7 +1100,9 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
ctx,
|
ctx,
|
||||||
locale,
|
locale,
|
||||||
household: result.household,
|
household: result.household,
|
||||||
created: false
|
created: false,
|
||||||
|
miniAppUrl: options.miniAppUrl,
|
||||||
|
botUsername: ctx.me.username
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1091,6 +1125,50 @@ export function registerHouseholdSetupCommands(options: {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
options.bot.command(['app', 'dashboard'], async (ctx) => {
|
||||||
|
const locale = await resolveReplyLocale({
|
||||||
|
ctx,
|
||||||
|
repository: options.householdConfigurationRepository
|
||||||
|
})
|
||||||
|
const t = getBotTranslations(locale)
|
||||||
|
|
||||||
|
if (!options.miniAppUrl) {
|
||||||
|
await ctx.reply(t.setup.openMiniAppUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.reply(
|
||||||
|
t.setup.openMiniAppFromPrivateChat,
|
||||||
|
openMiniAppReplyMarkup(locale, options.miniAppUrl, ctx.me.username)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
options.bot.command('keyboard', async (ctx) => {
|
||||||
|
const locale = await resolveReplyLocale({
|
||||||
|
ctx,
|
||||||
|
repository: options.householdConfigurationRepository
|
||||||
|
})
|
||||||
|
const t = getBotTranslations(locale)
|
||||||
|
|
||||||
|
if (!options.miniAppUrl) {
|
||||||
|
await ctx.reply(t.setup.openMiniAppUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const webAppUrl = buildOpenMiniAppUrl(options.miniAppUrl, ctx.me.username)
|
||||||
|
if (!webAppUrl) {
|
||||||
|
await ctx.reply(t.setup.openMiniAppUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.reply(t.keyboard.enabled, {
|
||||||
|
reply_markup: {
|
||||||
|
keyboard: [[{ text: t.keyboard.dashboardButton, web_app: { url: webAppUrl } }]],
|
||||||
|
resize_keyboard: true,
|
||||||
|
is_persistent: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function localeFromAccess(access: HouseholdMiniAppAccess, fallback: BotLocale): BotLocale {
|
function localeFromAccess(access: HouseholdMiniAppAccess, fallback: BotLocale): BotLocale {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
join_link: 'Get a shareable link for new members to join',
|
join_link: 'Get a shareable link for new members to join',
|
||||||
payment_add: 'Record your rent or utilities payment',
|
payment_add: 'Record your rent or utilities payment',
|
||||||
pending_members: 'List pending household join requests',
|
pending_members: 'List pending household join requests',
|
||||||
approve_member: 'Approve a pending household member'
|
approve_member: 'Approve a pending household member',
|
||||||
|
app: 'Open the Kojori mini app',
|
||||||
|
keyboard: 'Toggle persistent dashboard button'
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
intro: 'Household bot is live.',
|
intro: 'Household bot is live.',
|
||||||
@@ -121,6 +123,11 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
joinLinkReady: (link, householdName) =>
|
joinLinkReady: (link, householdName) =>
|
||||||
`Join link for ${householdName}:\n${link}\n\nAnyone with this link can join the household. Share it carefully.`
|
`Join link for ${householdName}:\n${link}\n\nAnyone with this link can join the household. Share it carefully.`
|
||||||
},
|
},
|
||||||
|
keyboard: {
|
||||||
|
dashboardButton: '🏡 Dashboard',
|
||||||
|
enabled: 'Persistent dashboard button enabled.',
|
||||||
|
disabled: 'Persistent dashboard button disabled.'
|
||||||
|
},
|
||||||
anonymousFeedback: {
|
anonymousFeedback: {
|
||||||
title: 'Anonymous household note',
|
title: 'Anonymous household note',
|
||||||
cancelButton: 'Cancel',
|
cancelButton: 'Cancel',
|
||||||
@@ -236,7 +243,7 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
reminders: {
|
reminders: {
|
||||||
utilities: (period) => `Utilities reminder for ${period}`,
|
utilities: (period) => `Utilities reminder for ${period}`,
|
||||||
rentWarning: (period) => `Rent reminder for ${period}: payment is coming up soon.`,
|
rentWarning: (period) => `Rent reminder for ${period}: payment is coming up soon.`,
|
||||||
rentDue: (period) => `Rent due reminder for ${period}: please settle payment today.`,
|
rentDue: (period) => `Rent is due for period ${period}. Request sent to the reminders topic.`,
|
||||||
guidedEntryButton: 'Guided entry',
|
guidedEntryButton: 'Guided entry',
|
||||||
copyTemplateButton: 'Copy template',
|
copyTemplateButton: 'Copy template',
|
||||||
openDashboardButton: 'Open dashboard',
|
openDashboardButton: 'Open dashboard',
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
|||||||
join_link: 'Получить ссылку для приглашения новых участников',
|
join_link: 'Получить ссылку для приглашения новых участников',
|
||||||
payment_add: 'Подтвердить оплату аренды или коммуналки',
|
payment_add: 'Подтвердить оплату аренды или коммуналки',
|
||||||
pending_members: 'Показать ожидающие заявки на вступление',
|
pending_members: 'Показать ожидающие заявки на вступление',
|
||||||
approve_member: 'Подтвердить участника дома'
|
approve_member: 'Подтвердить участника дома',
|
||||||
|
app: 'Открыть мини-приложение Kojori',
|
||||||
|
keyboard: 'Вкл/выкл кнопку дашборда'
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
intro: 'Бот для дома подключен.',
|
intro: 'Бот для дома подключен.',
|
||||||
@@ -123,6 +125,11 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
|||||||
joinLinkReady: (link, householdName) =>
|
joinLinkReady: (link, householdName) =>
|
||||||
`Поделитесь этой ссылкой, чтобы пригласить участников в ${householdName}:\n\n${link}\n\nЛюбой, у кого есть эта ссылка, может подать заявку на вступление.`
|
`Поделитесь этой ссылкой, чтобы пригласить участников в ${householdName}:\n\n${link}\n\nЛюбой, у кого есть эта ссылка, может подать заявку на вступление.`
|
||||||
},
|
},
|
||||||
|
keyboard: {
|
||||||
|
dashboardButton: '🏡 Дашборд',
|
||||||
|
enabled: 'Кнопка дашборда включена.',
|
||||||
|
disabled: 'Кнопка дашборда выключена.'
|
||||||
|
},
|
||||||
anonymousFeedback: {
|
anonymousFeedback: {
|
||||||
title: 'Анонимное сообщение по дому',
|
title: 'Анонимное сообщение по дому',
|
||||||
cancelButton: 'Отменить',
|
cancelButton: 'Отменить',
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export type TelegramCommandName =
|
|||||||
| 'payment_add'
|
| 'payment_add'
|
||||||
| 'pending_members'
|
| 'pending_members'
|
||||||
| 'approve_member'
|
| 'approve_member'
|
||||||
|
| 'app'
|
||||||
|
| 'keyboard'
|
||||||
|
|
||||||
export interface BotCommandDescriptions {
|
export interface BotCommandDescriptions {
|
||||||
help: string
|
help: string
|
||||||
@@ -25,6 +27,8 @@ export interface BotCommandDescriptions {
|
|||||||
payment_add: string
|
payment_add: string
|
||||||
pending_members: string
|
pending_members: string
|
||||||
approve_member: string
|
approve_member: string
|
||||||
|
app: string
|
||||||
|
keyboard: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingMemberSummary {
|
export interface PendingMemberSummary {
|
||||||
@@ -107,6 +111,11 @@ export interface BotTranslationCatalog {
|
|||||||
joinLinkUnavailable: string
|
joinLinkUnavailable: string
|
||||||
joinLinkReady: (link: string, householdName: string) => string
|
joinLinkReady: (link: string, householdName: string) => string
|
||||||
}
|
}
|
||||||
|
keyboard: {
|
||||||
|
dashboardButton: string
|
||||||
|
enabled: string
|
||||||
|
disabled: string
|
||||||
|
}
|
||||||
anonymousFeedback: {
|
anonymousFeedback: {
|
||||||
title: string
|
title: string
|
||||||
cancelButton: string
|
cancelButton: string
|
||||||
|
|||||||
@@ -125,11 +125,35 @@ export function createReminderJobsHandler(options: {
|
|||||||
}
|
}
|
||||||
case 'rent-warning':
|
case 'rent-warning':
|
||||||
return {
|
return {
|
||||||
text: t.rentWarning(period)
|
text: t.rentWarning(period),
|
||||||
|
replyMarkup: {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t.openDashboardButton,
|
||||||
|
url: options.botUsername
|
||||||
|
? `https://t.me/${options.botUsername}?start=dashboard`
|
||||||
|
: '#'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'rent-due':
|
case 'rent-due':
|
||||||
return {
|
return {
|
||||||
text: t.rentDue(period)
|
text: t.rentDue(period),
|
||||||
|
replyMarkup: {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t.openDashboardButton,
|
||||||
|
url: options.botUsername
|
||||||
|
? `https://t.me/${options.botUsername}?start=dashboard`
|
||||||
|
: '#'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user