From 703c27bfb9c65b296e8f7adafbddce48dca8b631 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 18 May 2026 02:29:53 -0300 Subject: [PATCH 1/9] feat: add /closecommunity command for superadmins --- bot/middleware/user.ts | 6 +- bot/modules/community/commands.ts | 95 ++++++++++++++++++++++++++++++- bot/modules/community/index.ts | 9 ++- locales/en.yaml | 8 +++ locales/es.yaml | 8 +++ 5 files changed, 123 insertions(+), 3 deletions(-) diff --git a/bot/middleware/user.ts b/bot/middleware/user.ts index 0ae215a8..70f78424 100644 --- a/bot/middleware/user.ts +++ b/bot/middleware/user.ts @@ -34,7 +34,11 @@ export const superAdminMiddleware = async ( ctx: CommunityContext, next: () => void, ) => { - const admin = await validateSuperAdmin(ctx); + const tgId = + 'callback_query' in ctx.update + ? ctx.update.callback_query.from.id.toString() + : undefined; + const admin = await validateSuperAdmin(ctx, tgId); if (!admin) return false; ctx.i18n.locale(admin.lang); ctx.admin = admin; diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 1170cbd1..687f55ac 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import { logger } from '../../../logger'; import { showUserCommunitiesMessage } from './messages'; -import { Community, Order } from '../../../models'; +import { Community, Order, User } from '../../../models'; import { validateParams, validateObjectId } from '../../validations'; import { MainContext } from '../../start'; import { CommunityContext } from './communityContext'; @@ -235,6 +235,99 @@ export const deleteCommunity = async (ctx: CommunityContext) => { } }; +export const closeCommunity = async (ctx: MainContext) => { + try { + const [input] = (await validateParams( + ctx, + 2, + '\\<_community id \\| @groupUsername_\\>', + ))!; + if (!input) return; + + let community; + if (input[0] === '@') { + const regex = new RegExp(['^', input, '$'].join(''), 'i'); + community = await Community.findOne({ group: regex }); + } else { + if (!(await validateObjectId(ctx, input))) return; + community = await Community.findOne({ _id: input }); + } + + if (!community) { + return ctx.reply(ctx.i18n.t('community_not_found')); + } + + const completedOrders = await Order.countDocuments({ + community_id: community._id, + status: 'SUCCESS', + }); + + const creator = await User.findById(community.creator_id); + const creatorUsername = creator?.username || 'unknown'; + + const solversText = + community.solvers.length > 0 + ? community.solvers.map(s => `@${s.username}`).join(', ') + : '-'; + + const text = ctx.i18n.t('close_community_confirmation', { + communityName: community.name, + completedOrders, + creatorUsername, + solvers: solversText, + }); + + await ctx.reply(text, { + reply_markup: { + inline_keyboard: [ + [ + { + text: ctx.i18n.t('continue'), + callback_data: `closeCommunityConfirmBtn_${community._id}`, + }, + { + text: ctx.i18n.t('cancel'), + callback_data: 'doNothingBtn', + }, + ], + ], + }, + }); + } catch (error) { + logger.error(error); + } +}; + +export const closeCommunityConfirm = async (ctx: CommunityContext) => { + try { + ctx.deleteMessage(); + const id = ctx.match?.[1]; + if (!id) return; + + if (!(await validateObjectId(ctx, id))) return; + const community = await Community.findById(id); + if (!community) { + return ctx.reply(ctx.i18n.t('community_not_found')); + } + + const creator = await User.findById(community.creator_id); + const communityName = community.name; + + await community.deleteOne(); + + if (creator) { + await ctx.telegram.sendMessage( + creator.tg_id, + ctx.i18n.t('community_closed_by_admin', { communityName }), + ); + } + + return ctx.reply(ctx.i18n.t('operation_successful')); + } catch (error) { + logger.error(error); + } +}; + export const changeVisibility = async (ctx: CommunityContext) => { try { ctx.deleteMessage(); diff --git a/bot/modules/community/index.ts b/bot/modules/community/index.ts index 9b9eacf9..99648938 100644 --- a/bot/modules/community/index.ts +++ b/bot/modules/community/index.ts @@ -1,5 +1,5 @@ import { Telegraf } from 'telegraf'; -import { userMiddleware } from '../../middleware/user'; +import { userMiddleware, superAdminMiddleware } from '../../middleware/user'; import * as actions from './actions'; import * as commands from './commands'; import { @@ -58,6 +58,13 @@ export const configure = (bot: Telegraf) => { await commands.updateCommunity(ctx, ctx.match[1], 'language'); }); + bot.command('closecommunity', superAdminMiddleware, commands.closeCommunity); + bot.action( + /^closeCommunityConfirmBtn_([0-9a-f]{24})$/, + superAdminMiddleware, + commands.closeCommunityConfirm, + ); + bot.command('findcomms', userMiddleware, commands.findCommunity); bot.action( /^communityInfo_([0-9a-f]{24})$/, diff --git a/locales/en.yaml b/locales/en.yaml index c12c9407..b05ebd5f 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -712,3 +712,11 @@ unblock_failed: "Error unblocking the user" check_solvers: Your community ${communityName} does not have any solvers. Please add at least one within ${remainingDays} days to prevent the community from being disabled. check_solvers_last_warning: Your community ${communityName} does not have any solvers. Please add at least one today to prevent the community from being disabled. image_processing_error: We had an error processing the image, please wait a few minutes and try again + +close_community_confirmation: | + Community ${communityName} + Completed orders: ${completedOrders} + Creator: @${creatorUsername} + Solvers: ${solvers} + Are you sure you want to continue? +community_closed_by_admin: An administrator has closed your community @${communityName} diff --git a/locales/es.yaml b/locales/es.yaml index b77314c4..d2cd1d87 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -709,3 +709,11 @@ unblock_failed: "Error al desbloquear al usuario" image_processing_error: Hemos tenido un error procesando la imagen, por favor espera unos minutos y vuelve a intentarlo. check_solvers: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno dentro de ${remainingDays} días para evitar que se deshabilite la comunidad. check_solvers_last_warning: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno hoy para evitar que la comunidad quede inhabilitada. + +close_community_confirmation: | + Comunidad ${communityName} + Ordenes completadas: ${completedOrders} + Creador: @${creatorUsername} + Solvers: ${solvers} + ¿Está seguro que desea continuar? +community_closed_by_admin: Un administrador ha cerrado tu comunidad @${communityName} From 696abba48184a0467b1b4faf822a5ccf96aa78e6 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 18 May 2026 02:33:54 -0300 Subject: [PATCH 2/9] feat: add /closecommunity translations for all languages --- locales/de.yaml | 7 +++++++ locales/fa.yaml | 7 +++++++ locales/fr.yaml | 7 +++++++ locales/it.yaml | 7 +++++++ locales/ko.yaml | 7 +++++++ locales/pt.yaml | 7 +++++++ locales/ru.yaml | 7 +++++++ locales/uk.yaml | 7 +++++++ 8 files changed, 56 insertions(+) diff --git a/locales/de.yaml b/locales/de.yaml index 07e691ab..b7b97e9a 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -717,3 +717,10 @@ payment_methods_saved: "Zahlungsmethoden gespeichert ✅" custom_payment_method: "✍️ Benutzerdefinierte Zahlungsmethode" payment_methods_reset: "Zahlungsmethoden entfernt. Benutzer können jetzt beliebige Zahlungsmethoden frei eingeben." payment_methods_wizard_commands: "/reset — alle Zahlungsmethoden entfernen und Standardverhalten wiederherstellen\n/exit — ohne Speichern beenden" +close_community_confirmation: | + Gemeinschaft ${communityName} + Abgeschlossene Bestellungen: ${completedOrders} + Ersteller: @${creatorUsername} + Löser: ${solvers} + Sind Sie sicher, dass Sie fortfahren möchten? +community_closed_by_admin: Ein Administrator hat Ihre Gemeinschaft @${communityName} geschlossen diff --git a/locales/fa.yaml b/locales/fa.yaml index 463206de..c2bc7b3c 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -816,3 +816,10 @@ payment_methods_saved: "روش‌های پرداخت ذخیره شد ✅" custom_payment_method: "✍️ روش پرداخت سفارشی" payment_methods_reset: "روش‌های پرداخت حذف شدند. کاربران اکنون می‌توانند هر روش پرداختی را آزادانه وارد کنند." payment_methods_wizard_commands: "/reset — حذف همه روش‌های پرداخت و بازگرداندن رفتار پیش‌فرض\n/exit — خروج بدون ذخیره" +close_community_confirmation: | + جامعه ${communityName} + سفارشات تکمیل‌شده: ${completedOrders} + سازنده: @${creatorUsername} + حل‌کنندگان: ${solvers} + آیا مطمئن هستید که می‌خواهید ادامه دهید؟ +community_closed_by_admin: یک مدیر جامعه شما @${communityName} را بست diff --git a/locales/fr.yaml b/locales/fr.yaml index e96d3e0b..76e97a88 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -716,3 +716,10 @@ payment_methods_saved: "Méthodes de paiement enregistrées ✅" custom_payment_method: "✍️ Méthode de paiement personnalisée" payment_methods_reset: "Méthodes de paiement supprimées. Les utilisateurs peuvent désormais saisir n'importe quelle méthode de paiement librement." payment_methods_wizard_commands: "/reset — supprimer toutes les méthodes de paiement et restaurer le comportement par défaut\n/exit — quitter sans enregistrer" +close_community_confirmation: | + Communauté ${communityName} + Commandes complétées: ${completedOrders} + Créateur: @${creatorUsername} + Solveurs: ${solvers} + Êtes-vous sûr de vouloir continuer? +community_closed_by_admin: Un administrateur a fermé votre communauté @${communityName} diff --git a/locales/it.yaml b/locales/it.yaml index 54aae511..06fe6343 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -714,3 +714,10 @@ payment_methods_saved: "Metodi di pagamento salvati ✅" custom_payment_method: "✍️ Metodo di pagamento personalizzato" payment_methods_reset: "Metodi di pagamento rimossi. Gli utenti possono ora inserire qualsiasi metodo di pagamento liberamente." payment_methods_wizard_commands: "/reset — rimuovere tutti i metodi di pagamento e ripristinare il comportamento predefinito\n/exit — uscire senza salvare" +close_community_confirmation: | + Comunità ${communityName} + Ordini completati: ${completedOrders} + Creatore: @${creatorUsername} + Risolutori: ${solvers} + Sei sicuro di voler continuare? +community_closed_by_admin: Un amministratore ha chiuso la tua comunità @${communityName} diff --git a/locales/ko.yaml b/locales/ko.yaml index 8880aa1d..69a0a6d8 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -714,3 +714,10 @@ payment_methods_saved: "결제 방법이 저장되었습니다 ✅" custom_payment_method: "✍️ 사용자 지정 결제 방법" payment_methods_reset: "결제 방법이 삭제되었습니다. 이제 사용자는 어떤 결제 방법이든 자유롭게 입력할 수 있습니다." payment_methods_wizard_commands: "/reset — 모든 결제 방법을 삭제하고 기본 동작을 복원합니다\n/exit — 저장하지 않고 종료" +close_community_confirmation: | + 커뮤니티 ${communityName} + 완료된 주문: ${completedOrders} + 생성자: @${creatorUsername} + 해결사: ${solvers} + 계속하시겠습니까? +community_closed_by_admin: 관리자가 귀하의 커뮤니티 @${communityName}을 폐쇄했습니다 diff --git a/locales/pt.yaml b/locales/pt.yaml index 7cfdebc2..1c9f15e2 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -716,3 +716,10 @@ payment_methods_saved: "Métodos de pagamento salvos ✅" custom_payment_method: "✍️ Método de pagamento personalizado" payment_methods_reset: "Métodos de pagamento removidos. Os usuários agora podem inserir qualquer método de pagamento livremente." payment_methods_wizard_commands: "/reset — remover todos os métodos de pagamento e restaurar o comportamento padrão\n/exit — sair sem salvar" +close_community_confirmation: | + Comunidade ${communityName} + Ordens concluídas: ${completedOrders} + Criador: @${creatorUsername} + Solvers: ${solvers} + Tem certeza que deseja continuar? +community_closed_by_admin: Um administrador fechou sua comunidade @${communityName} diff --git a/locales/ru.yaml b/locales/ru.yaml index 09956b27..7d11dcd8 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -717,3 +717,10 @@ payment_methods_saved: "Способы оплаты сохранены ✅" custom_payment_method: "✍️ Пользовательский способ оплаты" payment_methods_reset: "Способы оплаты удалены. Пользователи теперь могут свободно вводить любой способ оплаты." payment_methods_wizard_commands: "/reset — удалить все способы оплаты и восстановить поведение по умолчанию\n/exit — выйти без сохранения" +close_community_confirmation: | + Сообщество ${communityName} + Выполненные заказы: ${completedOrders} + Создатель: @${creatorUsername} + Решатели: ${solvers} + Вы уверены, что хотите продолжить? +community_closed_by_admin: Администратор закрыл ваше сообщество @${communityName} diff --git a/locales/uk.yaml b/locales/uk.yaml index 1176d9e4..58000469 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -713,3 +713,10 @@ payment_methods_saved: "Способи оплати збережено ✅" custom_payment_method: "✍️ Власний спосіб оплати" payment_methods_reset: "Способи оплати видалено. Користувачі тепер можуть вільно вводити будь-який спосіб оплати." payment_methods_wizard_commands: "/reset — видалити всі способи оплати та відновити поведінку за замовчуванням\n/exit — вийти без збереження" +close_community_confirmation: | + Спільнота ${communityName} + Виконані замовлення: ${completedOrders} + Створив: @${creatorUsername} + Вирішувачі: ${solvers} + Ви впевнені, що хочете продовжити? +community_closed_by_admin: Адміністратор закрив вашу спільноту @${communityName} From 70669f0a34c69fdd411ef4b48174486949bc2141 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 18 May 2026 02:46:43 -0300 Subject: [PATCH 3/9] fix: coderabbit suggestions on closecommunity command --- bot/modules/community/commands.ts | 15 ++++++++++----- locales/de.yaml | 2 +- locales/en.yaml | 2 +- locales/es.yaml | 2 +- locales/fa.yaml | 2 +- locales/fr.yaml | 2 +- locales/it.yaml | 2 +- locales/ko.yaml | 2 +- locales/pt.yaml | 2 +- locales/ru.yaml | 2 +- locales/uk.yaml | 2 +- 11 files changed, 20 insertions(+), 15 deletions(-) diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index d46e4c7f..f669ae86 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -252,7 +252,8 @@ export const closeCommunity = async (ctx: MainContext) => { let community; if (input[0] === '@') { - const regex = new RegExp(['^', input, '$'].join(''), 'i'); + const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`^${escapedInput}$`, 'i'); community = await Community.findOne({ group: regex }); } else { if (!(await validateObjectId(ctx, input))) return; @@ -322,10 +323,14 @@ export const closeCommunityConfirm = async (ctx: CommunityContext) => { await community.deleteOne(); if (creator) { - await ctx.telegram.sendMessage( - creator.tg_id, - ctx.i18n.t('community_closed_by_admin', { communityName }), - ); + try { + await ctx.telegram.sendMessage( + creator.tg_id, + ctx.i18n.t('community_closed_by_admin', { communityName }), + ); + } catch (notifyError) { + logger.error(notifyError); + } } return ctx.reply(ctx.i18n.t('operation_successful')); diff --git a/locales/de.yaml b/locales/de.yaml index b7b97e9a..3a264846 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -723,4 +723,4 @@ close_community_confirmation: | Ersteller: @${creatorUsername} Löser: ${solvers} Sind Sie sicher, dass Sie fortfahren möchten? -community_closed_by_admin: Ein Administrator hat Ihre Gemeinschaft @${communityName} geschlossen +community_closed_by_admin: Ein Administrator hat Ihre Gemeinschaft ${communityName} geschlossen diff --git a/locales/en.yaml b/locales/en.yaml index a83af9a2..8934b195 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -728,4 +728,4 @@ close_community_confirmation: | Creator: @${creatorUsername} Solvers: ${solvers} Are you sure you want to continue? -community_closed_by_admin: An administrator has closed your community @${communityName} +community_closed_by_admin: An administrator has closed your community ${communityName} diff --git a/locales/es.yaml b/locales/es.yaml index 89fedd98..cd2163a4 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -725,4 +725,4 @@ close_community_confirmation: | Creador: @${creatorUsername} Solvers: ${solvers} ¿Está seguro que desea continuar? -community_closed_by_admin: Un administrador ha cerrado tu comunidad @${communityName} +community_closed_by_admin: Un administrador ha cerrado tu comunidad ${communityName} diff --git a/locales/fa.yaml b/locales/fa.yaml index c2bc7b3c..4f59430b 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -822,4 +822,4 @@ close_community_confirmation: | سازنده: @${creatorUsername} حل‌کنندگان: ${solvers} آیا مطمئن هستید که می‌خواهید ادامه دهید؟ -community_closed_by_admin: یک مدیر جامعه شما @${communityName} را بست +community_closed_by_admin: یک مدیر جامعه شما ${communityName} را بست diff --git a/locales/fr.yaml b/locales/fr.yaml index 76e97a88..045d63c2 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -722,4 +722,4 @@ close_community_confirmation: | Créateur: @${creatorUsername} Solveurs: ${solvers} Êtes-vous sûr de vouloir continuer? -community_closed_by_admin: Un administrateur a fermé votre communauté @${communityName} +community_closed_by_admin: Un administrateur a fermé votre communauté ${communityName} diff --git a/locales/it.yaml b/locales/it.yaml index 06fe6343..bf357714 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -720,4 +720,4 @@ close_community_confirmation: | Creatore: @${creatorUsername} Risolutori: ${solvers} Sei sicuro di voler continuare? -community_closed_by_admin: Un amministratore ha chiuso la tua comunità @${communityName} +community_closed_by_admin: Un amministratore ha chiuso la tua comunità ${communityName} diff --git a/locales/ko.yaml b/locales/ko.yaml index 69a0a6d8..c2d97cb7 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -720,4 +720,4 @@ close_community_confirmation: | 생성자: @${creatorUsername} 해결사: ${solvers} 계속하시겠습니까? -community_closed_by_admin: 관리자가 귀하의 커뮤니티 @${communityName}을 폐쇄했습니다 +community_closed_by_admin: 관리자가 귀하의 커뮤니티 ${communityName}을 폐쇄했습니다 diff --git a/locales/pt.yaml b/locales/pt.yaml index 1c9f15e2..344a2c48 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -722,4 +722,4 @@ close_community_confirmation: | Criador: @${creatorUsername} Solvers: ${solvers} Tem certeza que deseja continuar? -community_closed_by_admin: Um administrador fechou sua comunidade @${communityName} +community_closed_by_admin: Um administrador fechou sua comunidade ${communityName} diff --git a/locales/ru.yaml b/locales/ru.yaml index 7d11dcd8..d04fcbca 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -723,4 +723,4 @@ close_community_confirmation: | Создатель: @${creatorUsername} Решатели: ${solvers} Вы уверены, что хотите продолжить? -community_closed_by_admin: Администратор закрыл ваше сообщество @${communityName} +community_closed_by_admin: Администратор закрыл ваше сообщество ${communityName} diff --git a/locales/uk.yaml b/locales/uk.yaml index 58000469..6061e481 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -719,4 +719,4 @@ close_community_confirmation: | Створив: @${creatorUsername} Вирішувачі: ${solvers} Ви впевнені, що хочете продовжити? -community_closed_by_admin: Адміністратор закрив вашу спільноту @${communityName} +community_closed_by_admin: Адміністратор закрив вашу спільноту ${communityName} From 780196369a6aa66fe0ee5ed9ca2ef9eb48f14a1b Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 18 May 2026 20:50:20 -0300 Subject: [PATCH 4/9] feat: replace /closecommunity with /disablecommunity and /enablecommunity --- bot/modules/community/actions.ts | 10 +- bot/modules/community/commands.ts | 159 ++++++++++++++++++---------- bot/modules/community/index.ts | 12 ++- bot/modules/community/messages.ts | 10 +- bot/modules/orders/scenes.ts | 5 +- bot/modules/user/scenes/settings.ts | 10 +- locales/de.yaml | 13 ++- locales/en.yaml | 13 ++- locales/es.yaml | 13 ++- locales/fa.yaml | 13 ++- locales/fr.yaml | 13 ++- locales/it.yaml | 13 ++- locales/ko.yaml | 13 ++- locales/pt.yaml | 13 ++- locales/ru.yaml | 13 ++- locales/uk.yaml | 13 ++- models/community.ts | 2 + 17 files changed, 198 insertions(+), 140 deletions(-) diff --git a/bot/modules/community/actions.ts b/bot/modules/community/actions.ts index e88c769a..3ca09f3e 100644 --- a/bot/modules/community/actions.ts +++ b/bot/modules/community/actions.ts @@ -59,7 +59,10 @@ const getVolumeNDays = async ( export const onCommunityInfo = async (ctx: MainContext) => { const commId = ctx.match?.[1]; - const community = await Community.findById(commId); + const community = await Community.findOne({ + _id: commId, + enabled: { $ne: false }, + }); if (community === null) throw new Error('community not found'); const userCount = await User.countDocuments({ default_community_id: commId }); const orderCount = await getOrdersNDays(1, commId); @@ -120,7 +123,10 @@ export const onSetCommunity = async (ctx: CommunityContext) => { export const withdrawEarnings = async (ctx: CommunityContext) => { ctx.deleteMessage(); - const community = await Community.findById(ctx.match?.[1]); + const community = await Community.findOne({ + _id: ctx.match?.[1], + enabled: { $ne: false }, + }); ctx.scene.enter('ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', { community, }); diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index f669ae86..6e6b035a 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -21,6 +21,7 @@ async function findCommunities(currency: string) { const communities = await Community.find({ currencies: currency, public: true, + enabled: { $ne: false }, }); const orderCount = await getOrderCountByCommunity(); return communities.map(comm => { @@ -49,9 +50,15 @@ export const setComm = async (ctx: MainContext) => { if (groupName[0] == '@') { // Allow find communities case insensitive const regex = new RegExp(['^', groupName, '$'].join(''), 'i'); - community = await Community.findOne({ group: regex }); + community = await Community.findOne({ + group: regex, + enabled: { $ne: false }, + }); } else if (groupName[0] == '-') { - community = await Community.findOne({ group: groupName }); + community = await Community.findOne({ + group: groupName, + enabled: { $ne: false }, + }); } if (!community) { return await ctx.reply(ctx.i18n.t('community_not_found')); @@ -70,7 +77,11 @@ export const communityAdmin = async (ctx: CommunityContext) => { try { const [group] = (await validateParams(ctx, 2, '\\<_community_\\>'))!; const creator_id = ctx.user.id; - const [community] = await Community.find({ group, creator_id }); + const [community] = await Community.find({ + group, + creator_id, + enabled: { $ne: false }, + }); if (!community) throw new Error('CommunityNotFound'); await ctx.scene.enter('COMMUNITY_ADMIN', { community }); } catch (err: any) { @@ -89,7 +100,10 @@ export const myComms = async (ctx: MainContext) => { try { const { user } = ctx; - const communities = await Community.find({ creator_id: user._id }); + const communities = await Community.find({ + creator_id: user._id, + enabled: { $ne: false }, + }); if (!communities.length) return await ctx.reply(ctx.i18n.t('you_dont_have_communities')); @@ -147,6 +161,7 @@ export const updateCommunity = async ( const community = await Community.findOne({ _id: id, creator_id: user._id, + enabled: { $ne: false }, }); if (!community) { @@ -228,6 +243,7 @@ export const deleteCommunity = async (ctx: CommunityContext) => { const community = await Community.findOne({ _id: id, creator_id: ctx.user._id, + enabled: { $ne: false }, }); if (!community) { @@ -241,7 +257,39 @@ export const deleteCommunity = async (ctx: CommunityContext) => { } }; -export const closeCommunity = async (ctx: MainContext) => { +async function findCommunityByInput( + ctx: MainContext, + input: string, +): Promise<(typeof Community.prototype) | null> { + if (input[0] === '@') { + const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`^${escapedInput}$`, 'i'); + return Community.findOne({ group: regex }); + } + if (!(await validateObjectId(ctx, input))) return null; + return Community.findOne({ _id: input }); +} + +function buildCommunityInfoText( + ctx: MainContext, + community: (typeof Community.prototype), + creatorUsername: string, + localeKey: string, +): string { + const solversText = + community.solvers.length > 0 + ? community.solvers.map((s: { username: string }) => `@${s.username}`).join(', ') + : '-'; + const groupText = community.group || '-'; + return ctx.i18n.t(localeKey, { + communityName: community.name, + group: groupText, + solvers: solversText, + creatorUsername, + }); +} + +export const disableCommunity = async (ctx: MainContext) => { try { const [input] = (await validateParams( ctx, @@ -250,92 +298,86 @@ export const closeCommunity = async (ctx: MainContext) => { ))!; if (!input) return; - let community; - if (input[0] === '@') { - const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const regex = new RegExp(`^${escapedInput}$`, 'i'); - community = await Community.findOne({ group: regex }); - } else { - if (!(await validateObjectId(ctx, input))) return; - community = await Community.findOne({ _id: input }); + const community = await findCommunityByInput(ctx, input); + if (community === null) { + return ctx.reply(ctx.i18n.t('community_not_found')); } - if (!community) { - return ctx.reply(ctx.i18n.t('community_not_found')); + if (community.enabled === false) { + return ctx.reply(ctx.i18n.t('community_already_disabled')); } - const completedOrders = await Order.countDocuments({ - community_id: community._id, - status: 'SUCCESS', - }); + community.enabled = false; + await community.save(); const creator = await User.findById(community.creator_id); const creatorUsername = creator?.username || 'unknown'; - const solversText = - community.solvers.length > 0 - ? community.solvers.map(s => `@${s.username}`).join(', ') - : '-'; - - const text = ctx.i18n.t('close_community_confirmation', { - communityName: community.name, - completedOrders, - creatorUsername, - solvers: solversText, - }); + if (creator) { + try { + await ctx.telegram.sendMessage( + creator.tg_id, + ctx.i18n.t('community_disabled_by_admin', { + communityName: community.name, + }), + ); + } catch (notifyError) { + logger.error(notifyError); + } + } - await ctx.reply(text, { - reply_markup: { - inline_keyboard: [ - [ - { - text: ctx.i18n.t('continue'), - callback_data: `closeCommunityConfirmBtn_${community._id}`, - }, - { - text: ctx.i18n.t('cancel'), - callback_data: 'doNothingBtn', - }, - ], - ], - }, - }); + return ctx.reply( + buildCommunityInfoText(ctx, community, creatorUsername, 'community_disabled_info'), + ); } catch (error) { logger.error(error); + await ctx.reply(ctx.i18n.t('generic_error')); } }; -export const closeCommunityConfirm = async (ctx: CommunityContext) => { +export const enableCommunity = async (ctx: MainContext) => { try { - ctx.deleteMessage(); - const id = ctx.match?.[1]; - if (!id) return; + const [input] = (await validateParams( + ctx, + 2, + '\\<_community id \\| @groupUsername_\\>', + ))!; + if (!input) return; - if (!(await validateObjectId(ctx, id))) return; - const community = await Community.findById(id); - if (!community) { + const community = await findCommunityByInput(ctx, input); + if (community === null) { return ctx.reply(ctx.i18n.t('community_not_found')); } - const creator = await User.findById(community.creator_id); - const communityName = community.name; + if (community.enabled !== false) { + return ctx.reply(ctx.i18n.t('community_already_enabled')); + } - await community.deleteOne(); + community.enabled = true; + await community.save(); + + const creator = await User.findById(community.creator_id); + const creatorUsername = creator?.username || 'unknown'; if (creator) { try { await ctx.telegram.sendMessage( creator.tg_id, - ctx.i18n.t('community_closed_by_admin', { communityName }), + ctx.i18n.t('community_enabled_by_admin', { + communityName: community.name, + }), ); } catch (notifyError) { logger.error(notifyError); } } - return ctx.reply(ctx.i18n.t('operation_successful')); + return ctx.reply( + buildCommunityInfoText(ctx, community, creatorUsername, 'community_enabled_info'), + ); } catch (error) { logger.error(error); + await ctx.reply(ctx.i18n.t('generic_error')); } }; @@ -349,6 +391,7 @@ export const changeVisibility = async (ctx: CommunityContext) => { const community = await Community.findOne({ _id: id, creator_id: ctx.user._id, + enabled: { $ne: false }, }); if (!community) { diff --git a/bot/modules/community/index.ts b/bot/modules/community/index.ts index 2750585a..f6668d8d 100644 --- a/bot/modules/community/index.ts +++ b/bot/modules/community/index.ts @@ -65,11 +65,15 @@ export const configure = (bot: Telegraf) => { }, ); - bot.command('closecommunity', superAdminMiddleware, commands.closeCommunity); - bot.action( - /^closeCommunityConfirmBtn_([0-9a-f]{24})$/, + bot.command( + 'disablecommunity', + superAdminMiddleware, + commands.disableCommunity, + ); + bot.command( + 'enablecommunity', superAdminMiddleware, - commands.closeCommunityConfirm, + commands.enableCommunity, ); bot.command('findcomms', userMiddleware, commands.findCommunity); diff --git a/bot/modules/community/messages.ts b/bot/modules/community/messages.ts index 94b2c8d6..da2d3d5f 100644 --- a/bot/modules/community/messages.ts +++ b/bot/modules/community/messages.ts @@ -50,7 +50,10 @@ export const updateCommunityMessage = async (ctx: MainContext) => { try { await ctx.deleteMessage(); const id = ctx.match?.[1]; - const community = await Community.findById(id); + const community = await Community.findOne({ + _id: id, + enabled: { $ne: false }, + }); if (community == null) throw new Error('community was not found'); let text = ctx.i18n.t('community') + `: ${community.name}\n`; text += ctx.i18n.t('what_to_do'); @@ -170,7 +173,10 @@ export const earningsMessage = async (ctx: MainContext) => { if (isScheduled) return await ctx.reply(ctx.i18n.t('invoice_already_being_paid')); - const community = await Community.findById(communityId); + const community = await Community.findOne({ + _id: communityId, + enabled: { $ne: false }, + }); if (community == null) throw new Error('community was not found'); const button = community.earnings > 0 diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 1eb6a497..d66823d5 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -154,7 +154,10 @@ const createOrderSteps = { const stateComm = ctx.wizard.state.community; const loadedComm = !stateComm && user?.default_community_id - ? await Community.findById(user.default_community_id) + ? await Community.findOne({ + _id: user.default_community_id, + enabled: { $ne: false }, + }) : null; const community = stateComm ?? loadedComm; if (loadedComm) ctx.wizard.state.community = loadedComm; diff --git a/bot/modules/user/scenes/settings.ts b/bot/modules/user/scenes/settings.ts index b5645873..9fda679c 100644 --- a/bot/modules/user/scenes/settings.ts +++ b/bot/modules/user/scenes/settings.ts @@ -26,9 +26,13 @@ function make() { lightning_address: '', }; if (user.default_community_id) { - const community = await Community.findById(user.default_community_id); - if (community == null) throw new Error('community not found'); - data.community = community.group; + const community = await Community.findOne({ + _id: user.default_community_id, + enabled: { $ne: false }, + }); + if (community != null) { + data.community = community.group; + } } if (user.nostr_public_key) { data.npub = NostrLib.encodeNpub(user.nostr_public_key); diff --git a/locales/de.yaml b/locales/de.yaml index 3a264846..6d2b163a 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -717,10 +717,9 @@ payment_methods_saved: "Zahlungsmethoden gespeichert ✅" custom_payment_method: "✍️ Benutzerdefinierte Zahlungsmethode" payment_methods_reset: "Zahlungsmethoden entfernt. Benutzer können jetzt beliebige Zahlungsmethoden frei eingeben." payment_methods_wizard_commands: "/reset — alle Zahlungsmethoden entfernen und Standardverhalten wiederherstellen\n/exit — ohne Speichern beenden" -close_community_confirmation: | - Gemeinschaft ${communityName} - Abgeschlossene Bestellungen: ${completedOrders} - Ersteller: @${creatorUsername} - Löser: ${solvers} - Sind Sie sicher, dass Sie fortfahren möchten? -community_closed_by_admin: Ein Administrator hat Ihre Gemeinschaft ${communityName} geschlossen +community_disabled_info: "Gemeinschaft: ${communityName}\nGruppe: ${group}\nLöser: ${solvers}\nErsteller: @${creatorUsername}\nDie Gemeinschaft wurde deaktiviert" +community_enabled_info: "Gemeinschaft: ${communityName}\nGruppe: ${group}\nLöser: ${solvers}\nErsteller: @${creatorUsername}\nDie Gemeinschaft wurde aktiviert" +community_disabled_by_admin: Ein Administrator hat Ihre Gemeinschaft ${communityName} deaktiviert +community_enabled_by_admin: Ein Administrator hat Ihre Gemeinschaft ${communityName} wieder aktiviert +community_already_disabled: Diese Gemeinschaft ist bereits deaktiviert +community_already_enabled: Diese Gemeinschaft ist bereits aktiv diff --git a/locales/en.yaml b/locales/en.yaml index 8934b195..c635350a 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -722,10 +722,9 @@ payment_methods_saved: "Payment methods saved ✅" custom_payment_method: "✍️ Custom payment method" payment_methods_reset: "Payment methods removed. Users can now enter any payment method freely." payment_methods_wizard_commands: "/reset — remove all payment methods and restore default behavior\n/exit — exit without saving" -close_community_confirmation: | - Community ${communityName} - Completed orders: ${completedOrders} - Creator: @${creatorUsername} - Solvers: ${solvers} - Are you sure you want to continue? -community_closed_by_admin: An administrator has closed your community ${communityName} +community_disabled_info: "Community: ${communityName}\nGroup: ${group}\nSolvers: ${solvers}\nCreator: @${creatorUsername}\nThe community was disabled" +community_enabled_info: "Community: ${communityName}\nGroup: ${group}\nSolvers: ${solvers}\nCreator: @${creatorUsername}\nThe community was enabled" +community_disabled_by_admin: An administrator has disabled your community ${communityName} +community_enabled_by_admin: An administrator has re-enabled your community ${communityName} +community_already_disabled: This community is already disabled +community_already_enabled: This community is already enabled diff --git a/locales/es.yaml b/locales/es.yaml index cd2163a4..b4a48bfe 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -719,10 +719,9 @@ payment_methods_saved: "Métodos de pago guardados ✅" custom_payment_method: "✍️ Método de pago personalizado" payment_methods_reset: "Métodos de pago eliminados. Los usuarios ahora pueden ingresar cualquier método de pago libremente." payment_methods_wizard_commands: "/reset — eliminar todos los métodos de pago y restaurar el comportamiento predeterminado\n/exit — salir sin guardar" -close_community_confirmation: | - Comunidad ${communityName} - Ordenes completadas: ${completedOrders} - Creador: @${creatorUsername} - Solvers: ${solvers} - ¿Está seguro que desea continuar? -community_closed_by_admin: Un administrador ha cerrado tu comunidad ${communityName} +community_disabled_info: "Comunidad: ${communityName}\nGrupo: ${group}\nSolvers: ${solvers}\nCreador: @${creatorUsername}\nLa comunidad fue deshabilitada" +community_enabled_info: "Comunidad: ${communityName}\nGrupo: ${group}\nSolvers: ${solvers}\nCreador: @${creatorUsername}\nLa comunidad fue habilitada" +community_disabled_by_admin: Un administrador ha deshabilitado tu comunidad ${communityName} +community_enabled_by_admin: Un administrador ha reactivado tu comunidad ${communityName} +community_already_disabled: Esta comunidad ya está deshabilitada +community_already_enabled: Esta comunidad ya está habilitada diff --git a/locales/fa.yaml b/locales/fa.yaml index 4f59430b..fcf41795 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -816,10 +816,9 @@ payment_methods_saved: "روش‌های پرداخت ذخیره شد ✅" custom_payment_method: "✍️ روش پرداخت سفارشی" payment_methods_reset: "روش‌های پرداخت حذف شدند. کاربران اکنون می‌توانند هر روش پرداختی را آزادانه وارد کنند." payment_methods_wizard_commands: "/reset — حذف همه روش‌های پرداخت و بازگرداندن رفتار پیش‌فرض\n/exit — خروج بدون ذخیره" -close_community_confirmation: | - جامعه ${communityName} - سفارشات تکمیل‌شده: ${completedOrders} - سازنده: @${creatorUsername} - حل‌کنندگان: ${solvers} - آیا مطمئن هستید که می‌خواهید ادامه دهید؟ -community_closed_by_admin: یک مدیر جامعه شما ${communityName} را بست +community_disabled_info: "جامعه: ${communityName}\nگروه: ${group}\nحل‌کنندگان: ${solvers}\nسازنده: @${creatorUsername}\nجامعه غیرفعال شد" +community_enabled_info: "جامعه: ${communityName}\nگروه: ${group}\nحل‌کنندگان: ${solvers}\nسازنده: @${creatorUsername}\nجامعه فعال شد" +community_disabled_by_admin: یک مدیر جامعه شما ${communityName} را غیرفعال کرد +community_enabled_by_admin: یک مدیر جامعه شما ${communityName} را مجدداً فعال کرد +community_already_disabled: این جامعه از قبل غیرفعال است +community_already_enabled: این جامعه از قبل فعال است diff --git a/locales/fr.yaml b/locales/fr.yaml index 045d63c2..d4a90118 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -716,10 +716,9 @@ payment_methods_saved: "Méthodes de paiement enregistrées ✅" custom_payment_method: "✍️ Méthode de paiement personnalisée" payment_methods_reset: "Méthodes de paiement supprimées. Les utilisateurs peuvent désormais saisir n'importe quelle méthode de paiement librement." payment_methods_wizard_commands: "/reset — supprimer toutes les méthodes de paiement et restaurer le comportement par défaut\n/exit — quitter sans enregistrer" -close_community_confirmation: | - Communauté ${communityName} - Commandes complétées: ${completedOrders} - Créateur: @${creatorUsername} - Solveurs: ${solvers} - Êtes-vous sûr de vouloir continuer? -community_closed_by_admin: Un administrateur a fermé votre communauté ${communityName} +community_disabled_info: "Communauté: ${communityName}\nGroupe: ${group}\nSolveurs: ${solvers}\nCréateur: @${creatorUsername}\nLa communauté a été désactivée" +community_enabled_info: "Communauté: ${communityName}\nGroupe: ${group}\nSolveurs: ${solvers}\nCréateur: @${creatorUsername}\nLa communauté a été activée" +community_disabled_by_admin: Un administrateur a désactivé votre communauté ${communityName} +community_enabled_by_admin: Un administrateur a réactivé votre communauté ${communityName} +community_already_disabled: Cette communauté est déjà désactivée +community_already_enabled: Cette communauté est déjà active diff --git a/locales/it.yaml b/locales/it.yaml index bf357714..09ba6228 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -714,10 +714,9 @@ payment_methods_saved: "Metodi di pagamento salvati ✅" custom_payment_method: "✍️ Metodo di pagamento personalizzato" payment_methods_reset: "Metodi di pagamento rimossi. Gli utenti possono ora inserire qualsiasi metodo di pagamento liberamente." payment_methods_wizard_commands: "/reset — rimuovere tutti i metodi di pagamento e ripristinare il comportamento predefinito\n/exit — uscire senza salvare" -close_community_confirmation: | - Comunità ${communityName} - Ordini completati: ${completedOrders} - Creatore: @${creatorUsername} - Risolutori: ${solvers} - Sei sicuro di voler continuare? -community_closed_by_admin: Un amministratore ha chiuso la tua comunità ${communityName} +community_disabled_info: "Comunità: ${communityName}\nGruppo: ${group}\nRisolutori: ${solvers}\nCreatore: @${creatorUsername}\nLa comunità è stata disabilitata" +community_enabled_info: "Comunità: ${communityName}\nGruppo: ${group}\nRisolutori: ${solvers}\nCreatore: @${creatorUsername}\nLa comunità è stata abilitata" +community_disabled_by_admin: Un amministratore ha disabilitato la tua comunità ${communityName} +community_enabled_by_admin: Un amministratore ha riattivato la tua comunità ${communityName} +community_already_disabled: Questa comunità è già disabilitata +community_already_enabled: Questa comunità è già attiva diff --git a/locales/ko.yaml b/locales/ko.yaml index c2d97cb7..ae565508 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -714,10 +714,9 @@ payment_methods_saved: "결제 방법이 저장되었습니다 ✅" custom_payment_method: "✍️ 사용자 지정 결제 방법" payment_methods_reset: "결제 방법이 삭제되었습니다. 이제 사용자는 어떤 결제 방법이든 자유롭게 입력할 수 있습니다." payment_methods_wizard_commands: "/reset — 모든 결제 방법을 삭제하고 기본 동작을 복원합니다\n/exit — 저장하지 않고 종료" -close_community_confirmation: | - 커뮤니티 ${communityName} - 완료된 주문: ${completedOrders} - 생성자: @${creatorUsername} - 해결사: ${solvers} - 계속하시겠습니까? -community_closed_by_admin: 관리자가 귀하의 커뮤니티 ${communityName}을 폐쇄했습니다 +community_disabled_info: "커뮤니티: ${communityName}\n그룹: ${group}\n해결사: ${solvers}\n생성자: @${creatorUsername}\n커뮤니티가 비활성화되었습니다" +community_enabled_info: "커뮤니티: ${communityName}\n그룹: ${group}\n해결사: ${solvers}\n생성자: @${creatorUsername}\n커뮤니티가 활성화되었습니다" +community_disabled_by_admin: 관리자가 귀하의 커뮤니티 ${communityName}을 비활성화했습니다 +community_enabled_by_admin: 관리자가 귀하의 커뮤니티 ${communityName}을 다시 활성화했습니다 +community_already_disabled: 이 커뮤니티는 이미 비활성화되어 있습니다 +community_already_enabled: 이 커뮤니티는 이미 활성화되어 있습니다 diff --git a/locales/pt.yaml b/locales/pt.yaml index 344a2c48..cd92c5e0 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -716,10 +716,9 @@ payment_methods_saved: "Métodos de pagamento salvos ✅" custom_payment_method: "✍️ Método de pagamento personalizado" payment_methods_reset: "Métodos de pagamento removidos. Os usuários agora podem inserir qualquer método de pagamento livremente." payment_methods_wizard_commands: "/reset — remover todos os métodos de pagamento e restaurar o comportamento padrão\n/exit — sair sem salvar" -close_community_confirmation: | - Comunidade ${communityName} - Ordens concluídas: ${completedOrders} - Criador: @${creatorUsername} - Solvers: ${solvers} - Tem certeza que deseja continuar? -community_closed_by_admin: Um administrador fechou sua comunidade ${communityName} +community_disabled_info: "Comunidade: ${communityName}\nGrupo: ${group}\nSolvers: ${solvers}\nCriador: @${creatorUsername}\nA comunidade foi desabilitada" +community_enabled_info: "Comunidade: ${communityName}\nGrupo: ${group}\nSolvers: ${solvers}\nCriador: @${creatorUsername}\nA comunidade foi habilitada" +community_disabled_by_admin: Um administrador desabilitou sua comunidade ${communityName} +community_enabled_by_admin: Um administrador reativou sua comunidade ${communityName} +community_already_disabled: Esta comunidade já está desabilitada +community_already_enabled: Esta comunidade já está habilitada diff --git a/locales/ru.yaml b/locales/ru.yaml index d04fcbca..738ea7e3 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -717,10 +717,9 @@ payment_methods_saved: "Способы оплаты сохранены ✅" custom_payment_method: "✍️ Пользовательский способ оплаты" payment_methods_reset: "Способы оплаты удалены. Пользователи теперь могут свободно вводить любой способ оплаты." payment_methods_wizard_commands: "/reset — удалить все способы оплаты и восстановить поведение по умолчанию\n/exit — выйти без сохранения" -close_community_confirmation: | - Сообщество ${communityName} - Выполненные заказы: ${completedOrders} - Создатель: @${creatorUsername} - Решатели: ${solvers} - Вы уверены, что хотите продолжить? -community_closed_by_admin: Администратор закрыл ваше сообщество ${communityName} +community_disabled_info: "Сообщество: ${communityName}\nГруппа: ${group}\nРешатели: ${solvers}\nСоздатель: @${creatorUsername}\nСообщество было отключено" +community_enabled_info: "Сообщество: ${communityName}\nГруппа: ${group}\nРешатели: ${solvers}\nСоздатель: @${creatorUsername}\nСообщество было активировано" +community_disabled_by_admin: Администратор отключил ваше сообщество ${communityName} +community_enabled_by_admin: Администратор повторно активировал ваше сообщество ${communityName} +community_already_disabled: Это сообщество уже отключено +community_already_enabled: Это сообщество уже активно diff --git a/locales/uk.yaml b/locales/uk.yaml index 6061e481..8f836874 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -713,10 +713,9 @@ payment_methods_saved: "Способи оплати збережено ✅" custom_payment_method: "✍️ Власний спосіб оплати" payment_methods_reset: "Способи оплати видалено. Користувачі тепер можуть вільно вводити будь-який спосіб оплати." payment_methods_wizard_commands: "/reset — видалити всі способи оплати та відновити поведінку за замовчуванням\n/exit — вийти без збереження" -close_community_confirmation: | - Спільнота ${communityName} - Виконані замовлення: ${completedOrders} - Створив: @${creatorUsername} - Вирішувачі: ${solvers} - Ви впевнені, що хочете продовжити? -community_closed_by_admin: Адміністратор закрив вашу спільноту ${communityName} +community_disabled_info: "Спільнота: ${communityName}\nГрупа: ${group}\nВирішувачі: ${solvers}\nСтворив: @${creatorUsername}\nСпільноту було вимкнено" +community_enabled_info: "Спільнота: ${communityName}\nГрупа: ${group}\nВирішувачі: ${solvers}\nСтворив: @${creatorUsername}\nСпільноту було увімкнено" +community_disabled_by_admin: Адміністратор вимкнув вашу спільноту ${communityName} +community_enabled_by_admin: Адміністратор повторно увімкнув вашу спільноту ${communityName} +community_already_disabled: Цю спільноту вже вимкнено +community_already_enabled: Ця спільнота вже активна diff --git a/models/community.ts b/models/community.ts index 0434174e..9e1d2640 100644 --- a/models/community.ts +++ b/models/community.ts @@ -47,6 +47,7 @@ export interface ICommunity extends Document { solvers: Types.DocumentArray; banned_users: Types.DocumentArray; public: boolean; + enabled: boolean; currencies: Array; payment_methods: Array; created_at: Date; @@ -78,6 +79,7 @@ const CommunitySchema = new Schema({ solvers: [usernameIdSchema], // users that are dispute solvers banned_users: [usernameIdSchema], // users that are banned from the community public: { type: Boolean, default: true }, + enabled: { type: Boolean, default: true }, currencies: { type: [String], required: true, From 49a1e26b0e3cabe262c3551aadb8601adf68164a Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Tue, 19 May 2026 02:58:25 -0300 Subject: [PATCH 5/9] fix: address coderabbit and mostronator review suggestions --- bot/middleware/user.ts | 6 +----- bot/modules/community/actions.ts | 3 +++ bot/modules/community/commands.ts | 22 +++++++++++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bot/middleware/user.ts b/bot/middleware/user.ts index 70f78424..0ae215a8 100644 --- a/bot/middleware/user.ts +++ b/bot/middleware/user.ts @@ -34,11 +34,7 @@ export const superAdminMiddleware = async ( ctx: CommunityContext, next: () => void, ) => { - const tgId = - 'callback_query' in ctx.update - ? ctx.update.callback_query.from.id.toString() - : undefined; - const admin = await validateSuperAdmin(ctx, tgId); + const admin = await validateSuperAdmin(ctx); if (!admin) return false; ctx.i18n.locale(admin.lang); ctx.admin = admin; diff --git a/bot/modules/community/actions.ts b/bot/modules/community/actions.ts index 3ca09f3e..e7d85aa3 100644 --- a/bot/modules/community/actions.ts +++ b/bot/modules/community/actions.ts @@ -127,6 +127,9 @@ export const withdrawEarnings = async (ctx: CommunityContext) => { _id: ctx.match?.[1], enabled: { $ne: false }, }); + if (community == null) { + return ctx.reply(ctx.i18n.t('community_not_found')); + } ctx.scene.enter('ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', { community, }); diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 6e6b035a..eee1dd08 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -260,7 +260,7 @@ export const deleteCommunity = async (ctx: CommunityContext) => { async function findCommunityByInput( ctx: MainContext, input: string, -): Promise<(typeof Community.prototype) | null> { +): Promise { if (input[0] === '@') { const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(`^${escapedInput}$`, 'i'); @@ -272,13 +272,15 @@ async function findCommunityByInput( function buildCommunityInfoText( ctx: MainContext, - community: (typeof Community.prototype), + community: typeof Community.prototype, creatorUsername: string, localeKey: string, ): string { const solversText = community.solvers.length > 0 - ? community.solvers.map((s: { username: string }) => `@${s.username}`).join(', ') + ? community.solvers + .map((s: { username: string }) => `@${s.username}`) + .join(', ') : '-'; const groupText = community.group || '-'; return ctx.i18n.t(localeKey, { @@ -327,7 +329,12 @@ export const disableCommunity = async (ctx: MainContext) => { } return ctx.reply( - buildCommunityInfoText(ctx, community, creatorUsername, 'community_disabled_info'), + buildCommunityInfoText( + ctx, + community, + creatorUsername, + 'community_disabled_info', + ), ); } catch (error) { logger.error(error); @@ -373,7 +380,12 @@ export const enableCommunity = async (ctx: MainContext) => { } return ctx.reply( - buildCommunityInfoText(ctx, community, creatorUsername, 'community_enabled_info'), + buildCommunityInfoText( + ctx, + community, + creatorUsername, + 'community_enabled_info', + ), ); } catch (error) { logger.error(error); From e115cc115ee2746631b31952d9685674d460eb18 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Wed, 20 May 2026 21:31:04 -0300 Subject: [PATCH 6/9] fix: preserve default_community_id when community is disabled, only clear on deletion --- util/communityHelper.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/util/communityHelper.ts b/util/communityHelper.ts index 76a18e4c..9b5006cc 100644 --- a/util/communityHelper.ts +++ b/util/communityHelper.ts @@ -27,20 +27,26 @@ export const getCommunityInfo = async ( if (chatType !== 'private' && chat?.username) { // Group chat - find community by group name const regex = new RegExp(`^@${chat.username}$`, 'i'); - community = await Community.findOne({ group: regex }); + community = await Community.findOne({ group: regex, enabled: { $ne: false } }); if (community) { communityId = community._id; } } else if (user.default_community_id) { // Private chat with default community communityId = user.default_community_id; - community = await Community.findById(communityId); + const foundCommunity = await Community.findById(communityId); - // If community not found, clear the user's default - if (!community) { + if (!foundCommunity) { + // Community was deleted - clear user's preference user.default_community_id = undefined; await user.save(); communityId = undefined; + } else if (foundCommunity.enabled !== false) { + // Community exists and is enabled + community = foundCommunity; + } else { + // Community exists but is disabled - preserve preference for when it's re-enabled + communityId = undefined; } } From a8647c0bbd281b105aff86de2502920b30842bd4 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Wed, 20 May 2026 21:40:43 -0300 Subject: [PATCH 7/9] style: apply prettier formatting to communityHelper --- util/communityHelper.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/communityHelper.ts b/util/communityHelper.ts index 9b5006cc..1d3cfd92 100644 --- a/util/communityHelper.ts +++ b/util/communityHelper.ts @@ -27,7 +27,10 @@ export const getCommunityInfo = async ( if (chatType !== 'private' && chat?.username) { // Group chat - find community by group name const regex = new RegExp(`^@${chat.username}$`, 'i'); - community = await Community.findOne({ group: regex, enabled: { $ne: false } }); + community = await Community.findOne({ + group: regex, + enabled: { $ne: false }, + }); if (community) { communityId = community._id; } From 19c286ae27cd0d26ab6e9d1fe7dfe9ac9e93707e Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Thu, 21 May 2026 00:57:36 -0300 Subject: [PATCH 8/9] fix: handle disabled/deleted community gracefully in order creation wizard --- bot/modules/orders/commands.ts | 46 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/bot/modules/orders/commands.ts b/bot/modules/orders/commands.ts index 75a9d385..5e1e7b91 100644 --- a/bot/modules/orders/commands.ts +++ b/bot/modules/orders/commands.ts @@ -187,32 +187,34 @@ async function enterWizard( user: UserDocument, type: string, ) { - const state: EnterWizardState = { - type, - user, - }; - if (user.default_community_id) { - // Use optimized community lookup - const communityInfo = await getCommunityInfo(user, 'private'); - const { community, isBanned } = communityInfo; - - if (!community) { - throw new Error('Default community not found'); - } + try { + const state: EnterWizardState = { + type, + user, + }; + if (user.default_community_id) { + const communityInfo = await getCommunityInfo(user, 'private'); + const { community, isBanned } = communityInfo; - // Check if user is banned - if (isBanned) { - return await messages.bannedUserErrorMessage(ctx, user); - } + if (!community) { + return await deletedCommunityMessage(ctx); + } - state.community = community; - state.currencies = community.currencies; - if (community.currencies.length === 1) { - state.currency = community.currencies[0]; + if (isBanned) { + return await messages.bannedUserErrorMessage(ctx, user); + } + + state.community = community; + state.currencies = community.currencies; + if (community.currencies.length === 1) { + state.currency = community.currencies[0]; + } } - } - await ctx.scene.enter(Scenes.CREATE_ORDER, state); + await ctx.scene.enter(Scenes.CREATE_ORDER, state); + } catch (error) { + logger.error(error); + } } const isMaxPending = async (user: UserDocument) => { From c21fa020d80e852b3fd6136438b21a0022c94cf2 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Thu, 21 May 2026 13:41:26 -0300 Subject: [PATCH 9/9] fix: actually unlink user from disabled community and fix misleading message --- bot/modules/orders/messages.ts | 2 ++ locales/de.yaml | 2 +- locales/en.yaml | 2 +- locales/es.yaml | 2 +- locales/fa.yaml | 2 +- locales/fr.yaml | 2 +- locales/it.yaml | 2 +- locales/ko.yaml | 2 +- locales/pt.yaml | 2 +- locales/ru.yaml | 2 +- locales/uk.yaml | 2 +- 11 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bot/modules/orders/messages.ts b/bot/modules/orders/messages.ts index db849102..d8b34b0b 100644 --- a/bot/modules/orders/messages.ts +++ b/bot/modules/orders/messages.ts @@ -86,6 +86,8 @@ export const createOrderWizardStatus = ( export const deletedCommunityMessage = async (ctx: MainContext) => { try { + ctx.user.default_community_id = undefined; + await ctx.user.save(); await ctx.reply(ctx.i18n.t('community_deleted')); } catch (error) { logger.error(error); diff --git a/locales/de.yaml b/locales/de.yaml index 6d2b163a..bdab9ad6 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -571,7 +571,7 @@ bot_kicked: Bot was kicked from chat, must be member and admin chat_not_found: Chat not found, are you sure you added the bot to the group/channel? not_member: You are not member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the Id has changed, check the chat Id again -community_deleted: Diese Community wurde aufgrund von Inaktivität gelöscht, ich habe dich davon getrennt, versuche die Bestellung erneut zu erstellen +community_deleted: Deine Standardgemeinschaft wurde deaktiviert. Ich habe dich davon getrennt. Versuche die Bestellung erneut zu erstellen. disput_too_soon: Sie können einen Streit nicht so früh beginnen, seien Sie geduldig und warten Sie ein paar Minuten, bis Ihre Gegenpartei antwortet maintenance: 🚨 Bot in Wartung, bitte versuche es später noch einmal 🚨 # START modules/nostr diff --git a/locales/en.yaml b/locales/en.yaml index c635350a..2f553a94 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -577,7 +577,7 @@ bot_kicked: bot was kicked from the chat, it must be a member and admin chat_not_found: Chat not found. Are you sure you added the bot to the group/channel? not_member: You are not a member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the ID has changed, check the chat ID again -community_deleted: This community was deleted due to inactivity. I have unlinked you from it, try to create the order again +community_deleted: Your default community has been disabled. I unlinked you from that community. Try creating your order again. dispute_too_soon: You can't start a dispute too soon, be patient and wait a few minutes for your counterparty to reply maintenance: 🚨 Bot in maintenance, please try again later 🚨 diff --git a/locales/es.yaml b/locales/es.yaml index b4a48bfe..4a09cbef 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -572,7 +572,7 @@ bot_kicked: El bot fue pateado del chat, debe ser miembro y administrador chat_not_found: Chat no encontrado, ¿Estas segur@ que agregaste al bot al chat? not_member: No eres miembro de ese chat upgraded_to_supergroup: El chat fue actualizado a 'supergrupo' y el Id ha cambiado, consulta el Id del chat nuevamente -community_deleted: Esta comunidad fue eliminada por inactividad, te he desvinculado de ella, intenta crear la orden nuevamente +community_deleted: Tu comunidad predeterminada ha sido deshabilitada. Te he desvinculado de ella. Intenta crear la orden nuevamente. dispute_too_soon: No puedes iniciar una disputa tan pronto, ten paciencia y espera unos minutos a que tu contraparte responda maintenance: 🚨 Bot en mantenimiento, inténtalo de nuevo más tarde 🚨 diff --git a/locales/fa.yaml b/locales/fa.yaml index fcf41795..e2d89ddc 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -656,7 +656,7 @@ bot_kicked: ربات از گروه بیرون انداخته شده است، ب chat_not_found: گروه یافت نشد. مطمئن هستید که ربات را به گروه/کانال اضافه کرده‌اید؟ not_member: شما عضو آن گروه نیستید. upgraded_to_supergroup: این گروه به «ابرگروه» ارتقا داده شده و شناسه‌اش تغییر پیدا کرده است؛ شناسه گروه را دوباره بررسی کنید. -community_deleted: این اجتماع، به دلیل عدم فعالیت، حذف شده است. من اتصال شما به آن را قطع کردم، ایجاد سفارش را دوباره امتحان کنید. +community_deleted: جامعه پیش‌فرض شما غیرفعال شده است. من اتصال شما به آن را قطع کردم. ایجاد سفارش را دوباره امتحان کنید. dispute_too_soon: شما نمی‌توانید اینقدر زود یک مشاجره ثبت کنید، صبور باشید و چند دقیقه منتظر پاسخ طرف مقابلتان بمانید. maintenance: 🚨 ربات در دست تعمیر است، لطفاً بعداً دوباره امتحان کنید. 🚨 # START modules/community diff --git a/locales/fr.yaml b/locales/fr.yaml index d4a90118..f1ca15d6 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -570,7 +570,7 @@ bot_kicked: le bot a été expulsé du chat, il doit être membre et administrat chat_not_found: Chat introuvable. Êtes-vous sûr d'avoir ajouté le bot au groupe/canal ? not_member: Vous n'êtes pas membre de ce chat upgraded_to_supergroup: Le chat a été transformé en "supergroupe" et l'identifiant a changé. Vérifiez à nouveau l'identifiant du chat. -community_deleted: Cette communauté a été supprimée pour cause d'inactivité. Je vous en ai dissocié, essayez de créer l'offre à nouveau. +community_deleted: Votre communauté par défaut a été désactivée. Je vous en ai dissocié. Essayez de créer l'offre à nouveau. dispute_too_soon: Vous ne pouvez pas lancer un litige trop tôt, soyez patient et attendez quelques minutes pour que votre contrepartie vous réponde. maintenance: 🚨 Bot en maintenance, veuillez réessayer plus tard 🚨 diff --git a/locales/it.yaml b/locales/it.yaml index 09ba6228..3e822ef3 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -568,7 +568,7 @@ bot_kicked: Bot was kicked from chat, must be member and admin chat_not_found: Chat not found, are you sure you added the bot to the group/channel? not_member: You are not member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the Id has changed, check the chat Id again -community_deleted: questa community è stata eliminata per inattività, ti ho scollegato da essa, prova a creare di nuovo l'ordine +community_deleted: La tua comunità predefinita è stata disabilitata. Ti ho scollegato da essa. Prova a creare di nuovo l'ordine. dispute_too_soon: Non puoi avviare una controversia così presto, sii paziente e attendi qualche minuto affinché la tua controparte risponda maintenance: 🚨 Bot in manutenzione, riprova più tardi 🚨 # START modules/nostr diff --git a/locales/ko.yaml b/locales/ko.yaml index ae565508..16e1be6d 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -568,7 +568,7 @@ bot_kicked: 봇이 채팅에서 쫓겨났습니다. 멤버 혹은 관리자여 chat_not_found: 대화를 찾을 수 없습니다. 봇을 그룹이나 채널에 추가했는지 확인해주세요. not_member: 당신은 해당 대화의 멤버가 아닙니다. upgraded_to_supergroup: 해당 대화는 '슈퍼그룹'으로 승격되었습니다. ID가 바뀌었거나 잘못되지 않았는지 확인해 주세요. -community_deleted: 이 커뮤니티는 활동이 없는 관계로 삭제되었습니다. 당신은 해당 커뮤니티에서 연결이 해제되었습니다. 주문을 다시 생성해보세요. +community_deleted: 기본 커뮤니티가 비활성화되었습니다. 해당 커뮤니티에서 연결을 해제했습니다. 주문을 다시 생성해보세요. dispute_too_soon: 이렇게 빨리 분쟁 해소 절차를 시작할 수 없습니다. 거래 상대방이 응답할 때까지 몇 분만 더 기다려 주세요. maintenance: 🚨 봇이 정비 중에 있습니다. 잠시 후 다시 시도해 주세요. 🚨 diff --git a/locales/pt.yaml b/locales/pt.yaml index cd92c5e0..47a9af56 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -570,7 +570,7 @@ bot_kicked: Bot was kicked from chat, must be member and admin chat_not_found: Chat not found, are you sure you added the bot to the group/channel? not_member: You are not member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the Id has changed, check the chat Id again -community_deleted: Esta comunidade foi deletada por inatividade, desvinculei você dela, tente criar o pedido novamente +community_deleted: Sua comunidade padrão foi desabilitada. Desvincuei você dela. Tente criar o pedido novamente. dispute_too_soon: Você não pode iniciar uma disputa tão cedo, seja paciente e aguarde alguns minutos para que sua contraparte responda maintenance: 🚨 Bot em manutenção, tente novamente mais tarde 🚨 # START modules/nostr diff --git a/locales/ru.yaml b/locales/ru.yaml index 738ea7e3..21093445 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -571,7 +571,7 @@ bot_kicked: Bot was kicked from chat, must be member and admin chat_not_found: Chat not found, are you sure you added the bot to the group/channel? not_member: You are not member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the Id has changed, check the chat Id again -community_deleted: Это сообщество было удалено из-за неактивности, я отвязал вас от него, попробуйте создать заказ заново +community_deleted: Ваше сообщество по умолчанию было отключено. Я отвязал вас от него. Попробуйте создать заказ заново. dispute_too_soon: Нельзя начинать диспут так скоро, наберитесь терпения и подождите несколько минут, пока ответит ваш контрагент maintenance: 🚨 Бот находится на обслуживании, повторите попытку позже 🚨 # START modules/nostr diff --git a/locales/uk.yaml b/locales/uk.yaml index 8f836874..fa79e4b5 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -567,7 +567,7 @@ bot_kicked: Bot was kicked from chat, must be member and admin chat_not_found: Chat not found, are you sure you added the bot to the group/channel? not_member: You are not member of that chat upgraded_to_supergroup: The chat was upgraded to 'supergroup' and the Id has changed, check the chat Id again -community_deleted: Ця спільнота була видалена через бездіяльність, я від’єднав вас від неї, спробуйте створити замовлення знову +community_deleted: Вашу спільноту за замовчуванням було вимкнено. Я від’єднав вас від неї. Спробуйте створити замовлення знову. dispute_too_soon: Ви не можете розпочати диспут так рано, наберіться терпіння та зачекайте кілька хвилин, поки ваш контрагент відповість maintenance: 🚨 Бот виконує технічне обслуговування, спробуйте пізніше 🚨 # START modules/nostr