From 454f397a79dd179decee5b2402edbb6706472a8c Mon Sep 17 00:00:00 2001 From: Alexander Ruliov Date: Sun, 21 Nov 2021 11:51:59 +0300 Subject: [PATCH] support silent bot messages #170 --- TODO.md | 5 + src/__tests__/captcha/custom.test.ts | 22 +++-- src/__tests__/commands/silent.test.ts | 101 ++++++++++++++++++++ src/__tests__/telegram-bot-server/server.ts | 1 + src/__tests__/test-data/types.ts | 1 + src/commands/allowInvitingBots.ts | 4 +- src/commands/ban.ts | 6 +- src/commands/banForFastRepliesToPosts.ts | 4 +- src/commands/banNewTelegramUsers.ts | 4 +- src/commands/banUsers.ts | 4 +- src/commands/buttonText.ts | 4 +- src/commands/captcha.ts | 20 ++-- src/commands/captchaMessage.ts | 5 +- src/commands/cas.ts | 4 +- src/commands/customCaptcha.ts | 16 +++- src/commands/deleteEntryMessages.ts | 4 +- src/commands/deleteEntryOnKick.ts | 4 +- src/commands/greeting.ts | 5 +- src/commands/greetingButtons.ts | 8 +- src/commands/help.ts | 15 +-- src/commands/language.ts | 1 + src/commands/lock.ts | 4 +- src/commands/noAttack.ts | 4 +- src/commands/noChannelLinks.ts | 4 +- src/commands/ping.ts | 24 +++++ src/commands/restrict.ts | 4 +- src/commands/setConfig.ts | 5 + src/commands/silent.ts | 31 ++++++ src/commands/skipOldUsers.ts | 4 +- src/commands/skipVerifiedUsers.ts | 4 +- src/commands/strict.ts | 4 +- src/commands/timeLimit.ts | 17 ++-- src/commands/trust.ts | 4 +- src/commands/underAttack.ts | 4 +- src/commands/viewConfig.ts | 13 ++- src/context.ts | 5 +- src/helpers/create-default-chat.ts | 1 + src/helpers/localizations.ts | 18 ++-- src/helpers/newcomers/greetUser.ts | 52 +++++----- src/helpers/newcomers/notifyCandidate.ts | 10 +- src/models/Chat.ts | 1 + src/updateHandler.ts | 4 + 42 files changed, 365 insertions(+), 90 deletions(-) create mode 100644 src/__tests__/commands/silent.test.ts create mode 100644 src/commands/ping.ts create mode 100644 src/commands/silent.ts diff --git a/TODO.md b/TODO.md index d4115191..613f407d 100644 --- a/TODO.md +++ b/TODO.md @@ -7,3 +7,8 @@ - Collect stats (at least requests per minute) - Use external queue for telegram updates (for seamless updates) - Write scripts for backup/restore +- Extract all commands to one object with their name/short description/long description/initializer for: + - viewConfig/setConfig can be implemented more convinient + - help will be always actual + - easy to implement command pallete https://github.com/Rulexec/shieldy/issues/3 +- Write tests for `/viewConfig` and `/setConfig` pair diff --git a/src/__tests__/captcha/custom.test.ts b/src/__tests__/captcha/custom.test.ts index 67a29600..00dee533 100644 --- a/src/__tests__/captcha/custom.test.ts +++ b/src/__tests__/captcha/custom.test.ts @@ -17,7 +17,8 @@ describe('custom captcha', () => { {name: 'should ask for custom question'}, {name: 'should ask for custom question, multianswer', isMultianswer: true}, {name: 'should ask for custom question, degradated', isDegradated: true}, - ])('$name', async ({isMultianswer, isDegradated}) => { + {name: 'should ask for custom question, silent', isSilent: true}, + ])('$name', async ({isMultianswer, isDegradated, isSilent = false}) => { const { appContext, handleUpdate, @@ -31,11 +32,18 @@ describe('custom captcha', () => { await findChatById(appContext, groupChat.id); - await appContext.database.setChatProperty({ - chatId: groupChat.id, - property: 'captchaType', - value: CaptchaType.CUSTOM, - }); + await Promise.all([ + appContext.database.setChatProperty({ + chatId: groupChat.id, + property: 'captchaType', + value: CaptchaType.CUSTOM, + }), + appContext.database.setChatProperty({ + chatId: groupChat.id, + property: 'silentMessages', + value: isSilent, + }), + ]); if (!isDegradated) { await appContext.database.setChatProperty({ @@ -109,6 +117,7 @@ describe('custom captcha', () => { expect(message.text).toBe( `@${user.username}, Say my name (60 sec)`, ); + expect(Boolean(message.isSilent)).toBe(isSilent); } for (let i = 0; i < 2; i++) { @@ -186,6 +195,7 @@ describe('custom captcha', () => { 'any message to this group within the time amount specified, otherwise ' + 'you will be kicked. Thank you! (60 sec)', ); + expect(Boolean(message.isSilent)).toBe(isSilent); } const anyMessageId = getUniqueCounterValue(); diff --git a/src/__tests__/commands/silent.test.ts b/src/__tests__/commands/silent.test.ts new file mode 100644 index 00000000..95cb014d --- /dev/null +++ b/src/__tests__/commands/silent.test.ts @@ -0,0 +1,101 @@ +import {findChatById} from '@root/helpers/find-chat'; +import {getUniqueCounterValue} from '@root/util/id/unique-counter'; +import {setupTest} from '../helpers/setup'; +import {createMessage} from '../test-data/updates'; + +describe('/silent', () => { + const botTest = setupTest(); + + afterEach(botTest.afterEach); + + it('should toggle silent notifications', async () => { + const { + appContext, + handleUpdate, + onIdle, + popMessages, + unixSeconds, + user, + groupChat, + } = await botTest.init(); + + await findChatById(appContext, groupChat.id); + await appContext.database.setChatProperty({ + chatId: groupChat.id, + property: 'silentMessages', + value: false, + }); + + const checkSilentPing = async (expectedSilent: boolean) => { + await handleUpdate( + createMessage({ + messageId: getUniqueCounterValue(), + user, + chat: groupChat, + unixSeconds, + text: '/ping', + isBotCommand: true, + }), + ); + await onIdle(); + + { + const messages = popMessages(); + + expect(messages.length).toBe(1); + expect(messages[0].text).toBe('pong'); + expect(Boolean(messages[0].isSilent)).toBe(expectedSilent); + } + }; + + await checkSilentPing(false); + + await handleUpdate( + createMessage({ + messageId: getUniqueCounterValue(), + user, + chat: groupChat, + unixSeconds, + text: '/silent', + isBotCommand: true, + }), + ); + await onIdle(); + + { + // Should change to silent + const messages = popMessages(); + + expect(messages.length).toBe(1); + expect(messages[0].text).toBe( + 'Sesuritu will send messages without sound', + ); + expect(messages[0].isSilent).toBeTruthy(); + } + + await checkSilentPing(true); + + await handleUpdate( + createMessage({ + messageId: getUniqueCounterValue(), + user, + chat: groupChat, + unixSeconds, + text: '/silent', + isBotCommand: true, + }), + ); + await onIdle(); + + { + // Should change to not silent + const messages = popMessages(); + + expect(messages.length).toBe(1); + expect(messages[0].text).toBe('Sesuritu will send messages with sound'); + expect(messages[0].isSilent).toBeFalsy(); + } + + await checkSilentPing(false); + }); +}); diff --git a/src/__tests__/telegram-bot-server/server.ts b/src/__tests__/telegram-bot-server/server.ts index 51e30ccf..9351e63d 100644 --- a/src/__tests__/telegram-bot-server/server.ts +++ b/src/__tests__/telegram-bot-server/server.ts @@ -118,6 +118,7 @@ export class TelegramBotServer { replyToMessageId: data.reply_to_message_id, inlineKeyboard: inlineKeyboard.length ? inlineKeyboard : undefined, unixSeconds, + isSilent: Boolean(data.disable_notification), }); const chat = this.getChatById(data.chat_id); diff --git a/src/__tests__/test-data/types.ts b/src/__tests__/test-data/types.ts index dfa82b84..cae4de74 100644 --- a/src/__tests__/test-data/types.ts +++ b/src/__tests__/test-data/types.ts @@ -7,6 +7,7 @@ export type Message = { replyToMessageId?: number; inlineKeyboard?: InlineKeyboardKey[]; unixSeconds: number; + isSilent?: boolean; }; export type MessageEdit = { diff --git a/src/commands/allowInvitingBots.ts b/src/commands/allowInvitingBots.ts index 60692912..7db4d0da 100644 --- a/src/commands/allowInvitingBots.ts +++ b/src/commands/allowInvitingBots.ts @@ -29,7 +29,9 @@ export function setupAllowInvitingBots(bot: Bot): void { ? 'allowInvitingBots_true' : 'allowInvitingBots_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/ban.ts b/src/commands/ban.ts index 8827960e..de45c4c4 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -7,6 +7,7 @@ import {isGroup} from '@helpers/isGroup'; import {Bot} from '@root/types/index'; import {checkLock} from '@middlewares/checkLock'; import {botDeleteMessageSafe} from '@root/helpers/deleteMessageSafe'; +import {Extra} from 'telegraf'; export function setupBan(bot: Bot): void { bot.command('ban', checkLock, clarifyIfPrivateMessages, async (ctx) => { @@ -61,6 +62,9 @@ export function setupBan(bot: Bot): void { }); } // Reply with success - await ctx.replyWithMarkdown(ctx.translate('trust_success')); + await ctx.replyWithMarkdown( + ctx.translate('trust_success'), + Extra.notifications(!ctx.dbchat.silentMessages), + ); }); } diff --git a/src/commands/banForFastRepliesToPosts.ts b/src/commands/banForFastRepliesToPosts.ts index 3a7fc36a..28bfc389 100644 --- a/src/commands/banForFastRepliesToPosts.ts +++ b/src/commands/banForFastRepliesToPosts.ts @@ -29,7 +29,9 @@ export function setupBanForFastRepliesToPosts(bot: Bot): void { ? 'banForFastRepliesToPosts_true' : 'banForFastRepliesToPosts_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/banNewTelegramUsers.ts b/src/commands/banNewTelegramUsers.ts index 6c7fa4ba..f4e3efe4 100644 --- a/src/commands/banNewTelegramUsers.ts +++ b/src/commands/banNewTelegramUsers.ts @@ -26,7 +26,9 @@ export function setupBanNewTelegramUsers(bot: Bot): void { ? 'banNewTelegramUsers_true' : 'banNewTelegramUsers_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/banUsers.ts b/src/commands/banUsers.ts index 0f3d72a9..13d647cd 100644 --- a/src/commands/banUsers.ts +++ b/src/commands/banUsers.ts @@ -18,7 +18,9 @@ export function setupBanUsers(bot: Bot): void { ctx.replyWithMarkdown( ctx.translate(chat.banUsers ? 'banUsers_true' : 'banUsers_false'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }); } diff --git a/src/commands/buttonText.ts b/src/commands/buttonText.ts index a0f67d53..a8f2f27e 100644 --- a/src/commands/buttonText.ts +++ b/src/commands/buttonText.ts @@ -25,7 +25,9 @@ export function setupButtonText(bot: Bot): void { }); await ctx.replyWithMarkdown( ctx.translate('trust_success'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/captcha.ts b/src/commands/captcha.ts index 872f990e..fca55f45 100644 --- a/src/commands/captcha.ts +++ b/src/commands/captcha.ts @@ -24,15 +24,17 @@ export function setupCaptcha(appContext: AppContext): void { ctx.appContext.telegramApi.replyWithMarkdown( ctx, ctx.translate('captcha'), - Extra.inReplyTo(message.message_id).markup((m) => - m.inlineKeyboard([ - m.callbackButton(ctx.translate('simple'), 'simple'), - m.callbackButton(ctx.translate('digits'), 'digits'), - m.callbackButton(ctx.translate('button'), 'button'), - m.callbackButton(ctx.translate('image'), 'image'), - m.callbackButton(ctx.translate('custom'), 'custom'), - ]), - ), + Extra.inReplyTo(message.message_id) + .markup((m) => + m.inlineKeyboard([ + m.callbackButton(ctx.translate('simple'), 'simple'), + m.callbackButton(ctx.translate('digits'), 'digits'), + m.callbackButton(ctx.translate('button'), 'button'), + m.callbackButton(ctx.translate('image'), 'image'), + m.callbackButton(ctx.translate('custom'), 'custom'), + ]), + ) + .notifications(!ctx.dbchat.silentMessages), ), ); diff --git a/src/commands/captchaMessage.ts b/src/commands/captchaMessage.ts index 94082b16..cf3a1f4b 100644 --- a/src/commands/captchaMessage.ts +++ b/src/commands/captchaMessage.ts @@ -37,7 +37,9 @@ export function setupCaptchaMessage(appContext: AppContext): void { : 'captchaMessage_true' : 'captchaMessage_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); if (chat.customCaptchaMessage && chat.captchaMessage) { @@ -46,6 +48,7 @@ export function setupCaptchaMessage(appContext: AppContext): void { // @ts-ignore chat.captchaMessage.message.chat = undefined; await ctx.telegram.sendCopy(chat.id, chat.captchaMessage.message, { + ...Extra.notifications(!ctx.dbchat.silentMessages), entities: chat.captchaMessage.message.entities, }); } diff --git a/src/commands/cas.ts b/src/commands/cas.ts index fe961d2b..605bff32 100644 --- a/src/commands/cas.ts +++ b/src/commands/cas.ts @@ -18,7 +18,9 @@ export function setupCAS(bot: Bot): void { ctx.replyWithMarkdown( ctx.translate(chat.cas ? 'cas_true' : 'cas_false'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }); } diff --git a/src/commands/customCaptcha.ts b/src/commands/customCaptcha.ts index 765d857e..98e29acc 100644 --- a/src/commands/customCaptcha.ts +++ b/src/commands/customCaptcha.ts @@ -34,7 +34,10 @@ export function setupCustomCaptcha(appContext: AppContext): void { text = ctx.translate('custom_no_variants'); } - await ctx.replyWithMarkdown(text); + await ctx.replyWithMarkdown( + text, + Extra.notifications(!ctx.dbchat.silentMessages), + ); return BotMiddlewareNextStrategy.abort; }, @@ -52,7 +55,10 @@ export function setupCustomCaptcha(appContext: AppContext): void { value: ctx.dbchat.customCaptchaVariants, }); - await ctx.replyWithMarkdown(ctx.translate('custom_removed')); + await ctx.replyWithMarkdown( + ctx.translate('custom_removed'), + Extra.notifications(!ctx.dbchat.silentMessages), + ); return BotMiddlewareNextStrategy.abort; }, @@ -65,6 +71,7 @@ export function setupCustomCaptcha(appContext: AppContext): void { async (ctx) => { const message = await ctx.replyWithMarkdown( ctx.translate('custom_add_question'), + Extra.notifications(!ctx.dbchat.silentMessages), ); ctx.dbchat.lastReplySetting = { type: ReplySettingType.ADD_CUSTOM_CAPTCHA, @@ -115,6 +122,7 @@ export function setupCustomCaptcha(appContext: AppContext): void { ) { const botMessage = await ctx.replyWithMarkdown( ctx.translate('custom_add_answer'), + Extra.notifications(!ctx.dbchat.silentMessages), ); ctx.dbchat.lastReplySetting = { @@ -160,7 +168,9 @@ export function setupCustomCaptcha(appContext: AppContext): void { await ctx.replyWithMarkdown( ctx.translate('custom_success'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); } diff --git a/src/commands/deleteEntryMessages.ts b/src/commands/deleteEntryMessages.ts index 6392295f..51f9475d 100644 --- a/src/commands/deleteEntryMessages.ts +++ b/src/commands/deleteEntryMessages.ts @@ -26,7 +26,9 @@ export function setupDeleteEntryMessages(bot: Bot): void { ? 'deleteEntryMessages_true' : 'deleteEntryMessages_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/deleteEntryOnKick.ts b/src/commands/deleteEntryOnKick.ts index 46be9bae..e52d1ff6 100644 --- a/src/commands/deleteEntryOnKick.ts +++ b/src/commands/deleteEntryOnKick.ts @@ -26,7 +26,9 @@ export function setupDeleteEntryOnKick(bot: Bot): void { ? 'deleteEntryOnKick_true' : 'deleteEntryOnKick_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/greeting.ts b/src/commands/greeting.ts index 3c23f6e7..2eb18741 100644 --- a/src/commands/greeting.ts +++ b/src/commands/greeting.ts @@ -30,7 +30,9 @@ export function setupGreeting(bot: Bot): void { : 'greetsUsers_true' : 'greetsUsers_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); if (chat.greetingMessage && chat.greetsUsers) { // TODO: investigate @@ -38,6 +40,7 @@ export function setupGreeting(bot: Bot): void { // @ts-ignore chat.greetingMessage.message.chat = undefined; await ctx.telegram.sendCopy(chat.id, chat.greetingMessage.message, { + ...Extra.notifications(!ctx.dbchat.silentMessages), entities: chat.greetingMessage.message.entities, }); } diff --git a/src/commands/greetingButtons.ts b/src/commands/greetingButtons.ts index 16e24dd0..d92a7ad9 100644 --- a/src/commands/greetingButtons.ts +++ b/src/commands/greetingButtons.ts @@ -18,13 +18,17 @@ export function setupGreetingButtons(bot: Bot): void { await ctx.replyWithMarkdown( `${ctx.translate('greetingButtons')}`, - Extra.inReplyTo(ctx.message.message_id).webPreview(false), + Extra.inReplyTo(ctx.message.message_id) + .webPreview(false) + .notifications(!ctx.dbchat.silentMessages), ); await ctx.replyWithMarkdown( `${ ctx.dbchat.greetingButtons || ctx.translate('greetingButtonsEmpty') }`, - Extra.webPreview(false).HTML(true), + Extra.webPreview(false) + .HTML(true) + .notifications(!ctx.dbchat.silentMessages), ); await clarifyReply(ctx); }, diff --git a/src/commands/help.ts b/src/commands/help.ts index 441c78f7..c7aa69eb 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -3,6 +3,7 @@ import {checkLockMiddleware} from '@middlewares/checkLock'; import {clarifyIfPrivateMessagesMiddleware} from '@helpers/clarifyIfPrivateMessages'; import {AppContext} from '@root/types/app-context'; import {BotMiddlewareFn, BotMiddlewareNextStrategy} from '@root/bot/types'; +import {Extra} from 'telegraf'; export function setupHelp(appContext: AppContext): void { const {addBotCommand} = appContext; @@ -29,9 +30,10 @@ function sendHelp(ctx: Context): Promise { }); } return ctx - .replyWithMarkdown(ctx.translate('helpShieldy'), { - disable_web_page_preview: true, - }) + .replyWithMarkdown( + ctx.translate('helpShieldy'), + Extra.webPreview(false).notifications(!ctx.dbchat.silentMessages), + ) .then(() => { // }); @@ -40,9 +42,10 @@ function sendHelp(ctx: Context): Promise { export function sendHelpSafe(ctx: Context): Promise { try { return ctx - .replyWithMarkdown(ctx.translate('helpShieldy'), { - disable_web_page_preview: true, - }) + .replyWithMarkdown( + ctx.translate('helpShieldy'), + Extra.webPreview(false).notifications(!ctx.dbchat.silentMessages), + ) .then(() => { // }); diff --git a/src/commands/language.ts b/src/commands/language.ts index ec2fb726..6f927aad 100644 --- a/src/commands/language.ts +++ b/src/commands/language.ts @@ -47,6 +47,7 @@ export function setupLanguage(bot: Bot): void { ], ]), ); + extra = extra.notifications(!ctx.dbchat.silentMessages); ctx.replyWithMarkdown(ctx.translate('language_shieldy'), extra); }); diff --git a/src/commands/lock.ts b/src/commands/lock.ts index 5182ea21..8b2394a6 100644 --- a/src/commands/lock.ts +++ b/src/commands/lock.ts @@ -20,7 +20,9 @@ export function setupLock(bot: Bot): void { ctx.translate( chat.adminLocked ? 'lock_true_shieldy' : 'lock_false_shieldy', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }); } diff --git a/src/commands/noAttack.ts b/src/commands/noAttack.ts index 9b12f3b2..3eb34384 100644 --- a/src/commands/noAttack.ts +++ b/src/commands/noAttack.ts @@ -17,7 +17,9 @@ export function setupNoAttack(bot: Bot): void { ctx.replyWithMarkdown( ctx.translate(ctx.dbchat.noAttack ? 'noAttack_true' : 'noAttack_false'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }); } diff --git a/src/commands/noChannelLinks.ts b/src/commands/noChannelLinks.ts index 06fb1661..02e2c406 100644 --- a/src/commands/noChannelLinks.ts +++ b/src/commands/noChannelLinks.ts @@ -24,7 +24,9 @@ export function setupNoChannelLinks(bot: Bot): void { ctx.translate( chat.noChannelLinks ? 'noChannelLinks_true' : 'noChannelLinks_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }, ); diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 00000000..3493c4c5 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,24 @@ +import {Extra} from 'telegraf'; +import {assertNonNullish} from '@root/util/assert/assert-non-nullish'; +import {AppContext} from '@root/types/app-context'; +import {BotMiddlewareNextStrategy} from '@root/bot/types'; + +export function setupPing(appContext: AppContext): void { + const {addBotCommand, idling} = appContext; + + addBotCommand('ping', (ctx) => { + const {dbchat, message} = ctx; + assertNonNullish(message); + + idling.wrapTask(() => + ctx.replyWithMarkdown( + 'pong', + Extra.inReplyTo(message.message_id).notifications( + !dbchat.silentMessages, + ), + ), + ); + + return BotMiddlewareNextStrategy.abort; + }); +} diff --git a/src/commands/restrict.ts b/src/commands/restrict.ts index cb92ebab..1c70cf3b 100644 --- a/src/commands/restrict.ts +++ b/src/commands/restrict.ts @@ -18,7 +18,9 @@ export function setupRestrict(bot: Bot): void { ctx.replyWithMarkdown( ctx.translate(chat.restrict ? 'restrict_true' : 'restrict_false'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }); } diff --git a/src/commands/setConfig.ts b/src/commands/setConfig.ts index 1fec14c3..db294787 100644 --- a/src/commands/setConfig.ts +++ b/src/commands/setConfig.ts @@ -156,6 +156,11 @@ export function setupSetConfig(bot: Bot): void { ctx.dbchat.skipVerifiedUsers = boolValue; break; } + case 'silent': { + const boolValue = value === 'true'; + ctx.dbchat.silentMessages = boolValue; + break; + } default: break; } diff --git a/src/commands/silent.ts b/src/commands/silent.ts new file mode 100644 index 00000000..eed2695b --- /dev/null +++ b/src/commands/silent.ts @@ -0,0 +1,31 @@ +import {Extra} from 'telegraf'; +import {assertNonNullish} from '@root/util/assert/assert-non-nullish'; +import {AppContext} from '@root/types/app-context'; +import {BotMiddlewareNextStrategy} from '@root/bot/types'; + +export function setupSilent(appContext: AppContext): void { + const {addBotCommand, database, idling} = appContext; + + addBotCommand('silent', async (ctx) => { + const {dbchat: chat, message, translate} = ctx; + assertNonNullish(message); + + const isSilent = !chat.silentMessages; + chat.silentMessages = isSilent; + + await database.setChatProperty({ + chatId: chat.id, + property: 'silentMessages', + value: isSilent, + }); + + idling.wrapTask(() => + ctx.replyWithMarkdown( + translate(isSilent ? 'silentMessages_true' : 'silentMessages_false'), + Extra.inReplyTo(message.message_id).notifications(!isSilent), + ), + ); + + return BotMiddlewareNextStrategy.abort; + }); +} diff --git a/src/commands/skipOldUsers.ts b/src/commands/skipOldUsers.ts index b9e46a0c..6aa876cf 100644 --- a/src/commands/skipOldUsers.ts +++ b/src/commands/skipOldUsers.ts @@ -24,7 +24,9 @@ export function setupSkipOldUsers(bot: Bot): void { ctx.translate( chat.skipOldUsers ? 'skipOldUsers_true' : 'skipOldUsers_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }, ); diff --git a/src/commands/skipVerifiedUsers.ts b/src/commands/skipVerifiedUsers.ts index afb47612..d5abf076 100644 --- a/src/commands/skipVerifiedUsers.ts +++ b/src/commands/skipVerifiedUsers.ts @@ -26,7 +26,9 @@ export function setupSkipVerifiedUsers(bot: Bot): void { ? 'skipVerifiedUsers_true' : 'skipVerifiedUsers_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }, ); diff --git a/src/commands/strict.ts b/src/commands/strict.ts index d5a84a85..7882d8c1 100644 --- a/src/commands/strict.ts +++ b/src/commands/strict.ts @@ -18,7 +18,9 @@ export function setupStrict(bot: Bot): void { ctx.replyWithMarkdown( ctx.translate(chat.strict ? 'strict_true' : 'strict_false'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !chat.silentMessages, + ), ); }); } diff --git a/src/commands/timeLimit.ts b/src/commands/timeLimit.ts index 33dbecab..3fef3372 100644 --- a/src/commands/timeLimit.ts +++ b/src/commands/timeLimit.ts @@ -31,20 +31,23 @@ export function setupTimeLimit(bot: Bot): void { `${ctx.translate('time_limit_selected')} (${ chat.timeGiven } ${ctx.translate('seconds')})`, + Extra.notifications(!ctx.dbchat.silentMessages), ); } return ctx.replyWithMarkdown( ctx.translate('time_limit'), - Extra.inReplyTo(ctx.message.message_id).markup((m) => - m.inlineKeyboard( - options.map((a) => - a.map((o) => - m.callbackButton(`${o} ${ctx.translate('seconds')}`, o), + Extra.inReplyTo(ctx.message.message_id) + .markup((m) => + m.inlineKeyboard( + options.map((a) => + a.map((o) => + m.callbackButton(`${o} ${ctx.translate('seconds')}`, o), + ), ), ), - ), - ), + ) + .notifications(!ctx.dbchat.silentMessages), ); }); diff --git a/src/commands/trust.ts b/src/commands/trust.ts index a270847c..b865d505 100644 --- a/src/commands/trust.ts +++ b/src/commands/trust.ts @@ -78,7 +78,9 @@ export function setupTrust(bot: Bot): void { // Reply with success await ctx.replyWithMarkdown( ctx.translate('trust_success'), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/underAttack.ts b/src/commands/underAttack.ts index b7493afd..9fbd941e 100644 --- a/src/commands/underAttack.ts +++ b/src/commands/underAttack.ts @@ -23,7 +23,9 @@ export function setupUnderAttack(bot: Bot): void { ctx.translate( ctx.dbchat.underAttack ? 'underAttack_true' : 'underAttack_false', ), - Extra.inReplyTo(ctx.message.message_id), + Extra.inReplyTo(ctx.message.message_id).notifications( + !ctx.dbchat.silentMessages, + ), ); }, ); diff --git a/src/commands/viewConfig.ts b/src/commands/viewConfig.ts index 93c03143..af069a5a 100644 --- a/src/commands/viewConfig.ts +++ b/src/commands/viewConfig.ts @@ -79,9 +79,12 @@ skipOldUsers: ${chat.skipOldUsers} skipVerifiedUsers: ${chat.skipVerifiedUsers} restrictTimeHours: ${chat.restrictTime || 24} banNewTelegramUsers: ${chat.banNewTelegramUsers} +silent: ${Boolean(chat.silentMessages)} greetingButtons: ${chat.greetingButtons || 'Not set'}`, - Extra.inReplyTo(ctx.message.message_id).HTML(true), + Extra.inReplyTo(ctx.message.message_id) + .HTML(true) + .notifications(!chat.silentMessages), ); if (chat.greetingMessage) { // TODO: investigate @@ -89,7 +92,9 @@ greetingButtons: // @ts-ignore chat.greetingMessage.message.chat = undefined; await ctx.telegram.sendCopy(ctx.dbchat.id, chat.greetingMessage.message, { - ...Extra.webPreview(false).inReplyTo(ctx.message.message_id), + ...Extra.webPreview(false) + .inReplyTo(ctx.message.message_id) + .notifications(!ctx.dbchat.silentMessages), entities: chat.greetingMessage.message.entities, }); } @@ -99,7 +104,9 @@ greetingButtons: // @ts-ignore chat.captchaMessage.message.chat = undefined; await ctx.telegram.sendCopy(ctx.dbchat.id, chat.captchaMessage.message, { - ...Extra.webPreview(false).inReplyTo(ctx.message.message_id), + ...Extra.webPreview(false) + .inReplyTo(ctx.message.message_id) + .notifications(!ctx.dbchat.silentMessages), entities: chat.captchaMessage.message.entities, }); } diff --git a/src/context.ts b/src/context.ts index 11a5d047..358e3830 100644 --- a/src/context.ts +++ b/src/context.ts @@ -39,7 +39,10 @@ export function createContext({ telegramApi: { replyWithMarkdown: (context, markdown, extra) => { return idling.wrapTask(() => - context.replyWithMarkdown(markdown, extra), + context.replyWithMarkdown(markdown, { + ...extra, + disable_notification: Boolean(context.dbchat?.silentMessages), + }), ); }, }, diff --git a/src/helpers/create-default-chat.ts b/src/helpers/create-default-chat.ts index 4d06d4ea..ca9ad800 100644 --- a/src/helpers/create-default-chat.ts +++ b/src/helpers/create-default-chat.ts @@ -28,5 +28,6 @@ export const createDefaultChat = (id: number): Chat => { members: [], restrictTime: 24, banNewTelegramUsers: false, + silentMessages: false, }; }; diff --git a/src/helpers/localizations.ts b/src/helpers/localizations.ts index 60b1b0da..0ef425ca 100644 --- a/src/helpers/localizations.ts +++ b/src/helpers/localizations.ts @@ -1,7 +1,7 @@ export const localizations = { helpShieldy: { - en: "Sesuritu — is the best solution in Telegram to fight annoying spammers. It asks newcomers to execute a set action within a set period, otherwise it kicks them. It's this easy. Make sure @Sesuritu\\_bot is an admin at your chat and it's all done! You can also further setup the behaviour with the following commands:\n\n/help — shows this message\n/language — switches language\n/captcha — changes type of captcha used\n/timeLimit — changes amount of time given to newcomers\n/lock — makes commands accessible only by admins\n/restrict — restricts newcomers to send only text messages in the first 24 hours\n/deleteEntryMessages — delete messages about user entry\n/greeting — greets users who pass the test\n/trust — reply with this command to a message sent by user that you don't want to check\n/ban — same as trust, but the opposite\n/strict — use when you don't want to receive any newcomers' messages but captcha solutions until they pass captcha\n/customCaptchaMessage — setup custom captcha message\n/deleteGreetingTime — setup when to delete Sesuritu's greeting in seconds like `/deleteGreetingTime 100`, reset by setting it to 0\n/banUsers — whether to ban or to kick users\n/deleteEntryOnKick — whether to delete entry messages for users who failed captcha\n/cas — whether to use Combot Anti-Spam or not\n/underAttack — toggle the mode to automatically kick all newcomers\n/noAttack — disables Sesuritu\n/noChannelLinks — automatically delete messages that link to other telegram channels\n/viewConfig — view the current Sesuritu configuration\n/buttonText — change the captcha button text in the form of `/buttonText I'm not a bot!`\n/allowInvitingBots — whether users can invite other bots or not\n/greetingButtons — setup buttons for greeting message\n/skipOldUsers — don't show captcha to Telegram accounts with ID less than 1000000000\n/skipVerifiedUsers — don't show captcha to users who have ever passed captcha in any chat\n/setConfig — setup Sesuritu configuration in one message\n/banForFastRepliesToPosts — turn on or off banning users who reply to new channel posts within 5 seconds after posts are created\n/restrictTime — setup how much time will Sesuritu /restrict a newcomer in hours like `/restrictTime 24`, reset by setting it to 0\n/banNewTelegramUsers — instantly ban (or kick) newcomers with Telegram ID over 1 000 000 000\n/addCustomCaptcha — add custom captcha type question&answer\n/viewCustomCaptcha — view custom captcha type questions&answers\n/removeAllCustomCaptcha — remove all custom captcha type questions\n\nSesuritu code is open source and can be found [here](https://github.com/Rulexec/shieldy).", - ru: 'Sesuritu — это лучшее решение в Телеграме по борьбе с надоедливыми спамерами. Он просит всех новичков сделать определенное действие в течение заданного времени и банит их, в случае несогласия. Да, это настолько просто. Убедитесь, что @Sesuritu\\_bot — админ в вашей группе, и все уже готово! Вы можете продолжить настройку бота, воспользовавшись следующими командами:\n\n/help — показывает это сообщение\n/language — меняет язык\n/captcha — меняет тип капчи\n/timeLimit — меняет количество времени, данное новичками\n/lock — делает команды доступными только админами\n/restrict — запрещает новичкам посылать что-либо, кроме текстовых сообщений в первые 24 часа\n/deleteEntryMessages — удалять сообщения о входе пользователей в чат\n/greeting — встречать прошедших проверку пользователей сообщением\n/trust — ответьте этой командой на сообщение пользователя, которого не нужно проверять\n/ban — то же, что и trust, но наоборот\n/strict — используйте, если не хотите получать никакие сообщения от новичков кроме решения капчи, до прохождения капчи\n/customCaptchaMessage — установить свое сообщение с капчей\n/deleteGreetingTime — установить время в секундах, когда Sesuritu должен удалить приветствие вида `/deleteGreetingTime 100`\nотключите эту фцнкцию, установив значение 0\n/banUsers — банить или кикать пользователей\n/deleteEntryOnKick — удалять ли сообщения о входе для пользователей, которые провалили капчу\n/cas — использовать Combot Anti-Spam или нет\n/underAttack — включить или выключить режим автоматического кика всех новичков\n/noAttack — отключить Sesuritu\n/noChannelLinks — автоматически удалять сообщения со ссылками на Телеграм-каналы\n/viewConfig — посмотреть текущие настройки Sesuritu\n/buttonText — изменить текст кнопки капчи в формате `/buttonText Я не бот!`\n/allowInvitingBots — могут ли пользователи приглашать в чат других ботов\n/greetingButtons — настроить кнопки для приветственного сообщения\n/skipOldUsers — не спрашивать капчу у Телеграм-аккаунтов с ID меньше 1000000000\n/skipVerifiedUsers — не спрашивать капчу у людей, которые когда-либо успешно проходили капчу в других чатах\n/setConfig — настроить Sesuritu одним сообщением\n/banForFastRepliesToPosts — включить или выключить бан пользователей, которые отвечают на посты на канале в течение 5 секунд после создания этих постов\n/restrictTime — настроить, на сколько часов Sesuritu будет ограничивать отправку медиа через /restrict для новичков форматом `/restrictTime 24`, можно сбросить время на стандартное, установив 0\n/banNewTelegramUsers — сразу банить (или кикать) новичков с Телеграм ID больше 1 000 000 000\n/addCustomCaptcha — добавить вопрос&ответ для пользовательского типа капчи\n/viewCustomCaptcha — просмотреть вопросы&ответы пользовательского типа капчи\n/removeAllCustomCaptcha — удалить все вопросы пользовательского типа капчи\n\n Код Sesuritu в открытом доступе [вот тут](https://github.com/Rulexec/shieldy).', + en: "Sesuritu — is the best solution in Telegram to fight annoying spammers. It asks newcomers to execute a set action within a set period, otherwise it kicks them. It's this easy. Make sure @Sesuritu\\_bot is an admin at your chat and it's all done! You can also further setup the behaviour with the following commands:\n\n/help — shows this message\n/language — switches language\n/captcha — changes type of captcha used\n/timeLimit — changes amount of time given to newcomers\n/lock — makes commands accessible only by admins\n/restrict — restricts newcomers to send only text messages in the first 24 hours\n/deleteEntryMessages — delete messages about user entry\n/greeting — greets users who pass the test\n/trust — reply with this command to a message sent by user that you don't want to check\n/ban — same as trust, but the opposite\n/strict — use when you don't want to receive any newcomers' messages but captcha solutions until they pass captcha\n/customCaptchaMessage — setup custom captcha message\n/deleteGreetingTime — setup when to delete Sesuritu's greeting in seconds like `/deleteGreetingTime 100`, reset by setting it to 0\n/banUsers — whether to ban or to kick users\n/deleteEntryOnKick — whether to delete entry messages for users who failed captcha\n/cas — whether to use Combot Anti-Spam or not\n/underAttack — toggle the mode to automatically kick all newcomers\n/noAttack — disables Sesuritu\n/noChannelLinks — automatically delete messages that link to other telegram channels\n/viewConfig — view the current Sesuritu configuration\n/buttonText — change the captcha button text in the form of `/buttonText I'm not a bot!`\n/allowInvitingBots — whether users can invite other bots or not\n/greetingButtons — setup buttons for greeting message\n/skipOldUsers — don't show captcha to Telegram accounts with ID less than 1000000000\n/skipVerifiedUsers — don't show captcha to users who have ever passed captcha in any chat\n/setConfig — setup Sesuritu configuration in one message\n/banForFastRepliesToPosts — turn on or off banning users who reply to new channel posts within 5 seconds after posts are created\n/restrictTime — setup how much time will Sesuritu /restrict a newcomer in hours like `/restrictTime 24`, reset by setting it to 0\n/banNewTelegramUsers — instantly ban (or kick) newcomers with Telegram ID over 1 000 000 000\n/addCustomCaptcha — add custom captcha type question&answer\n/viewCustomCaptcha — view custom captcha type questions&answers\n/removeAllCustomCaptcha — remove all custom captcha type questions\n/silent — toggle silent notifications\n\nSesuritu code is open source and can be found [here](https://github.com/Rulexec/shieldy).", + ru: 'Sesuritu — это лучшее решение в Телеграме по борьбе с надоедливыми спамерами. Он просит всех новичков сделать определенное действие в течение заданного времени и банит их, в случае несогласия. Да, это настолько просто. Убедитесь, что @Sesuritu\\_bot — админ в вашей группе, и все уже готово! Вы можете продолжить настройку бота, воспользовавшись следующими командами:\n\n/help — показывает это сообщение\n/language — меняет язык\n/captcha — меняет тип капчи\n/timeLimit — меняет количество времени, данное новичками\n/lock — делает команды доступными только админами\n/restrict — запрещает новичкам посылать что-либо, кроме текстовых сообщений в первые 24 часа\n/deleteEntryMessages — удалять сообщения о входе пользователей в чат\n/greeting — встречать прошедших проверку пользователей сообщением\n/trust — ответьте этой командой на сообщение пользователя, которого не нужно проверять\n/ban — то же, что и trust, но наоборот\n/strict — используйте, если не хотите получать никакие сообщения от новичков кроме решения капчи, до прохождения капчи\n/customCaptchaMessage — установить свое сообщение с капчей\n/deleteGreetingTime — установить время в секундах, когда Sesuritu должен удалить приветствие вида `/deleteGreetingTime 100`\nотключите эту фцнкцию, установив значение 0\n/banUsers — банить или кикать пользователей\n/deleteEntryOnKick — удалять ли сообщения о входе для пользователей, которые провалили капчу\n/cas — использовать Combot Anti-Spam или нет\n/underAttack — включить или выключить режим автоматического кика всех новичков\n/noAttack — отключить Sesuritu\n/noChannelLinks — автоматически удалять сообщения со ссылками на Телеграм-каналы\n/viewConfig — посмотреть текущие настройки Sesuritu\n/buttonText — изменить текст кнопки капчи в формате `/buttonText Я не бот!`\n/allowInvitingBots — могут ли пользователи приглашать в чат других ботов\n/greetingButtons — настроить кнопки для приветственного сообщения\n/skipOldUsers — не спрашивать капчу у Телеграм-аккаунтов с ID меньше 1000000000\n/skipVerifiedUsers — не спрашивать капчу у людей, которые когда-либо успешно проходили капчу в других чатах\n/setConfig — настроить Sesuritu одним сообщением\n/banForFastRepliesToPosts — включить или выключить бан пользователей, которые отвечают на посты на канале в течение 5 секунд после создания этих постов\n/restrictTime — настроить, на сколько часов Sesuritu будет ограничивать отправку медиа через /restrict для новичков форматом `/restrictTime 24`, можно сбросить время на стандартное, установив 0\n/banNewTelegramUsers — сразу банить (или кикать) новичков с Телеграм ID больше 1 000 000 000\n/addCustomCaptcha — добавить вопрос&ответ для пользовательского типа капчи\n/viewCustomCaptcha — просмотреть вопросы&ответы пользовательского типа капчи\n/removeAllCustomCaptcha — удалить все вопросы пользовательского типа капчи\n/silent — включает/выключает нотификации от сообщений бота\n\n Код Sesuritu в открытом доступе [вот тут](https://github.com/Rulexec/shieldy).', it: "Sesuritu — è la soluzione migliore per combattere gli spammer su Telegram. Chiede ai nuovi utenti di compiere un'azione entro un lasso di tempo prestabilito, altrimenti verranno kickati. E' semplice. Assicurati che @Sesuritu\\_bot sia un amministratore del gruppo ed è tutto pronto! Puoi anche impostare le azioni con i seguenti comandi:\n\n/help — Mostra questo messaggio\n/language — Modifica la lingua\n/captcha — Modifica il tipo di captcha\n/timeLimit — Modifica il tempo a disposizione per i nuovi membri\n/lock — Rendi accessibili i comandi solo agli amministratori\n/restrict — limita i nuovi membri ad inviare solo messaggi per le prime 24 ore\n/deleteEntryMessages — cancella i messaggi d'ingresso dei nuovi membri\n/greeting — dai il benvenuto ad un utente se passa il captcha\n/trust — rispondi con questo messaggio ad un utente che non vuoi che controlli\n/ban — same as trust, but the opposite\n/strict — usa questo comando quando non vuoi che i nuovi utenti inviino messaggi prima che passino il captcha\n/customCaptchaMessage — imposta un messaggio del captcha personalizzato\n/deleteGreetingTime — imposta in quanto tempo in secondi il messaggio di benvenuto debba essere eliminato, ad esempio `/deleteGreetingTime 100`, resettalo impostandolo a 0\n/banUsers — scegli se bannare o kickare\n/deleteEntryOnKick — scegli se eliminare i messaggi d'ingresso per coloro che hanno fallito il captcha\n/cas — scegli se usare Combot Anti-Spam\n/underAttack — toggle the mode to automatically kick all newcomers\n/noAttack — disables Sesuritu\n/noChannelLinks — automatically delete messages that link to other telegram channels\n/viewConfig — view the current Sesuritu configuration\n/buttonText — change the captcha button text in the form of `/buttonText I'm not a bot!`\n/allowInvitingBots — whether users can invite other bots or not\n/greetingButtons — setup buttons for greeting message\n/skipOldUsers — don't show captcha to Telegram accounts with ID less than 1000000000\n/skipVerifiedUsers — don't show captcha to users who have ever passed captcha in any chat\n/setConfig — setup Sesuritu configuration in one message\n/banForFastRepliesToPosts — turn on or off banning users who reply to new channel posts within 5 seconds after posts are created\n/restrictTime — setup how much time will Sesuritu /restrict a newcomer in hours like `/restrictTime 24`, reset by setting it to 0\n/banNewTelegramUsers — instantly ban (or kick) newcomers with Telegram ID over 1 000 000 000\n\nSesuritu è open source e il suo codice sorgente può essere trovato [qui](https://github.com/Rulexec/shieldy).", et: "Sesuritu — Telegrami parim lahendus ängistatavate rämpspostitajate vastu. Ta palub kõigil uustulnukitel teha mingi tegevus, teatud aja jooksul, kui nad ei ole seda teinud, siis see blokeerib neid. Jah, see on nii lihtne. Veenduge, et @Sesuritu\\_bot — administraator teie gruppis, ja kõik on valmis! Konfigureerige Sesuritu kasutades järgmised käsud:\n\n/help — näitab seda sõnumi\n/language — muutub keelt\n/captcha — muutub captcha tüüpi\n/timeLimit — muutub uustilnukitele antud aega\n/lock — muudab käsud kättesaadavaks ainult administraatoritele\n/restrict — restricts newcomers to send only text messages in the first 24 hours\n/deleteEntryMessages — delete messages about user entry\n/greeting — greets users who pass the test\n/trust — reply with this command to a message sent by user that you don't want to check\n/ban — same as trust, but the opposite\n/strict — use when you don't want to receive any newcomers' messages but captcha solutions until they pass captcha\n/customCaptchaMessage — setup custom captcha message\n/deleteGreetingTime — setup when to delete Sesuritu's greeting in seconds like `/deleteGreetingTime 100`, reset by setting it to 0\n/banUsers — whether to ban or to kick users\n/deleteEntryOnKick — whether to delete entry messages for users who failed captcha\n/cas — whether to use Combot Anti-Spam or not\n/underAttack — toggle the mode to automatically kick all newcomers\n/noAttack — disables Sesuritu\n/noChannelLinks — automatically delete messages that link to other telegram channels\n/viewConfig — view the current Sesuritu configuration\n/buttonText — change the captcha button text in the form of `/buttonText I'm not a bot!`\n/allowInvitingBots — whether users can invite other bots or not\n/greetingButtons — setup buttons for greeting message\n/skipOldUsers — don't show captcha to Telegram accounts with ID less than 1000000000\n/skipVerifiedUsers — don't show captcha to users who have ever passed captcha in any chat\n/setConfig — setup Sesuritu configuration in one message\n/banForFastRepliesToPosts — turn on or off banning users who reply to new channel posts within 5 seconds after posts are created\n/restrictTime — setup how much time will Sesuritu /restrict a newcomer in hours like `/restrictTime 24`, reset by setting it to 0\n/banNewTelegramUsers — instantly ban (or kick) newcomers with Telegram ID over 1 000 000 000\n\n. Sesuritu lähtekood on avatud [siin](https://github.com/Rulexec/shieldy).", uk: "Sesuritu — це найкраще рішення в Телеграмі для боротьби з надокучливими спамерами. Він просить усіх новачків зробити певну дію протягом вказаного часу і банить їх у разі незгоди. Так, це настільки просто! Переконайтеся, що @Sesuritu\\_bot — адміністратор у Вашій групі і усе вже готово! Ви можете продовжити налаштування бота, використавши наступні команди:\n\n/help — показує це повідомлення\n/language — змінює мову\n/captcha — змінює тип капчі\n/timeLimit — змінює кількість часу, що дається новачкам\n/lock — робить команди доступними тільки адміністраторам\n/restrict — забороняє новачкам надсилати будь-що, крім текстових повідомлень в перші 24 години\n/deleteEntryMessages — видаляти повідомлення про вхід користувачів у чат\n/greeting — вітає користувачів, які пройшли тест\n/trust — надішліть у відповідь на повідомлення користувача, якого не треба перевіряти\n/ban — те саме що й trust, але навпаки\n/strict — використовуйте, якщо не хочете отримувати жодних повідомлень від новачків, поки вони не вирішать капчу\n/customCaptchaMessage — встановити власне повідомлення капчі\n/deleteGreetingTime — встановлює час, коли потрібно видаляти вітання від бота, наприклад `/deleteGreetingTime 100`, встановіть у 0 щоб скинути\n/banUsers — банити користувачів чи одразу вилучати\n/deleteEntryOnKick — чи видаляти повідомлення про вхід користувачів, що не вирішили капчу\n/cas — використовувати Combot Anti-Spam чи ні\n/underAttack — спеціальний режим коли всі новачки одразу вилучаються з чату\n/noAttack — відключити Sesuritu\n/noChannelLinks — автоматично видаляти повідомлення з посиланнями на телеграм-канали\n/viewConfig — подивитися поточні настройки\n/buttonText — змінити текст кнопки капчи в форматі `/buttonText Я не бот!`\n/allowInvitingBots — чи можуть користувачі додавати ботів\n/greetingButtons — налаштування кнопок у повідомленні з привітанням\n/skipOldUsers — don't show captcha to Telegram accounts with ID less than 1000000000\n/skipVerifiedUsers — don't show captcha to users who have ever passed captcha in any chat\n/setConfig — setup Sesuritu configuration in one message\n/banForFastRepliesToPosts — turn on or off banning users who reply to new channel posts within 5 seconds after posts are created\n/restrictTime — setup how much time will Sesuritu /restrict a newcomer in hours like `/restrictTime 24`, reset by setting it to 0\n/banNewTelegramUsers — instantly ban (or kick) newcomers with Telegram ID over 1 000 000 000\n\n. Код Sesuritu у відкритому доступі [ось тут](https://github.com/Rulexec/shieldy)", @@ -266,10 +266,8 @@ export const localizations = { ru: 'Пожалуйста, ответьте на это сообщение вопросом, который вы хотите задавать новым пользователям.', }, custom_add_answer: { - en: - 'Please, reply to this message with the answer to this question. Use comma to separate variants of answer.', - ru: - 'Пожалуйста, ответьте на это сообщение ответом на этот вопрос. Используйте запятую чтобы разделять варианты ответа.', + en: 'Please, reply to this message with the answer to this question. Use comma to separate variants of answer.', + ru: 'Пожалуйста, ответьте на это сообщение ответом на этот вопрос. Используйте запятую чтобы разделять варианты ответа.', }, custom_no_variants: { en: 'There is no custom captcha questions, you can add them with /addCustomCaptcha', @@ -1330,4 +1328,12 @@ export const localizations = { en: 'Nice! Sesuritu will no ban (or kick) all newcomers with Telegram ID greater than 1 000 000 000.', bg: 'Чудесно! Sesuritu няма да забранява (или да "изрита") никой новодошъл с Telegram ID над 1 000 000 000.', }, + silentMessages_true: { + en: 'Sesuritu will send messages without sound', + ru: 'Sesuritu будет отправлять сообщения без звука', + }, + silentMessages_false: { + en: 'Sesuritu will send messages with sound', + ru: 'Sesuritu будет отправлять сообщения со звуком', + }, }; diff --git a/src/helpers/newcomers/greetUser.ts b/src/helpers/newcomers/greetUser.ts index 4d4ef978..3a63a9c8 100644 --- a/src/helpers/newcomers/greetUser.ts +++ b/src/helpers/newcomers/greetUser.ts @@ -62,18 +62,20 @@ export async function doGreetUser( message.chat = undefined; messageSent = await ctx.telegram.sendCopy(ctx.dbchat.id, message, { ...(greetingButtons - ? Extra.webPreview(false).markup((m) => - m.inlineKeyboard( - greetingButtons - .split('\n') - .map((s) => { - const components = s.split(' - '); - return m.urlButton(components[0], components[1]); - }) - .map((v) => [v]), - ), - ) - : Extra.webPreview(false)), + ? Extra.webPreview(false) + .markup((m) => + m.inlineKeyboard( + greetingButtons + .split('\n') + .map((s) => { + const components = s.split(' - '); + return m.urlButton(components[0], components[1]); + }) + .map((v) => [v]), + ), + ) + .notifications(!ctx.dbchat.silentMessages) + : Extra.webPreview(false).notifications(!ctx.dbchat.silentMessages)), entities: message.entities, }); } catch (err) { @@ -84,18 +86,20 @@ export async function doGreetUser( message.chat = undefined; messageSent = await ctx.telegram.sendCopy(ctx.dbchat.id, message, { ...(greetingButtons - ? Extra.webPreview(false).markup((m) => - m.inlineKeyboard( - greetingButtons - .split('\n') - .map((s) => { - const components = s.split(' - '); - return m.urlButton(components[0], components[1]); - }) - .map((v) => [v]), - ), - ) - : Extra.webPreview(false)), + ? Extra.webPreview(false) + .markup((m) => + m.inlineKeyboard( + greetingButtons + .split('\n') + .map((s) => { + const components = s.split(' - '); + return m.urlButton(components[0], components[1]); + }) + .map((v) => [v]), + ), + ) + .notifications(!ctx.dbchat.silentMessages) + : Extra.webPreview(false).notifications(!ctx.dbchat.silentMessages)), entities: message.entities, }); } diff --git a/src/helpers/newcomers/notifyCandidate.ts b/src/helpers/newcomers/notifyCandidate.ts index a6380daa..e60c9aa1 100644 --- a/src/helpers/newcomers/notifyCandidate.ts +++ b/src/helpers/newcomers/notifyCandidate.ts @@ -20,6 +20,7 @@ export async function notifyCandidate( captcha: Captcha, ): Promise { const chat = ctx.dbchat; + const {silentMessages} = chat; const captchaMessage = ctx.dbchat.captchaMessage ? cloneDeep(ctx.dbchat.captchaMessage) : undefined; @@ -40,6 +41,8 @@ export async function notifyCandidate( ]), ); + extra = extra.notifications(!silentMessages); + const getUserMention = async () => { if (chat.customCaptchaMessage && captchaMessage) { const text = captchaMessage.message.text; @@ -107,7 +110,7 @@ export async function notifyCandidate( }, ); if (image) { - extra = extra.HTML(true); + extra = extra.HTML(true).notifications(!silentMessages); const formattedText = formatHTML( messageToSend.text, messageToSend.entities, @@ -130,7 +133,7 @@ export async function notifyCandidate( }); } } else { - extra = extra.HTML(true); + extra = extra.HTML(true).notifications(!silentMessages); const message = cloneDeep(captchaMessage.message); const formattedText = formatHTML(message.text, message.entities); @@ -160,7 +163,7 @@ export async function notifyCandidate( } } } else { - extra = extra.HTML(true); + extra = extra.HTML(true).notifications(!silentMessages); let message: string | null = null; @@ -192,6 +195,7 @@ export async function notifyCandidate( { caption: text, parse_mode: 'HTML', + disable_notification: Boolean(silentMessages), }, ); } else { diff --git a/src/models/Chat.ts b/src/models/Chat.ts index 50f85e0b..5e7136ba 100644 --- a/src/models/Chat.ts +++ b/src/models/Chat.ts @@ -120,4 +120,5 @@ export class Chat { restrictTime: number; banNewTelegramUsers: boolean; lastReplySetting?: ReplySetting; + silentMessages?: boolean; } diff --git a/src/updateHandler.ts b/src/updateHandler.ts index 235b3c9c..b5e0f795 100644 --- a/src/updateHandler.ts +++ b/src/updateHandler.ts @@ -46,6 +46,8 @@ import {botDeleteMessageSafe} from './helpers/deleteMessageSafe'; import {strings} from './helpers/strings'; import {Language} from './models/Chat'; import {BotMiddlewareNextStrategy} from './bot/types'; +import {setupPing} from './commands/ping'; +import {setupSilent} from './commands/silent'; export function setupBot(appContext: AppContext): void { const {telegrafBot: bot, addBotMiddleware} = appContext; @@ -112,6 +114,8 @@ export function setupBot(appContext: AppContext): void { setupBanForFastRepliesToPosts(bot); setupRestrictTime(bot); setupBanNewTelegramUsers(bot); + setupPing(appContext); + setupSilent(appContext); // Newcomers logic setupNewcomers(appContext);