diff --git a/.env-sample b/.env-sample index 009fda2f..c00f4a2e 100644 --- a/.env-sample +++ b/.env-sample @@ -111,3 +111,7 @@ MONITOR_URL='' MONITOR_AUTH_TOKEN='' # Bot name identifier sent with heartbeats (default: lnp2pBot) MONITOR_BOT_NAME='lnp2pBot' + +# Set to 'true' to enable the /community command, allowing users to create new communities. +# Default is false (disabled) so bot operators can run closed instances without exposing community creation. +COMMUNITY_CREATION_ENABLED=false diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 78e60525..ede199f4 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'; @@ -241,6 +241,144 @@ export const deleteCommunity = async (ctx: CommunityContext) => { } }; +async function findCommunityByInput( + ctx: MainContext, + input: string, +): Promise { + 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 undefined; + 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, + 2, + '\\<_community id \\| @groupUsername_\\>', + ))!; + if (!input) return; + + const community = await findCommunityByInput(ctx, input); + if (community === undefined) return; + if (community === null) { + return ctx.reply(ctx.i18n.t('community_not_found')); + } + + if (community.enabled === false) { + return ctx.reply(ctx.i18n.t('community_already_disabled')); + } + + community.enabled = false; + 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_disabled_by_admin', { + communityName: community.name, + }), + ); + } catch (notifyError) { + logger.error(notifyError); + } + } + + 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 enableCommunity = async (ctx: MainContext) => { + try { + const [input] = (await validateParams( + ctx, + 2, + '\\<_community id \\| @groupUsername_\\>', + ))!; + if (!input) return; + + const community = await findCommunityByInput(ctx, input); + if (community === undefined) return; + if (community === null) { + return ctx.reply(ctx.i18n.t('community_not_found')); + } + + if (community.enabled !== false) { + return ctx.reply(ctx.i18n.t('community_already_enabled')); + } + + 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_enabled_by_admin', { + communityName: community.name, + }), + ); + } catch (notifyError) { + logger.error(notifyError); + } + } + + return ctx.reply( + buildCommunityInfoText( + ctx, + community, + creatorUsername, + 'community_enabled_info', + ), + ); + } catch (error) { + logger.error(error); + await ctx.reply(ctx.i18n.t('generic_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 e9ac6da4..f9282a0c 100644 --- a/bot/modules/community/index.ts +++ b/bot/modules/community/index.ts @@ -13,11 +13,14 @@ import * as Scenes from './scenes'; export const configure = (bot: Telegraf) => { bot.command('mycomm', userMiddleware, commands.communityAdmin); bot.command('mycomms', userMiddleware, commands.myComms); - // TODO: Uncomment when the community wizard is ready - // bot.command('community', userMiddleware, async ctx => { - // const { user } = ctx; - // await ctx.scene.enter('COMMUNITY_WIZARD_SCENE_ID', { bot, user }); - // }); + if (process.env.COMMUNITY_CREATION_ENABLED === 'true') { + bot.command('community', userMiddleware, async ctx => { + await ctx.scene.enter('COMMUNITY_WIZARD_SCENE_ID', { + bot, + user: ctx.user, + }); + }); + } bot.command('setcomm', userMiddleware, commands.setComm); bot.action(