commands: localize generic bot commands

This commit is contained in:
2026-05-10 22:53:32 +03:00
parent 3d14e3c0d5
commit 94d695e008
33 changed files with 341 additions and 299 deletions
+2 -1
View File
@@ -1,8 +1,9 @@
import {CallbackCommand} from "../base/callback-command"; import {CallbackCommand} from "../base/callback-command";
import {Environment} from "../common/environment";
export class Cancel extends CallbackCommand { export class Cancel extends CallbackCommand {
text = "❌ Отменить"; text = Environment.cancelText;
data = ""; data = "";
constructor(text?: string, data?: string) { constructor(text?: string, data?: string) {
+6 -6
View File
@@ -8,8 +8,8 @@ import {botUser} from "../index";
export class AdminsAdd extends Command { export class AdminsAdd extends Command {
command = "addAdmin"; command = "addAdmin";
title = "/addAdmin"; title = Environment.commandTitles.adminsAdd;
description = "Add user to admins"; description = Environment.commandDescriptions.adminsAdd;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_CREATOR, Requirement.BOT_CREATOR,
@@ -24,19 +24,19 @@ export class AdminsAdd extends Command {
const text = fullName(msg.reply_to_message.from); const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) { if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя сделать админом").catch(logError); await oldSendMessage(msg, Environment.botCannotMakeItselfAdminText).catch(logError);
return; return;
} }
if (id === Environment.CREATOR_ID) { if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Создатель бота и так является админом").catch(logError); await oldSendMessage(msg, Environment.botCreatorAlreadyAdminText).catch(logError);
return; return;
} }
if (await Environment.addAdmin(id)) { if (await Environment.addAdmin(id)) {
await oldSendMessage(msg, text + " теперь админ!").catch(logError); await oldSendMessage(msg, Environment.getUserIsNowAdminText(text)).catch(logError);
} else { } else {
await oldSendMessage(msg, text + " и так уже админ 🤔").catch(logError); await oldSendMessage(msg, Environment.getUserAlreadyAdminText(text)).catch(logError);
} }
} }
} }
+4 -4
View File
@@ -3,7 +3,7 @@ import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements"; import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
import {fullName, logError, replyToMessage, sendErrorPlaceholder} from "../util/utils"; import {escapePlainMarkdownV2, fullName, logError, replyToMessage, sendErrorPlaceholder} from "../util/utils";
import {StoredUser} from "../model/stored-user"; import {StoredUser} from "../model/stored-user";
import {UserStore} from "../common/user-store"; import {UserStore} from "../common/user-store";
@@ -29,14 +29,14 @@ export class AdminsList extends Command {
} }
} }
let text = "*Администраторы*:\n\n"; let text = Environment.administratorsHeaderText;
users.forEach(user => { users.forEach(user => {
text += "\\* "; text += "\\* ";
if (user) { if (user) {
text += `[${fullName(user)}](tg://user?id=${user.id})`; text += `[${escapePlainMarkdownV2(fullName(user))}](tg://user?id=${user.id})`;
} else { } else {
text += "Нет информации о пользователе"; text += Environment.noUserInfoText;
} }
text += "\n"; text += "\n";
+6 -6
View File
@@ -8,8 +8,8 @@ import {botUser} from "../index";
export class AdminsRemove extends Command { export class AdminsRemove extends Command {
command = "removeAdmin"; command = "removeAdmin";
title = "/removeAdmin"; title = Environment.commandTitles.adminsRemove;
description = "Remove user from admins"; description = Environment.commandDescriptions.adminsRemove;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_CREATOR, Requirement.BOT_CREATOR,
@@ -24,19 +24,19 @@ export class AdminsRemove extends Command {
const text = fullName(msg.reply_to_message.from); const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) { if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя убрать из админов").catch(logError); await oldSendMessage(msg, Environment.botCannotRemoveItselfFromAdminsText).catch(logError);
return; return;
} }
if (id === Environment.CREATOR_ID) { if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Создатель бота не может перестать быть админом").catch(logError); await oldSendMessage(msg, Environment.botCreatorCannotStopBeingAdminText).catch(logError);
return; return;
} }
if (await Environment.removeAdmin(id)) { if (await Environment.removeAdmin(id)) {
await oldSendMessage(msg, text + " больше не админ!").catch(logError); await oldSendMessage(msg, Environment.getUserNoLongerAdminText(text)).catch(logError);
} else { } else {
await oldSendMessage(msg, text + " и так не был админом 🤔").catch(logError); await oldSendMessage(msg, Environment.getUserWasNotAdminText(text)).catch(logError);
} }
} }
} }
+5 -4
View File
@@ -3,14 +3,15 @@ import {Message} from "typescript-telegram-bot-api";
import {errorPlaceholder, logError, oldSendMessage} from "../util/utils"; import {errorPlaceholder, logError, oldSendMessage} from "../util/utils";
import {Requirements} from "../base/requirements"; import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {Environment} from "../common/environment";
export class Ae extends Command { export class Ae extends Command {
argsMode = "required" as const; argsMode = "required" as const;
command = ["ae"]; command = ["ae"];
title = "/ae"; title = Environment.commandTitles.ae;
description = "evaluation"; description = Environment.commandDescriptions.ae;
requirements = Requirements.Build(Requirement.BOT_CREATOR); requirements = Requirements.Build(Requirement.BOT_CREATOR);
@@ -24,7 +25,7 @@ export class Ae extends Command {
const text = e.message.toString(); const text = e.message.toString();
if (text.includes("is not defined")) { if (text.includes("is not defined")) {
await oldSendMessage(msg, "variable is not defined").catch(logError); await oldSendMessage(msg, Environment.variableNotDefinedText).catch(logError);
return; return;
} }
@@ -46,7 +47,7 @@ export class Ae extends Command {
const text = e.message.toString(); const text = e.message.toString();
if (text.includes("is not defined")) { if (text.includes("is not defined")) {
return "Variable not defined"; return Environment.evaluationVariableNotDefinedText;
} }
logError(`${text} logError(`${text}
+12 -8
View File
@@ -5,10 +5,11 @@ import {Message} from "typescript-telegram-bot-api";
import {bot, botUser} from "../index"; import {bot, botUser} from "../index";
import {fullName, logError, oldSendMessage, oldReplyToMessage} from "../util/utils"; import {fullName, logError, oldSendMessage, oldReplyToMessage} from "../util/utils";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
export class Ban extends Command { export class Ban extends Command {
title = "/ban [reply]"; title = Environment.commandTitles.ban;
description = "ban user from chat"; description = Environment.commandDescriptions.ban;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
@@ -25,26 +26,29 @@ export class Ban extends Command {
const userId = user.id; const userId = user.id;
if (userId === botUser.id) { if (userId === botUser.id) {
await oldReplyToMessage(msg, "Используй /leave").catch(logError); await oldReplyToMessage(msg, Environment.useLeaveCommandText).catch(logError);
return; return;
} }
if (userId === Environment.CREATOR_ID) { if (userId === Environment.CREATOR_ID) {
await oldReplyToMessage(msg, "Бот не будет банить своего создателя.").catch(logError); await oldReplyToMessage(msg, Environment.botWillNotBanCreatorText).catch(logError);
return; return;
} }
if (msg.from.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) { if (msg.from.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
await oldReplyToMessage(msg, "Бот не будет банить своих администраторов.").catch(logError); await oldReplyToMessage(msg, Environment.botWillNotBanAdminsText).catch(logError);
return; return;
} }
bot.banChatMember({chat_id: msg.chat.id, user_id: userId}) enqueueTelegramApiCall(
() => bot.banChatMember({chat_id: msg.chat.id, user_id: userId}),
{method: "banChatMember", chatId: msg.chat.id, chatType: msg.chat.type}
)
.then(async () => { .then(async () => {
await oldSendMessage(msg, `${fullName(user)} забанен 🚫`).catch(logError); await oldSendMessage(msg, Environment.getUserBannedText(fullName(user))).catch(logError);
}) })
.catch(async () => { .catch(async () => {
await oldSendMessage(msg, `Не смог забанить ${fullName(user)} ☹️`).catch(logError); await oldSendMessage(msg, Environment.getUserBanFailedText(fullName(user))).catch(logError);
}); });
} }
} }
+13 -3
View File
@@ -1,13 +1,15 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {logError, oldReplyToMessage, randomValue} from "../util/utils"; import {logError, oldReplyToMessage, randomValue} from "../util/utils";
import {prepareTelegramMarkdownV2} from "../util/markdown-v2-renderer";
import {Environment} from "../common/environment";
export class Choice extends Command { export class Choice extends Command {
command = "choice"; command = "choice";
argsMode = "required" as const; argsMode = "required" as const;
title = "/choice a, b, ..., c"; title = Environment.commandTitles.choice;
description = "Выбор случайного значения"; description = Environment.commandDescriptions.choice;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match); console.log("match", match);
@@ -33,7 +35,15 @@ export class Choice extends Command {
} }
const random = randomValue(out); const random = randomValue(out);
if (!random) {
await oldReplyToMessage(msg, Environment.noChoicesText).catch(logError);
return;
}
await oldReplyToMessage(msg, `Выбрал *${random}*`, "Markdown").catch(logError); await oldReplyToMessage(
msg,
Environment.getChoiceText(prepareTelegramMarkdownV2(random, {mode: "final"})),
"MarkdownV2"
).catch(logError);
} }
} }
+6 -4
View File
@@ -1,13 +1,15 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {getRangedRandomInt, logError, oldReplyToMessage} from "../util/utils"; import {getRangedRandomInt, logError, oldReplyToMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Coin extends Command { export class Coin extends Command {
title = "/coin"; title = Environment.commandTitles.coin;
description = "Heads or tails"; description = Environment.commandDescriptions.coin;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
const random = getRangedRandomInt(0, 2); const random = getRangedRandomInt(0, 2);
const headsOrTails = random === 1 ? "Выпал *Орёл* 🪙" : "Выпала *Решка* 🪙"; const headsOrTails = Environment.getCoinResultText(random === 1 ? Environment.coinHeadsText : Environment.coinTailsText) + " 🪙";
await oldReplyToMessage(msg, headsOrTails, "Markdown").catch(logError); } await oldReplyToMessage(msg, headsOrTails, "Markdown").catch(logError);
}
} }
+3 -2
View File
@@ -3,10 +3,11 @@ import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements"; import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {logError, replyToMessage} from "../util/utils"; import {logError, replyToMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Debug extends Command { export class Debug extends Command {
title = "/debug"; title = Environment.commandTitles.debug;
description = "Returns msg (or reply) as json"; description = Environment.commandDescriptions.debug;
requirements = Requirements.Build(Requirement.BOT_ADMIN); requirements = Requirements.Build(Requirement.BOT_ADMIN);
+12 -7
View File
@@ -2,26 +2,31 @@ import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {logError, randomValue} from "../util/utils"; import {logError, randomValue} from "../util/utils";
import {bot} from "../index"; import {bot} from "../index";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {Environment} from "../common/environment";
type DiceEmoji = "🎲" | "🎯" | "🏀" | "⚽" | "🎳" | "🎰"; type DiceEmoji = "🎲" | "🎯" | "🏀" | "⚽" | "🎳" | "🎰";
const emojis = ["🎲", "🎯", "🏀", "⚽", "🎳", "🎰"]; const emojis: readonly DiceEmoji[] = ["🎲", "🎯", "🏀", "⚽", "🎳", "🎰"];
export class Dice extends Command { export class Dice extends Command {
title = "/dice"; title = Environment.commandTitles.dice;
description = "Sends random or specific dice"; description = Environment.commandDescriptions.dice;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
const split = msg.text?.split("/dice "); const split = msg.text?.split("/dice ");
const secondPart = split?.[1]?.trim() || ""; const secondPart = split?.[1]?.trim() || "";
const emojiIndex = emojis.indexOf(secondPart); const requestedEmoji = secondPart as DiceEmoji;
const emojiToDice: DiceEmoji = (emojiIndex >= 0 ? emojis[emojiIndex] : randomValue(emojis)) as DiceEmoji; const emojiToDice = emojis.includes(requestedEmoji) ? requestedEmoji : randomValue(emojis) ?? "🎲";
await bot.sendDice({ await enqueueTelegramApiCall(
() => bot.sendDice({
chat_id: msg.chat.id, chat_id: msg.chat.id,
emoji: emojiToDice, emoji: emojiToDice,
reply_parameters: { reply_parameters: {
message_id: msg.message_id message_id: msg.message_id
} }
}).catch(logError); }),
{method: "sendDice", chatId: msg.chat.id, chatType: msg.chat.type}
).catch(logError);
} }
} }
+17 -9
View File
@@ -2,13 +2,15 @@ import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {downloadTelegramFile, extractImageFileId, logError, oldReplyToMessage, waveDistortSharp} from "../util/utils"; import {downloadTelegramFile, extractImageFileId, logError, oldReplyToMessage, waveDistortSharp} from "../util/utils";
import {bot} from "../index"; import {bot} from "../index";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {Environment} from "../common/environment";
export class Distort extends Command { export class Distort extends Command {
command = "distort"; command = "distort";
argsMode = "optional" as const; argsMode = "optional" as const;
title = "/distort [amp] [wavelength]"; title = Environment.commandTitles.distort;
description = "Distortion of picture"; description = Environment.commandDescriptions.distort;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const chatId = msg.chat.id; const chatId = msg.chat.id;
@@ -17,7 +19,7 @@ export class Distort extends Command {
if (!reply) { if (!reply) {
await oldReplyToMessage( await oldReplyToMessage(
msg, msg,
"Ответь командой /distort на сообщение с картинкой (фото, документ или стикер).\n" + "Пример: /distort 16 80" Environment.distortReplyInstructionText
); );
return; return;
} }
@@ -26,7 +28,7 @@ export class Distort extends Command {
if (!fileId) { if (!fileId) {
await oldReplyToMessage( await oldReplyToMessage(
msg, msg,
"В реплае не вижу картинку. Пришли фото или файл-изображение." Environment.distortMissingImageText
); );
return; return;
} }
@@ -37,7 +39,10 @@ export class Distort extends Command {
const wavelength = b ? Number(b) : 72; const wavelength = b ? Number(b) : 72;
try { try {
await bot.sendChatAction({chat_id: chatId, action: "upload_photo"}); await enqueueTelegramApiCall(
() => bot.sendChatAction({chat_id: chatId, action: "upload_photo"}),
{method: "sendChatAction", chatId, chatType: msg.chat.type}
);
const file = await bot.getFile({file_id: fileId}); const file = await bot.getFile({file_id: fileId});
if (!file.file_path) { if (!file.file_path) {
@@ -49,14 +54,17 @@ export class Distort extends Command {
const outBuf = await waveDistortSharp(<Buffer>inputBuf, amp, wavelength); const outBuf = await waveDistortSharp(<Buffer>inputBuf, amp, wavelength);
await bot.sendPhoto({ await enqueueTelegramApiCall(
() => bot.sendPhoto({
chat_id: chatId, chat_id: chatId,
photo: outBuf, photo: outBuf,
caption: `Искажение готово ✅ (amp=${amp}, wavelength=${wavelength})`, caption: Environment.getDistortionReadyCaption(amp, wavelength),
}); }),
{method: "sendPhoto", chatId, chatType: msg.chat.type}
);
} catch (e: any) { } catch (e: any) {
await oldReplyToMessage( await oldReplyToMessage(
msg, `Не получилось исказить изображение: ${e?.message ?? String(e)}` msg, Environment.getDistortFailedText(e)
).catch(logError); ).catch(logError);
} }
} }
+9 -7
View File
@@ -6,6 +6,7 @@ import {Environment} from "../common/environment";
import fs from "node:fs"; import fs from "node:fs";
import {logError, replyToMessage, sendErrorPlaceholder} from "../util/utils"; import {logError, replyToMessage, sendErrorPlaceholder} from "../util/utils";
import {bot} from "../index"; import {bot} from "../index";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
export class ExportDb extends Command { export class ExportDb extends Command {
@@ -23,16 +24,17 @@ export class ExportDb extends Command {
} }
try { try {
const buffer = fs.readFileSync(fullPath); await enqueueTelegramApiCall(
() => bot.sendDocument({
await bot.sendDocument({
chat_id: Environment.CREATOR_ID, chat_id: Environment.CREATOR_ID,
document: new FileOptions(buffer, {filename: "database.db", contentType: "application/sql"}), document: new FileOptions(fs.createReadStream(fullPath), {filename: "database.db", contentType: "application/sql"}),
caption: "Бэкап базы данных", caption: Environment.databaseBackupCaption,
}); }),
{method: "sendDocument", chatId: Environment.CREATOR_ID, chatType: "private"}
);
if (msg.chat.id !== Environment.CREATOR_ID) { if (msg.chat.id !== Environment.CREATOR_ID) {
await replyToMessage({message: msg, text: "Успешно отправлено в ЛС создателю!"}); await replyToMessage({message: msg, text: Environment.databaseBackupSentText});
} }
} catch (e) { } catch (e) {
logError(e); logError(e);
+6 -5
View File
@@ -3,16 +3,17 @@ import {chatCommandToString, delay, logError, sendMessage} from "../util/utils";
import {Command} from "../base/command"; import {Command} from "../base/command";
import {commands} from "../index"; import {commands} from "../index";
import {TelegramError} from "typescript-telegram-bot-api/dist/errors"; import {TelegramError} from "typescript-telegram-bot-api/dist/errors";
import {Environment} from "../common/environment";
export class Help extends Command { export class Help extends Command {
command = ["h", "help"]; command = ["h", "help"];
title = "/help"; title = Environment.commandTitles.help;
description = "Show list of commands"; description = Environment.commandDescriptions.help;
async execute(msg: Message) { async execute(msg: Message) {
if (!msg.from) return; if (!msg.from) return;
let text = "Commands:\n\n"; let text = Environment.commandsHeaderText;
commands.forEach(c => { commands.forEach(c => {
text += `${chatCommandToString(c)}\n`; text += `${chatCommandToString(c)}\n`;
@@ -21,7 +22,7 @@ export class Help extends Command {
await sendMessage({chat_id: msg.from.id, text: text}) await sendMessage({chat_id: msg.from.id, text: text})
.then(async () => { .then(async () => {
if (msg.chat.type !== "private") { if (msg.chat.type !== "private") {
await sendMessage({message: msg, text: "Отправил команды в ЛС 😎"}).catch(logError); await sendMessage({message: msg, text: Environment.sentCommandsInDmText}).catch(logError);
} }
}) })
.catch(async (e) => { .catch(async (e) => {
@@ -29,7 +30,7 @@ export class Help extends Command {
if (e.response?.error_code === 403) { if (e.response?.error_code === 403) {
await sendMessage({ await sendMessage({
message: msg, message: msg,
text: "Не смог отправить команды в ЛС ☹️\nТогда отправлю сюда" text: Environment.couldNotSendCommandsInDmText
}).catch(logError); }).catch(logError);
await delay(1000); await delay(1000);
+8 -8
View File
@@ -1,17 +1,17 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {logError, oldReplyToMessage} from "../util/utils"; import {logError, oldReplyToMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Id extends Command { export class Id extends Command {
title = "/id"; title = Environment.commandTitles.id;
description = "ID of chat, user and reply (if replied to any message)"; description = Environment.commandDescriptions.id;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
let text = `chat id: \n\`\`\`${msg.chat.id}\`\`\` \nfrom id: \n\`\`\`${msg.from?.id}\`\`\``; await oldReplyToMessage(
if (msg.reply_to_message) { msg,
text += ` \nreply id: \n\`\`\`${msg.reply_to_message.from?.id}\`\`\``; Environment.getIdText(msg.chat.id, msg.from?.id, msg.reply_to_message?.from?.id),
} "MarkdownV2",
).catch(logError);
await oldReplyToMessage(msg, text, "MarkdownV2").catch(logError);
} }
} }
+6 -6
View File
@@ -7,8 +7,8 @@ import {botUser} from "../index";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
export class Ignore extends Command { export class Ignore extends Command {
title = "/ignore"; title = Environment.commandTitles.ignore;
description = "Bot will ignore user"; description = Environment.commandDescriptions.ignore;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
@@ -25,19 +25,19 @@ export class Ignore extends Command {
const text = fullName(msg.reply_to_message.from); const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) { if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя игнорировать").catch(logError); await oldSendMessage(msg, Environment.botWillNotIgnoreItselfText).catch(logError);
return; return;
} }
if (id === Environment.CREATOR_ID) { if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Бот не будет игнорировать своего создателя").catch(logError); await oldSendMessage(msg, Environment.botWillNotIgnoreCreatorText).catch(logError);
return; return;
} }
if (await Environment.addMute(id)) { if (await Environment.addMute(id)) {
await oldSendMessage(msg, text + " в муте! 🔇").catch(logError); await oldSendMessage(msg, Environment.getUserIgnoredText(text)).catch(logError);
} else { } else {
await oldSendMessage(msg, text + " уже в муте 🤔").catch(logError); await oldSendMessage(msg, Environment.getUserAlreadyIgnoredText(text)).catch(logError);
} }
} }
} }
+8 -3
View File
@@ -3,10 +3,12 @@ import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements"; import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {bot} from "../index"; import {bot} from "../index";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {Environment} from "../common/environment";
export class Leave extends Command { export class Leave extends Command {
title = "/leave"; title = Environment.commandTitles.leave;
description = "Bot will leave current chat"; description = Environment.commandDescriptions.leave;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
@@ -14,6 +16,9 @@ export class Leave extends Command {
); );
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
await bot.leaveChat({chat_id: msg.chat.id}); await enqueueTelegramApiCall(
() => bot.leaveChat({chat_id: msg.chat.id}),
{method: "leaveChat", chatId: msg.chat.id, chatType: msg.chat.type}
);
} }
} }
+8 -16
View File
@@ -1,44 +1,36 @@
import {logError, sendMessage} from "../util/utils"; import {logError, sendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Environment} from "../common/environment";
export class Ping extends Command { export class Ping extends Command {
title = "/ping"; title = Environment.commandTitles.ping;
description = "Ping between received and sent message"; description = Environment.commandDescriptions.ping;
async execute(msg: Message) { async execute(msg: Message) {
let d = new Date(); let d = new Date();
const u = (n: number): string => n > 9 ? n.toString() : `0${n}`; const u = (n: number): string => n > 9 ? n.toString() : `0${n}`;
const date = `${u(d.getDay())}.${u(d.getMonth() + 1)}.${d.getFullYear()}`; const date = `${u(d.getDate())}.${u(d.getMonth() + 1)}.${d.getFullYear()}`;
const time = `${u(d.getHours())}:${u(d.getMinutes())}:${u(d.getSeconds())}:${u(d.getMilliseconds())}`; const time = `${u(d.getHours())}:${u(d.getMinutes())}:${u(d.getSeconds())}:${u(d.getMilliseconds())}`;
const mDate = msg.date; const mDate = msg.date;
const nowDate = new Date().getTime() / 1000; const nowDate = new Date().getTime() / 1000;
const diff = nowDate - mDate; const diff = nowDate - mDate;
const tgPing = diff.toFixed(2); const tgPing = (diff * 1000).toFixed(0);
d = new Date(mDate * 1000); d = new Date(mDate * 1000);
const msgDate = `${u(d.getDay())}.${u(d.getMonth() + 1)}.${d.getFullYear()}`; const msgDate = `${u(d.getDate())}.${u(d.getMonth() + 1)}.${d.getFullYear()}`;
const msgTime = `${u(d.getHours())}:${u(d.getMinutes())}:${u(d.getSeconds())}:${u(d.getMilliseconds())}`; const msgTime = `${u(d.getHours())}:${u(d.getMinutes())}:${u(d.getSeconds())}:${u(d.getMilliseconds())}`;
const then = Date.now(); const then = Date.now();
await sendMessage({message: msg, text: "pong"}).catch(logError); await sendMessage({message: msg, text: Environment.pongText}).catch(logError);
const now = Date.now(); const now = Date.now();
const msgSendDiff = (now - then).toFixed(2); const msgSendDiff = (now - then).toFixed(2);
await sendMessage( await sendMessage(
{ {
message: msg, message: msg,
text: text: Environment.getPingReportText(tgPing, msgSendDiff, msgDate, msgTime, date, time),
"```ping\n" +
`TG: ${tgPing}ms\n` +
`API ${msgSendDiff}ms\n\n` +
`🗓️ Message date: ${msgDate}\n` +
`🕒 Message time: ${msgTime}\n\n` +
`🗓️ Local date : ${date}\n` +
`🕒 Local time: ${time}` +
"```",
parse_mode: "Markdown" parse_mode: "Markdown"
} }
).catch(logError); ).catch(logError);
+1 -1
View File
@@ -5,6 +5,6 @@ import {Environment} from "../common/environment";
export class PrefixResponse extends Command { export class PrefixResponse extends Command {
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
await replyToMessage({message: msg, text: randomValue(Environment.ANSWERS.prefix)}).catch(logError); await replyToMessage({message: msg, text: randomValue(Environment.ANSWERS.prefix) ?? Environment.prefixFallbackText}).catch(logError);
} }
} }
+24 -16
View File
@@ -1,15 +1,17 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {extractMessagePayload, logError, replyToMessage} from "../util/utils"; import {escapeHtml, extractMessagePayload, logError, replyToMessage} from "../util/utils";
import {bot, botUser} from "../index"; import {bot, botUser} from "../index";
import QRCode from "qrcode"; import QRCode from "qrcode";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {Environment} from "../common/environment";
export class Qr extends Command { export class Qr extends Command {
argsMode = "optional" as const; argsMode = "optional" as const;
title = "/qr"; title = Environment.commandTitles.qr;
description = "Generates QR-code from text you sent or replied to."; description = Environment.commandDescriptions.qr;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const chatId = msg.chat.id; const chatId = msg.chat.id;
@@ -19,27 +21,29 @@ export class Qr extends Command {
await replyToMessage( await replyToMessage(
{ {
message: msg, message: msg,
text: "Не найден текст для генерации QR-кода." text: Environment.qrCodeMissingTextText
} }
); );
return; return;
} }
// TODO: 16/02/2026, Danil Nikolaev: escape html symbols in payload const maxQrPayloadLength = 1500;
if (payload.length > maxQrPayloadLength) {
if (payload.length > 1500) { payload = payload.slice(0, maxQrPayloadLength);
payload = payload.slice(0, 1500);
await replyToMessage( await replyToMessage(
{ {
message: msg, message: msg,
text: `Слишком длинный текст для QR (${payload.length} символов). Текст будет обрезан до 1500 символов.` text: Environment.getQrCodeTextTooLongText(payload.length, maxQrPayloadLength)
} }
); );
} }
try { try {
await bot.sendChatAction({chat_id: chatId, action: "upload_photo"}); await enqueueTelegramApiCall(
() => bot.sendChatAction({chat_id: chatId, action: "upload_photo"}),
{method: "sendChatAction", chatId, chatType: msg.chat.type}
);
const pngBuffer = await QRCode.toBuffer(payload, { const pngBuffer = await QRCode.toBuffer(payload, {
type: "png", type: "png",
@@ -49,22 +53,26 @@ export class Qr extends Command {
}); });
const maxCaptionLength = botUser.is_premium ? 4096 : 1024; const maxCaptionLength = botUser.is_premium ? 4096 : 1024;
const visiblePayload = payload.length > maxCaptionLength - 80
? payload.slice(0, maxCaptionLength - 83) + "..."
: payload;
await bot.sendPhoto({ await enqueueTelegramApiCall(
() => bot.sendPhoto({
chat_id: chatId, chat_id: chatId,
photo: pngBuffer, photo: pngBuffer,
caption: "QR-код готов ✅\nСодержимое:\n<blockquote expandable>" + caption: Environment.getQrCodeReadyText(escapeHtml(visiblePayload)),
`${payload.length > maxCaptionLength ? payload.slice(0, maxCaptionLength - 40) + "..." : payload}` +
"</blockquote>",
reply_parameters: { reply_parameters: {
message_id: msg.message_id, message_id: msg.message_id,
}, },
parse_mode: "HTML" parse_mode: "HTML"
}); }),
{method: "sendPhoto", chatId, chatType: msg.chat.type}
);
} catch (e: any) { } catch (e: any) {
await replyToMessage({ await replyToMessage({
message: msg, message: msg,
text: `Не получилось сгенерировать QR: ${e?.message ?? String(e)}` text: Environment.getQrCodeFailedText(e)
}).catch(logError); }).catch(logError);
} }
} }
+22 -13
View File
@@ -17,9 +17,12 @@ import {
import {Requirements} from "../base/requirements"; import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import twemoji from "twemoji"; import twemoji from "twemoji";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {AsyncSemaphore} from "../util/async-lock";
import {Environment} from "../common/environment";
import {getLruMapValue, setLruMapValue} from "../util/lru-map";
try { try {
GlobalFonts.registerFromPath("./assets/Inter_18pt-ExtraThin.ttf", "InterExtraThin");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Thin.ttf", "InterThin"); GlobalFonts.registerFromPath("./assets/Inter_18pt-Thin.ttf", "InterThin");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Light.ttf", "InterLight"); GlobalFonts.registerFromPath("./assets/Inter_18pt-Light.ttf", "InterLight");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Regular.ttf", "Inter"); GlobalFonts.registerFromPath("./assets/Inter_18pt-Regular.ttf", "Inter");
@@ -40,8 +43,8 @@ export class Quote extends Command {
command = ["cit", "citation", "q", "quote"]; command = ["cit", "citation", "q", "quote"];
argsMode = "none" as const; argsMode = "none" as const;
title = "/quote"; title = Environment.commandTitles.quote;
description = "Make quote from text (or quote)"; description = Environment.commandDescriptions.quote;
requirements = Requirements.Build(Requirement.REPLY); requirements = Requirements.Build(Requirement.REPLY);
@@ -53,31 +56,37 @@ export class Quote extends Command {
try { try {
const quoteRaw = (msg.quote?.text ?? reply.text ?? reply.caption ?? "").trim(); const quoteRaw = (msg.quote?.text ?? reply.text ?? reply.caption ?? "").trim();
if (quoteRaw.length === 0) { if (quoteRaw.length === 0) {
await replyToMessage({message: msg, text: "Не нашёл в сообщении текста 😢"}).catch(logError); await replyToMessage({message: msg, text: Environment.quoteMissingTextText}).catch(logError);
return; return;
} }
const quote = quoteRaw.length ? quoteRaw : "…"; const quote = quoteRaw.length ? quoteRaw : "…";
const entities = msg.quote ? msg.quote.entities : reply.entities ?? reply.caption_entities ?? []; const entities = msg.quote ? msg.quote.entities ?? [] : reply.entities ?? reply.caption_entities ?? [];
const png = await renderQuoteCard(msg, quote, reply, entities); const png = await quoteRenderSemaphore.runExclusive(() => renderQuoteCard(msg, quote, reply, entities));
await bot.sendPhoto({ await enqueueTelegramApiCall(
() => bot.sendPhoto({
chat_id: chatId, chat_id: chatId,
photo: png, photo: png,
reply_parameters: { reply_parameters: {
message_id: msg.message_id, message_id: msg.message_id,
}, },
}).catch(logError); }),
{method: "sendPhoto", chatId, chatType: msg.chat.type}
).catch(logError);
} catch (e) { } catch (e) {
logError(e); logError(e);
await replyToMessage({message: msg, text: "Не смог собрать цитату 😢"}).catch(logError); await replyToMessage({message: msg, text: Environment.quoteBuildFailedText}).catch(logError);
} }
} }
} }
const emojiCache = new Map<string, CanvasImage>(); const emojiCache = new Map<string, CanvasImage>();
const customEmojiCache = new Map<string, CanvasImage>(); const customEmojiCache = new Map<string, CanvasImage>();
const quoteRenderSemaphore = new AsyncSemaphore(2);
const EMOJI_CACHE_MAX_ENTRIES = 256;
const CUSTOM_EMOJI_CACHE_MAX_ENTRIES = 512;
function appleEmojiUrl(emoji: string): string { function appleEmojiUrl(emoji: string): string {
const codePoints = [...emoji] const codePoints = [...emoji]
@@ -104,13 +113,13 @@ async function loadEmoji(emoji: string | undefined): Promise<CanvasImage | null>
const downloadAndCache = async (url: string): Promise<Image> => { const downloadAndCache = async (url: string): Promise<Image> => {
const res = await axios.get<ArrayBuffer>(url, {responseType: "arraybuffer"}); const res = await axios.get<ArrayBuffer>(url, {responseType: "arraybuffer"});
const img = await loadImage(Buffer.from(res.data)); const img = await loadImage(Buffer.from(res.data));
emojiCache.set(url, img); setLruMapValue(emojiCache, url, img, EMOJI_CACHE_MAX_ENTRIES);
return img; return img;
}; };
const checkIfCached = async (emoji: string, emojiToUrl: (emoji: string) => string): Promise<CanvasImage> => { const checkIfCached = async (emoji: string, emojiToUrl: (emoji: string) => string): Promise<CanvasImage> => {
const url = emojiToUrl(emoji); const url = emojiToUrl(emoji);
const cached = emojiCache.get(url); const cached = getLruMapValue(emojiCache, url);
if (cached) return cached; if (cached) return cached;
return await downloadAndCache(emojiToUrl(emoji)); return await downloadAndCache(emojiToUrl(emoji));
}; };
@@ -128,7 +137,7 @@ async function loadEmoji(emoji: string | undefined): Promise<CanvasImage | null>
} }
async function loadCustomEmoji(customEmojiId: string): Promise<CanvasImage | null> { async function loadCustomEmoji(customEmojiId: string): Promise<CanvasImage | null> {
const cached = customEmojiCache.get(customEmojiId); const cached = getLruMapValue(customEmojiCache, customEmojiId);
if (cached) return cached; if (cached) return cached;
try { try {
@@ -159,7 +168,7 @@ async function loadCustomEmoji(customEmojiId: string): Promise<CanvasImage | nul
} }
const img = await loadImage(buffer); const img = await loadImage(buffer);
customEmojiCache.set(customEmojiId, img); setLruMapValue(customEmojiCache, customEmojiId, img, CUSTOM_EMOJI_CACHE_MAX_ENTRIES);
return img; return img;
} catch (e) { } catch (e) {
console.warn(`Failed to load custom emoji ${customEmojiId}:`, e); console.warn(`Failed to load custom emoji ${customEmojiId}:`, e);
+19 -11
View File
@@ -1,27 +1,35 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {getRandomInt, getRangedRandomInt, logError, oldSendMessage} from "../util/utils"; import {getRandomInt, logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {Environment} from "../common/environment";
export class RandomInt extends Command { export class RandomInt extends Command {
argsMode = "optional" as const; argsMode = "optional" as const;
title = "/randomInt"; title = Environment.commandTitles.randomInt;
description = "Ranged random integer from parameters"; description = Environment.commandDescriptions.randomInt;
async execute(msg: Message) { async execute(msg: Message) {
// TODO: 01/05/2026, Danil Nikolaev: improve
if (!msg.text) return; if (!msg.text) return;
const split = msg.text.split(" "); const args = msg.text.trim().split(/\s+/).slice(1);
const min = parseInt(split[1]); const values = args
const max = parseInt(split[2]); .map(value => Number(value))
.filter(value => Number.isSafeInteger(value));
const min = values.length === 1 ? 1 : values[0];
const max = values.length === 1 ? values[0] : values[1];
const good = max > min; const sufficient = Number.isSafeInteger(min) && Number.isSafeInteger(max);
const sufficient = !!(min && max) && good; if (sufficient && min === max) {
await oldSendMessage(msg, Environment.getRandomIntRangeText(min, max, min)).catch(logError);
return;
}
const random = !sufficient ? getRandomInt(Math.pow(2, 60)) : getRangedRandomInt(min, max); const from = sufficient ? Math.min(min, max) : 0;
const to = sufficient ? Math.max(min, max) : 1_000_000_000;
const random = getRandomInt(to - from + 1) + from;
const randomText = !sufficient ? random.toString() : `[${min}; ${max}]: ${random}`; const randomText = !sufficient ? random.toString() : Environment.getRandomIntRangeText(from, to, random);
await oldSendMessage(msg, randomText).catch(logError); await oldSendMessage(msg, randomText).catch(logError);
} }
+11 -10
View File
@@ -1,33 +1,34 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {getRandomInt, logError, replyToMessage} from "../util/utils"; import {getRandomInt, logError, replyToMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {Environment} from "../common/environment";
export class RandomString extends Command { export class RandomString extends Command {
argsMode = "optional" as const; argsMode = "optional" as const;
title = "/randomString"; title = Environment.commandTitles.randomString;
description = "literally random string (up to 4096 symbols)"; description = Environment.commandDescriptions.randomString;
async execute(msg: Message) { async execute(msg: Message) {
// TODO: 01/05/2026, Danil Nikolaev: improve
if (!msg.text) return; if (!msg.text) return;
const split = msg.text.split(" "); const [, lengthArg] = msg.text.trim().split(/\s+/);
const l = parseInt(split.length > 1 ? split[1] : "1"); const requestedLength = Number(lengthArg ?? 1);
const length = (l <= 0 || l > 4096) ? 1 : l; const length = Number.isSafeInteger(requestedLength)
? Math.min(4096, Math.max(1, requestedLength))
: 1;
const characters = Array.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя0123456789");
let result = ""; let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя0123456789";
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
result += characters.charAt(getRandomInt(characters.length)); result += characters[getRandomInt(characters.length)];
} }
await replyToMessage({ await replyToMessage({
message: msg, message: msg,
text: "<blockquote expandable>" + result + "</blockquote>", text: Environment.getExpandableBlockquoteText(result),
parse_mode: "HTML" parse_mode: "HTML"
}).catch(logError); }).catch(logError);
} }
+18 -16
View File
@@ -4,44 +4,46 @@ import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {bot} from "../index"; import {bot} from "../index";
import {delay, logError, randomValue} from "../util/utils"; import {delay, logError, randomValue} from "../util/utils";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
const texts = [ import {Environment} from "../common/environment";
"ну что-же, господа",
"приятно было с вами пообщаться",
"но мне пора на покой",
"всего хорошего"
];
const timings = [1500, 2500]; const timings = [1500, 2500];
const timer = [3, 2, 1]; const timer = [3, 2, 1];
export class Shutdown extends Command { export class Shutdown extends Command {
title = "/shutdown"; title = Environment.commandTitles.shutdown;
description = "Self-destruction sequence for bot (shutdown)"; description = Environment.commandDescriptions.shutdown;
argsMode = "optional" as const; argsMode = "optional" as const;
requirements = Requirements.Build(Requirement.BOT_CREATOR); requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
await bot.sendMessage({chat_id: msg.chat.id, text: "..."}).catch(logError); const send = async (text: string) => {
await enqueueTelegramApiCall(
() => bot.sendMessage({chat_id: msg.chat.id, text}),
{method: "sendMessage", chatId: msg.chat.id, chatType: msg.chat.type}
).catch(logError);
};
await send(Environment.shutdownFallbackText);
const now = match?.[3]?.toLowerCase() === "now"; const now = match?.[3]?.toLowerCase() === "now";
if (msg.chat.type !== "private" && !now) { if (msg.chat.type !== "private" && !now) {
for (const text of texts) { for (const text of Environment.shutdownSequenceTexts) {
await delay(randomValue(timings)); await delay(randomValue(timings) ?? 1500);
await bot.sendMessage({chat_id: msg.chat.id, text: text}).catch(logError); await send(text);
} }
await delay(randomValue(timings)); await delay(randomValue(timings) ?? 1500);
for (const t of timer) { for (const t of timer) {
await bot.sendMessage({chat_id: msg.chat.id, text: `${t}`}).catch(logError); await send(`${t}`);
await delay(1000); await delay(1000);
} }
} }
await bot.sendMessage({chat_id: msg.chat.id, text: "*R.I.P*"}).catch(logError); await send(Environment.shutdownDoneText);
delay(2000).then(() => process.exit(0)); delay(2000).then(() => process.exit(0));
} }
+3 -2
View File
@@ -2,10 +2,11 @@ import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {commands} from "../index"; import {commands} from "../index";
import {Help} from "./help"; import {Help} from "./help";
import {Environment} from "../common/environment";
export class Start extends Command { export class Start extends Command {
title = "/start"; title = Environment.commandTitles.start;
description = "Start the bot"; description = Environment.commandDescriptions.start;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
await commands.find(e => e instanceof Help)?.execute(msg); await commands.find(e => e instanceof Help)?.execute(msg);
+8 -6
View File
@@ -1,18 +1,20 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {logError, replyToMessage} from "../util/utils"; import {logError, replyToMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {Environment} from "../common/environment";
export class SystemInfo extends Command { export class SystemInfo extends Command {
title = "/systemInfo"; title = Environment.commandTitles.systemInfo;
description = "System information"; description = Environment.commandDescriptions.systemInfo;
private static systemInfoText: string; private static systemInfoParams: Parameters<typeof Environment.getSystemSpecsText>[0] | null = null;
static setSystemInfo(info: string) { static setSystemInfo(params: Parameters<typeof Environment.getSystemSpecsText>[0]) {
SystemInfo.systemInfoText = info; SystemInfo.systemInfoParams = params;
} }
async execute(msg: Message) { async execute(msg: Message) {
await replyToMessage({message: msg, text: SystemInfo.systemInfoText}).catch(logError); if (!SystemInfo.systemInfoParams) return;
await replyToMessage({message: msg, text: Environment.getSystemSpecsText(SystemInfo.systemInfoParams)}).catch(logError);
} }
} }
+3 -3
View File
@@ -5,10 +5,10 @@ import {Environment} from "../common/environment";
export class Test extends Command { export class Test extends Command {
regexp = /^(test|тест|еуые|ntcn|инноке(нтий|ш|нтич))$/i; regexp = /^(test|тест|еуые|ntcn|инноке(нтий|ш|нтич))$/i;
title = "тест"; title = Environment.commandTitles.test;
description = "System functionality check"; description = Environment.commandDescriptions.test;
async execute(msg: Message) { async execute(msg: Message) {
await oldReplyToMessage(msg, randomValue(Environment.ANSWERS.test) || "а").catch(logError); await oldReplyToMessage(msg, randomValue(Environment.ANSWERS.test) || Environment.defaultTestAnswerText).catch(logError);
} }
} }
+9 -4
View File
@@ -4,13 +4,15 @@ import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement"; import {Requirement} from "../base/requirement";
import {logError, oldReplyToMessage} from "../util/utils"; import {logError, oldReplyToMessage} from "../util/utils";
import {bot} from "../index"; import {bot} from "../index";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
import {Environment} from "../common/environment";
export class Title extends Command { export class Title extends Command {
command = "title"; command = "title";
argsMode = "required" as const; argsMode = "required" as const;
title = "/title"; title = Environment.commandTitles.title;
description = "Change group title"; description = Environment.commandDescriptions.title;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
@@ -22,10 +24,13 @@ export class Title extends Command {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const title = (match?.[3] ?? "").trim(); const title = (match?.[3] ?? "").trim();
if (title.length === 0) { if (title.length === 0) {
await oldReplyToMessage(msg, "Не нашёл название...").catch(logError); await oldReplyToMessage(msg, Environment.titleMissingText).catch(logError);
return; return;
} }
await bot.setChatTitle({chat_id: msg.chat.id, title: title}).catch(logError); await enqueueTelegramApiCall(
() => bot.setChatTitle({chat_id: msg.chat.id, title: title}),
{method: "setChatTitle", chatId: msg.chat.id, chatType: msg.chat.type}
).catch(logError);
} }
} }
+4 -3
View File
@@ -1,6 +1,7 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {logError, oldReplyToMessage, randomValue} from "../util/utils"; import {logError, oldReplyToMessage, randomValue} from "../util/utils";
import {Environment} from "../common/environment";
const EN = const EN =
"`qwertyuiop[]asdfghjkl;'zxcvbnm,./" + "`qwertyuiop[]asdfghjkl;'zxcvbnm,./" +
@@ -60,7 +61,7 @@ export function fixLayoutAuto(
): string { ): string {
let guess = detectScript(text); let guess = detectScript(text);
if (guess === "mixed") { if (guess === "mixed") {
guess = randomValue([true, false]) ? "ru" : "en"; guess = (randomValue([true, false]) ?? false) ? "ru" : "en";
} }
if (guess === "en") { if (guess === "en") {
@@ -77,8 +78,8 @@ export function fixLayoutAuto(
export class Transliteration extends Command { export class Transliteration extends Command {
command = ["transliteration", "tr"]; command = ["transliteration", "tr"];
title = "/tr [text or reply]"; title = Environment.commandTitles.transliteration;
description = "Transliteration EN <--> RU"; description = Environment.commandDescriptions.transliteration;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
if (!msg.text && !msg.caption) return; if (!msg.text && !msg.caption) return;
+12 -8
View File
@@ -5,10 +5,11 @@ import {Message} from "typescript-telegram-bot-api";
import {bot, botUser} from "../index"; import {bot, botUser} from "../index";
import {fullName, logError, oldReplyToMessage, oldSendMessage} from "../util/utils"; import {fullName, logError, oldReplyToMessage, oldSendMessage} from "../util/utils";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
export class Unban extends Command { export class Unban extends Command {
title = "/unban [reply]"; title = Environment.commandTitles.unban;
description = "unban user from chat"; description = Environment.commandDescriptions.unban;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
@@ -25,26 +26,29 @@ export class Unban extends Command {
const userId = user.id; const userId = user.id;
if (userId === botUser.id) { if (userId === botUser.id) {
await oldReplyToMessage(msg, "Бот и так не в бане сам у себя.").catch(logError); await oldReplyToMessage(msg, Environment.botIsNotBannedByItselfText).catch(logError);
return; return;
} }
if (userId === Environment.CREATOR_ID) { if (userId === Environment.CREATOR_ID) {
await oldReplyToMessage(msg, "Создатель бота и так не в бане и никогда не будет.").catch(logError); await oldReplyToMessage(msg, Environment.botCreatorNeverBannedText).catch(logError);
return; return;
} }
if (msg.from?.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) { if (msg.from?.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
await oldReplyToMessage(msg, "Админимтраторы бота и так не в бане.").catch(logError); await oldReplyToMessage(msg, Environment.botAdminsNotBannedText).catch(logError);
return; return;
} }
bot.unbanChatMember({chat_id: msg.chat.id, user_id: userId}) enqueueTelegramApiCall(
() => bot.unbanChatMember({chat_id: msg.chat.id, user_id: userId}),
{method: "unbanChatMember", chatId: msg.chat.id, chatType: msg.chat.type}
)
.then(async () => { .then(async () => {
await oldSendMessage(msg, `${fullName(user)} разбанен ⛓️‍💥`).catch(logError); await oldSendMessage(msg, Environment.getUserUnbannedText(fullName(user))).catch(logError);
}) })
.catch(async () => { .catch(async () => {
await oldSendMessage(msg, `Не смог разбанить ${fullName(user)} ☹️`).catch(logError); await oldSendMessage(msg, Environment.getUserUnbanFailedText(fullName(user))).catch(logError);
}); });
} }
} }
+6 -6
View File
@@ -7,8 +7,8 @@ import {botUser} from "../index";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
export class Unignore extends Command { export class Unignore extends Command {
title = "/unignore"; title = Environment.commandTitles.unignore;
description = "Bot will start responding to the user"; description = Environment.commandDescriptions.unignore;
requirements = Requirements.Build( requirements = Requirements.Build(
Requirement.BOT_ADMIN, Requirement.BOT_ADMIN,
Requirement.CHAT, Requirement.CHAT,
@@ -24,19 +24,19 @@ export class Unignore extends Command {
const text = fullName(msg.reply_to_message.from); const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) { if (id === botUser.id) {
await oldSendMessage(msg, "Бот и так всегда к себе прислушивается").catch(logError); await oldSendMessage(msg, Environment.botAlreadyAlwaysListensToItselfText).catch(logError);
return; return;
} }
if (id === Environment.CREATOR_ID) { if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Бот всегда слушает своего создателя").catch(logError); await oldSendMessage(msg, Environment.botAlwaysListensToCreatorText).catch(logError);
return; return;
} }
if (await Environment.removeMute(id)) { if (await Environment.removeMute(id)) {
await oldSendMessage(msg, text + " больше не в муте! 🔈").catch(logError); await oldSendMessage(msg, Environment.getUserUnignoredText(text)).catch(logError);
} else { } else {
await oldSendMessage(msg, text + " не был в муте 🤔").catch(logError); await oldSendMessage(msg, Environment.getUserWasNotIgnoredText(text)).catch(logError);
} }
} }
} }
+3 -2
View File
@@ -1,10 +1,11 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {getUptime, logError, oldSendMessage} from "../util/utils"; import {getUptime, logError, oldSendMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Uptime extends Command { export class Uptime extends Command {
title = "/uptime"; title = Environment.commandTitles.uptime;
description = "Bot's uptime"; description = Environment.commandDescriptions.uptime;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
await oldSendMessage(msg, getUptime()).catch(logError); await oldSendMessage(msg, getUptime()).catch(logError);
+3 -3
View File
@@ -7,8 +7,8 @@ export class WhatBetter extends Command {
command = ["what", "что"]; command = ["what", "что"];
argsMode = "required" as const; argsMode = "required" as const;
title = "/what better [a] or [b]"; title = Environment.commandTitles.whatBetter;
description = "either a or b randomly (50% chance)"; description = Environment.commandDescriptions.whatBetter;
private argsRe = /^(better|лучше)\s+([\s\S]+?)\s+(or|или)\s+([\s\S]+)$/i; private argsRe = /^(better|лучше)\s+([\s\S]+?)\s+(or|или)\s+([\s\S]+)$/i;
@@ -19,7 +19,7 @@ export class WhatBetter extends Command {
const a = m[2].trim(); const a = m[2].trim();
const b = m[4].trim(); const b = m[4].trim();
const text = `${randomValue(Environment.ANSWERS.better)} ${randomValue([a, b])}`; const text = `${randomValue(Environment.ANSWERS.better) ?? Environment.betterFallbackText} ${randomValue([a, b]) ?? a}`;
await oldSendMessage(msg, text).catch(logError); await oldSendMessage(msg, text).catch(logError);
} }
+12 -44
View File
@@ -1,88 +1,56 @@
import {Command} from "../base/command"; import {Command} from "../base/command";
import {getRandomInt, getRangedRandomInt, logError, oldReplyToMessage} from "../util/utils"; import {getRandomInt, getRangedRandomInt, logError, oldReplyToMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api"; import {Message} from "typescript-telegram-bot-api";
import {Environment} from "../common/environment";
export class When extends Command { export class When extends Command {
command = ["when", "когда"]; command = ["when", "когда"];
argsMode = "required" as const; argsMode = "required" as const;
title = "/when [value]"; title = Environment.commandTitles.when;
description = "random date"; description = Environment.commandDescriptions.when;
async execute(msg: Message) { async execute(msg: Message) {
let text = "через "; let text = Environment.getWhenPrefixText();
const type = getRandomInt(8); const type = getRandomInt(8);
switch (type) { switch (type) {
case 0: case 0:
text = "сейчас"; text = Environment.whenNowText;
break; break;
case 1: case 1:
text = "никогда"; text = Environment.whenNeverText;
break; break;
case 2: //seconds case 2: //seconds
{ {
const seconds = getRangedRandomInt(1, 60); const seconds = getRangedRandomInt(1, 60);
text = Environment.getWhenDurationText(seconds, Environment.whenSecondUnitText);
text += `${seconds} `;
text += (
(seconds == 1 || seconds % 10 == 1) ? "секунду" :
((seconds > 1 && seconds < 5) || (seconds % 10 > 1 && seconds % 10 < 5)) ? "секунды" : "секунд"
);
break; break;
} }
case 3: { case 3: {
const minutes = getRangedRandomInt(1, 60); const minutes = getRangedRandomInt(1, 60);
text = Environment.getWhenDurationText(minutes, Environment.whenMinuteUnitText);
text += `${minutes} `;
text += (
(minutes == 1 || minutes % 10 == 1) ? "минуту" :
((minutes > 1 && minutes < 5) || (minutes % 10 > 1 && minutes % 10 < 5)) ? "минуты" : "минут"
);
break; break;
} }
case 4: { case 4: {
const hours = getRangedRandomInt(1, 24); const hours = getRangedRandomInt(1, 24);
text = Environment.getWhenDurationText(hours, Environment.whenHourUnitText);
text += `${hours} `;
text += (
(hours == 1 || hours % 10 == 1) ? "час" :
((hours > 1 && hours < 5) || (hours % 10 > 1 && hours % 10 < 5)) ? "часа" : "часов"
);
break; break;
} }
case 5: { case 5: {
const weeks = getRangedRandomInt(1, 4); const weeks = getRangedRandomInt(1, 4);
text = Environment.getWhenDurationText(weeks, Environment.whenWeekUnitText);
text += `${weeks} `;
text += (weeks == 1 ? "неделю" : "недель");
break; break;
} }
case 6: { case 6: {
const months = getRandomInt(12); const months = getRandomInt(12);
text = Environment.getWhenDurationText(months, Environment.whenMonthUnitText);
text += `${months} `;
text += (
(months == 1 || months % 10 == 1) ? "месяц" :
((months > 1 && months < 5) || (months % 10 > 1 && months % 10 < 5)) ? "месяца" : "месяцев"
);
break; break;
} }
case 7: { case 7: {
const years = getRangedRandomInt(1, 100); const years = getRangedRandomInt(1, 100);
text = Environment.getWhenDurationText(years, Environment.whenYearUnitText);
text += `${years} `;
text += (
(years == 1 || years % 10 == 1) ? "год" :
((years > 1 && years < 5) || (years % 10 > 1 && years % 10 < 5)) ? "года" : "лет"
);
break; break;
} }
} }