refactor!: rewrite bot core; add AI (Ollama, Gemini), DB, new commands

This commit is contained in:
2026-01-12 15:32:50 +03:00
parent 9d74ad9861
commit df9471a7e4
137 changed files with 11341 additions and 2025 deletions
+42
View File
@@ -0,0 +1,42 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {fullName, logError, oldSendMessage} from "../util/utils";
import {Environment} from "../common/environment";
import {botUser} from "../index";
export class AdminsAdd extends ChatCommand {
regexp = /^\/addadmin/i;
title = "/addAdmin";
description = "Add user to admins";
requirements = Requirements.Build(
Requirement.BOT_CREATOR,
Requirement.REPLY,
Requirement.CHAT
);
async execute(msg: Message): Promise<void> {
if (!msg.reply_to_message) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя сделать админом").catch(logError);
return;
}
if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Создатель бота и так является админом").catch(logError);
return;
}
if (await Environment.addAdmin(id)) {
await oldSendMessage(msg, text + " теперь админ!").catch(logError);
} else {
await oldSendMessage(msg, text + " и так уже админ 🤔").catch(logError);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {fullName, logError, oldSendMessage} from "../util/utils";
import {Environment} from "../common/environment";
import {botUser} from "../index";
export class AdminsRemove extends ChatCommand {
regexp = /^\/removeadmin/i;
title = "/removeAdmin";
description = "Remove user from admins";
requirements = Requirements.Build(
Requirement.BOT_ADMIN,
Requirement.REPLY,
Requirement.CHAT,
);
async execute(msg: Message): Promise<void> {
if (!msg.reply_to_message) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя убрать из админов").catch(logError);
return;
}
if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Создатель бота не может перестать быть админом").catch(logError);
return;
}
if (await Environment.removeAdmin(id)) {
await oldSendMessage(msg, text + " больше не админ!").catch(logError);
} else {
await oldSendMessage(msg, text + " и так не был админом 🤔").catch(logError);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {errorPlaceholder, logError, oldSendMessage} from "../util/utils";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
export class Ae implements ChatCommand {
regexp = /^\/ae\s([^]+)/i;
title = "/ae";
description = "evaluation";
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message, params: string[]) {
const match = params[1];
try {
let e = eval(match);
e = ((typeof e == "string") ? e : JSON.stringify(e));
await oldSendMessage(msg, e).catch(async () => await errorPlaceholder(msg));
} catch (e) {
const text = e.message.toString();
if (text.includes("is not defined")) {
await oldSendMessage(msg, "variable is not defined").catch(logError);
return;
}
console.error(`${text}
* Stacktrace: ${e.stack}`);
await oldSendMessage(msg, text).catch(logError);
}
}
}
+50
View File
@@ -0,0 +1,50 @@
import {ChatCommand} from "../base/chat-command";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {bot, botUser} from "../index";
import {fullName, logError, oldSendMessage, replyToMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Ban extends ChatCommand {
regexp = /^\/ban/i;
title = "/ban [reply]";
description = "ban user from chat";
requirements = Requirements.Build(
Requirement.CHAT,
Requirement.BOT_CHAT_ADMIN,
Requirement.REPLY,
Requirement.BOT_ADMIN
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
const user = msg.reply_to_message.from;
const userId = user.id;
if (userId === botUser.id) {
await replyToMessage(msg, "Используй /leave").catch(logError);
return;
}
if (userId === Environment.CREATOR_ID) {
await replyToMessage(msg, "Бот не будет банить своего создателя.").catch(logError);
return;
}
if (msg.from.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
await replyToMessage(msg, "Бот не будет банить своих администраторов.").catch(logError);
return;
}
bot.banChatMember({chat_id: msg.chat.id, user_id: userId})
.then(async () => {
await oldSendMessage(msg, `${fullName(user)} забанен 🚫`).catch(logError);
})
.catch(async () => {
await oldSendMessage(msg, `Не смог забанить ${fullName(user)} ☹️`).catch(logError);
});
}
}
+18
View File
@@ -0,0 +1,18 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {MessageStore} from "../common/message-store";
import {logError, sendMessage} from "../util/utils";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
export class CacheClear extends ChatCommand {
regexp = /^\/clearcache$/i;
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message): Promise<void> {
const size = MessageStore.all().size;
MessageStore.clear();
await sendMessage({chatId: msg.chat.id, text: `Было удалено сообщений: ${size}`}).catch(logError);
}
}
+17
View File
@@ -0,0 +1,17 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, sendMessage} from "../util/utils";
import {MessageStore} from "../common/message-store";
export class CacheSize extends ChatCommand {
regexp = /^\/cachesize$/i;
async execute(msg: Message): Promise<void> {
const cacheSize = MessageStore.all();
await sendMessage({
chatId: msg.chat.id,
text: `Количество сохранённых сообщений: ${cacheSize.size}`
}).catch(logError);
}
}
+37
View File
@@ -0,0 +1,37 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, randomValue, replyToMessage} from "../util/utils";
export class Choice extends ChatCommand {
regexp = /^\/choice\b\s*(.*)$/i;
title = "/choice a, b, ..., c";
description = "Выбор случайного значения";
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
const payload = match[1];
const re =
/\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^,]+?))\s*(?:,|$)/g;
const out: string[] = [];
for (const mm of payload.matchAll(re)) {
const raw = (mm[1] ?? mm[2] ?? mm[3] ?? "").trim();
const val = raw
.replace(/\\n/g, "\n")
.replace(/\\r/g, "\r")
.replace(/\\t/g, "\t")
.replace(/\\"/g, "\"")
.replace(/\\'/g, "'")
.replace(/\\\\/g, "\\");
if (val.length) out.push(val);
}
const random = randomValue(out);
await replyToMessage(msg, `Выбрал *${random}*`, "Markdown").catch(logError);
}
}
+14
View File
@@ -0,0 +1,14 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {getRangedRandomInt, logError, replyToMessage} from "../util/utils";
export class Coin extends ChatCommand {
regexp = /^\/coin$/i;
title = "/coin";
description = "Heads or tails";
async execute(msg: Message): Promise<void> {
const random = getRangedRandomInt(0, 2);
const headsOrTails = random === 1 ? "Выпал *Орёл* 🪙" : "Выпала *Решка* 🪙";
await replyToMessage(msg, headsOrTails, "Markdown").catch(logError); }
}
+28
View File
@@ -0,0 +1,28 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, randomValue} from "../util/utils";
import {bot} from "../index";
type DiceEmoji = "🎲" | "🎯" | "🏀" | "⚽" | "🎳" | "🎰";
const emojis = ["🎲", "🎯", "🏀", "⚽", "🎳", "🎰"];
export class Dice extends ChatCommand {
regexp = /^\/dice/i;
title = "/dice [emoji]";
description = "Sends random or specific dice";
async execute(msg: Message): Promise<void> {
const split = msg.text.split("/dice ");
const secondPart = split[1]?.trim();
const emojiIndex = emojis.indexOf(secondPart);
const emojiToDice: DiceEmoji = (emojiIndex >= 0 ? emojis[emojiIndex] : randomValue(emojis)) as DiceEmoji;
await bot.sendDice({
chat_id: msg.chat.id,
emoji: emojiToDice,
reply_parameters: {
message_id: msg.message_id
}
}).catch(logError);
}
}
+56
View File
@@ -0,0 +1,56 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {downloadTelegramFile, extractImageFileId, logError, replyToMessage, waveDistortSharp} from "../util/utils";
import {bot} from "../index";
export class Distort extends ChatCommand {
regexp = /^\/distort(?:@[\w_]+)?(?:\s+(\d+))?(?:\s+(\d+))?\s*$/i;
title = "/distort [amp] [wavelength]";
description = "Distortion of picture";
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const chatId = msg.chat.id;
const reply = msg.reply_to_message;
if (!reply) {
await replyToMessage(
msg,
"Ответь командой /distort на сообщение с картинкой (фото, документ или стикер).\n" + "Пример: /distort 16 80"
);
return;
}
const fileId = extractImageFileId(reply);
if (!fileId) {
await replyToMessage(
msg,
"В реплае не вижу картинку. Пришли фото или файл-изображение."
);
return;
}
const amp = match?.[1] ? parseInt(match[1], 10) : 14;
const wavelength = match?.[2] ? parseInt(match[2], 10) : 72;
try {
await bot.sendChatAction({chat_id: chatId, action: "upload_photo"});
const file = await bot.getFile({file_id: fileId});
if (!file.file_path) throw new Error("No file_path in Telegram getFile response");
const inputBuf = await downloadTelegramFile(file.file_path);
const outBuf = await waveDistortSharp(inputBuf, amp, wavelength);
await bot.sendPhoto({
chat_id: chatId,
photo: outBuf,
caption: `Искажение готово ✅ (amp=${amp}, wavelength=${wavelength})`,
});
} catch (e) {
await replyToMessage(
msg, `Не получилось исказить изображение: ${e?.message ?? String(e)}`
).catch(logError);
}
}
}
+139
View File
@@ -0,0 +1,139 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {
collectReplyChainText,
editMessageText,
escapeMarkdownV2Text,
logError,
replyToMessage,
startIntervalEditor
} from "../util/utils";
import {Environment} from "../common/environment";
import {bot, googleAi} from "../index";
import {MessageStore} from "../common/message-store";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {ApiError} from "@google/genai";
export class GeminiChat extends ChatCommand {
regexp = /^\/gemini\s([^]+)/i;
title = "/gemini";
description = "Chat with AI (Gemini)";
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
return this.executeGemini(msg, match?.[1]);
}
async executeGemini(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
const chatId = msg.chat.id;
const messageParts = await collectReplyChainText(msg, "/gemini");
console.log("MESSAGE PARTS", messageParts);
const chatMessages = messageParts.map(part => {
return {
role: part.bot ? "ASSISTANT" : "USER",
content: part.content
};
});
chatMessages.reverse();
chatMessages.unshift({role: "SYSTEM", content: Environment.SYSTEM_PROMPT});
let chatContent = "";
for (const part of chatMessages) {
chatContent += `${part.role.toUpperCase()}:\n${part.content}\n\n`;
}
chatContent = chatContent.trim();
let waitMessage: Message;
const startTime = new Date().getSeconds();
try {
waitMessage = await bot.sendMessage({
chat_id: chatId,
text: Environment.waitText,
reply_parameters: {
chat_id: chatId,
message_id: msg.message_id
}
});
const stream = await googleAi.models.generateContentStream({
model: "gemini-2.5-flash",
contents: chatContent,
});
let messageText = "";
let shouldBreak = false;
let diff = 0;
const editor = startIntervalEditor({
intervalMs: 4500,
getText: () => messageText,
editFn: async (text) => {
await editMessageText(chatId, waitMessage.message_id, escapeMarkdownV2Text(text), "Markdown");
},
});
try {
for await (const chunk of stream) {
const text = chunk.text;
const length = (messageText + text).length;
if (length > 4096) {
messageText = messageText.slice(0, 4093) + "...";
shouldBreak = true;
} else {
messageText += text;
}
if (shouldBreak) {
console.log("messageText", messageText);
console.log("length", length);
console.log("break", true);
diff = Math.abs(new Date().getSeconds() - startTime);
await editor.tick();
await editor.stop();
break;
}
console.log("messageText", messageText);
console.log("length", messageText.length);
diff = Math.abs(new Date().getSeconds() - startTime);
}
} finally {
await editor.tick();
await editor.stop();
console.log("time", diff);
console.log("ended", true);
waitMessage.reply_to_message = msg;
waitMessage.text = messageText;
MessageStore.put(waitMessage);
await replyToMessage(waitMessage, `⏱️ ${diff}s`);
}
} catch (error) {
console.error(error);
if (error instanceof ApiError) {
if (error.status === 429) {
await replyToMessage(waitMessage, "На сегодня всё, лимиты закончились.").catch(logError);
return;
}
}
await replyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
}
}
}
+39
View File
@@ -0,0 +1,39 @@
import {Message} from "typescript-telegram-bot-api";
import {chatCommandToString, delay, logError, sendMessage} from "../util/utils";
import {ChatCommand} from "../base/chat-command";
import {chatCommands} from "../index";
import {TelegramError} from "typescript-telegram-bot-api/dist/errors";
export class Help implements ChatCommand {
regexp = /^\/(h|help)/i;
title = "/help";
description = "Show list of commands";
async execute(msg: Message) {
let text = "Commands:\n\n";
chatCommands.forEach(c => {
text += `${chatCommandToString(c)}\n`;
});
await sendMessage({chatId: msg.from.id, text: text})
.then(async () => {
if (msg.chat.type !== "private") {
await sendMessage({message: msg, text: "Отправил команды в ЛС 😎"}).catch(logError);
}
})
.catch(async (e) => {
if (e instanceof TelegramError) {
if (e.response?.error_code === 403) {
await sendMessage({
message: msg,
text: "Не смог отправить команды в ЛС ☹️\nТогда отправлю сюда"
}).catch(logError);
await delay(1000);
await sendMessage({message: msg, text: text}).catch(logError);
}
}
});
}
}
+18
View File
@@ -0,0 +1,18 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, replyToMessage} from "../util/utils";
export class Id extends ChatCommand {
regexp = /^\/id/i;
title = "/id";
description = "ID of chat, user and reply (if replied to any message)";
async execute(msg: Message): Promise<void> {
let text = `chat id: \n\`\`\`${msg.chat.id}\`\`\` \nfrom id: \n\`\`\`${msg.from.id}\`\`\``;
if (msg.reply_to_message) {
text += ` \nreply id: \n\`\`\`${msg.reply_to_message.from.id}\`\`\``;
}
await replyToMessage(msg, text, "MarkdownV2").catch(logError);
}
}
+17
View File
@@ -0,0 +1,17 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {bot} from "../index";
export class Leave extends ChatCommand {
regexp = /^\/leave/i;
title = "/leave";
description = "Bot will leave current chat";
requirements = Requirements.Build(Requirement.BOT_ADMIN, Requirement.CHAT);
async execute(msg: Message): Promise<void> {
await bot.leaveChat({chat_id: msg.chat.id});
}
}
+39
View File
@@ -0,0 +1,39 @@
import {addMute} from "../db/database";
import {ChatCommand} from "../base/chat-command";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {fullName, logError, oldSendMessage} from "../util/utils";
import {botUser} from "../index";
import {Environment} from "../common/environment";
export class Mute implements ChatCommand {
regexp = /^\/mute/i;
title = "/mute";
description = "Bot will ignore user";
requirements = Requirements.Build(Requirement.BOT_ADMIN, Requirement.REPLY);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) {
await oldSendMessage(msg, "Бот не может сам себя игнорировать").catch(logError);
return;
}
if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Бот не будет игнорировать своего создателя").catch(logError);
return;
}
if (await addMute(id)) {
await oldSendMessage(msg, text + " в муте! 🔇").catch(logError);
} else {
await oldSendMessage(msg, text + " уже в муте 🤔").catch(logError);
}
}
}
+122
View File
@@ -0,0 +1,122 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {bot, ollama} from "../index";
import {
collectReplyChainText,
editMessageText,
escapeMarkdownV2Text,
extractText,
logError,
replyToMessage,
startIntervalEditor
} from "../util/utils";
import {Environment} from "../common/environment";
import {MessageStore} from "../common/message-store";
export class OllamaChat extends ChatCommand {
regexp = /^\/ollama\s([^]+)/;
title = "/ollama";
description = "talk to AI (Ollama)";
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
console.log("match", match);
return this.executeOllama(msg, match?.[1]);
}
async executeOllama(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
const chatId = msg.chat.id;
const messageParts = await collectReplyChainText(msg);
console.log("MESSAGE PARTS", messageParts);
const chatMessages = messageParts.map(part => {
return {
role: part.bot ? "ASSISTANT" : "USER",
content: extractText(part.content, Environment.BOT_PREFIX)
};
});
chatMessages.reverse();
chatMessages.unshift({role: "SYSTEM", content: Environment.SYSTEM_PROMPT});
let waitMessage: Message;
const startTime = Date.now();
try {
waitMessage = await bot.sendMessage({
chat_id: chatId,
text: Environment.waitText,
reply_parameters: {
chat_id: chatId,
message_id: msg.message_id
}
});
const stream = await ollama.chat({
model: Environment.OLLAMA_MODEL,
stream: true,
think: false,
keep_alive: 300,
messages: chatMessages
});
let currentText = "";
let shouldBreak = false;
const editor = startIntervalEditor({
intervalMs: 4500,
getText: () => currentText,
editFn: async (text) => {
await editMessageText(chatId, waitMessage.message_id, escapeMarkdownV2Text(text), "Markdown");
},
});
try {
for await (const chunk of stream) {
const content = chunk.message.content;
currentText += content;
const length = currentText.length;
if (length > 4096) {
currentText = currentText.slice(0, 4093) + "...";
shouldBreak = true;
}
if (shouldBreak || chunk.done) {
console.log("messageText", currentText);
console.log("length", length);
if (shouldBreak) {
console.log("break", true);
} else {
console.log("ended", true);
}
stream.abort();
const diff = Math.abs(Date.now() - startTime) / 1000;
await editor.tick();
await editor.stop();
waitMessage.reply_to_message = msg;
waitMessage.text = currentText;
MessageStore.put(waitMessage);
await replyToMessage(waitMessage, `⏱️ ${diff}s`);
break;
}
}
} finally {
await editor.tick();
await editor.stop();
}
} catch (error) {
console.error(error);
await replyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
}
return Promise.resolve();
}
}
+24
View File
@@ -0,0 +1,24 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {oldSendMessage} from "../util/utils";
import {ollama} from "../index";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
export class OllamaKill extends ChatCommand {
regexp = /^\/killollama/i;
title = "/killOllama";
description = "dunno, do some shit";
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message): Promise<void> {
try {
ollama.abort();
} catch (e) {
console.error(e);
}
await oldSendMessage(msg, "Остановил все генерации");
}
}
+115
View File
@@ -0,0 +1,115 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {bot, ollama} from "../index";
import {editMessageText, ignore, replyToMessage} from "../util/utils";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {Environment} from "../common/environment";
export class OllamaPrompt extends ChatCommand {
regexp = /^\/ollamaprompt\s([^]+)/i;
title = "/ollamaPrompt";
description = "Custom prompt for AI (Ollama)";
requirements = Requirements.Build(Requirement.BOT_ADMIN);
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
return this.executeOllama(msg, match?.[1]);
}
async executeOllama(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
const chatId = msg.chat.id;
let waitMessage: Message;
try {
waitMessage = await bot.sendMessage({
chat_id: chatId,
text: Environment.waitText,
reply_parameters: {
chat_id: chatId,
message_id: msg.message_id
},
parse_mode: "Markdown"
});
const stream = await ollama.chat({
model: Environment.OLLAMA_MODEL,
stream: true,
messages: [
{
role: "system",
content: text
}
]
});
let ended = false;
let messageText = "";
const interval = setInterval(async () => {
const length = messageText.length;
console.log("messageText", messageText);
console.log("length", length);
console.log("ended", ended);
await editMessageText(chatId, waitMessage.message_id, messageText);
if (ended) {
clearInterval(interval);
}
}, 4500);
let shouldBreak = false;
for await (const chunk of stream) {
messageText += chunk.message.content;
const length = messageText.length;
if (length > 4096) {
messageText = messageText.slice(0, 4093) + "...";
shouldBreak = true;
}
if (shouldBreak) {
console.log("messageText", messageText);
console.log("length", length);
console.log("break", true);
ended = true;
stream.abort();
clearInterval(interval);
const diff = Math.abs(new Date().getSeconds() - waitMessage.date);
messageText += `\n\nДумал ${diff}s`;
await editMessageText(chatId, waitMessage.message_id, messageText);
await replyToMessage(waitMessage, "Закончил лишь часть 😉");
break;
}
if (chunk.done) {
console.log("messageText", messageText);
console.log("length", messageText.length);
console.log("ended", true);
ended = true;
clearInterval(interval);
const diff = Math.abs(Date.now() / 1000 - waitMessage.date);
messageText += `\n\nДумал ${diff}s`;
await editMessageText(chatId, waitMessage.message_id, messageText);
await replyToMessage(waitMessage, "Закончил 😉");
}
}
} catch (error) {
console.error(error);
await editMessageText(chatId, waitMessage.message_id, `Произошла ошибка!\n${error.toString()}`)
.catch(async (e) => {
await editMessageText(chatId, waitMessage.message_id, `Произошла ошибка!\n${e.toString()}`).catch(ignore);
});
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import {ChatCommand} from "../base/chat-command";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {bot, ollama} from "../index";
import {WebSearchResponse} from "../model/web-search-response";
import {editMessageText} from "../util/utils";
import {Environment} from "../common/environment";
export class OllamaSearch extends ChatCommand {
regexp = /^\/(s|search)\s([^]+)/;
title = "/search";
description = "Web search via Ollama";
override requirements = Requirements.Build(Requirement.BOT_ADMIN);
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
console.log("match", match);
const chatId = msg.chat.id;
try {
const wait = await bot.sendMessage({
chat_id: chatId,
text: Environment.waitText,
reply_parameters: {
chat_id: chatId,
message_id: msg.message_id
},
parse_mode: "Markdown"
});
const results = await ollama.webSearch({query: match?.[1]});
console.log("results", results);
let message = "Результаты:\n\n";
results.results.forEach((result, index) => {
const r = result as WebSearchResponse;
message += `${index + 1}. ${r.url}\n`;
});
await editMessageText(chatId, wait.message_id, message);
} catch (error) {
console.error(error);
}
return Promise.resolve();
}
}
+17
View File
@@ -0,0 +1,17 @@
import {logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
import {ChatCommand} from "../base/chat-command";
export class Ping implements ChatCommand {
regexp = /^\/ping/i;
title = "/ping";
description = "Ping between received and sent message";
async execute(msg: Message) {
const then = new Date().getMilliseconds();
await oldSendMessage(msg, "pong").catch(logError);
const now = new Date().getMilliseconds();
const diff = Math.abs(now - then);
await oldSendMessage(msg, `ping: ${diff}ms`).catch(logError);
}
}
+12
View File
@@ -0,0 +1,12 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, randomValue, replyToMessage} from "../util/utils";
import {prefixAnswers} from "../db/database";
export class PrefixResponse extends ChatCommand {
regexp: RegExp;
async execute(msg: Message): Promise<void> {
await replyToMessage(msg, randomValue(prefixAnswers)).catch(logError);
}
}
+57
View File
@@ -0,0 +1,57 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {extractMessagePayload, logError, sendMessage} from "../util/utils";
import {bot} from "../index";
import QRCode from "qrcode";
export class Qr extends ChatCommand {
regexp = /^\/qr/i;
title = "/qr";
description = "Generates QR-code from text you sent or replied to.";
async execute(msg: Message): Promise<void> {
const chatId = msg.chat.id;
const split = msg.text?.split("/qr ");
const matchText = split[1];
const payload = extractMessagePayload(msg, matchText);
if (!payload) {
await sendMessage({
chatId: chatId,
text: "Отправь: /qr <текст или ссылка>\n" + "или ответь командой /qr на сообщение, из которого взять текст."
});
return;
}
if (payload.length > 1500) {
await sendMessage({
chatId: chatId,
text: `Слишком длинный текст для QR (${payload.length} символов). Максимум 1500 символов.`
});
return;
}
try {
await bot.sendChatAction({chat_id: chatId, action: "upload_photo"});
const pngBuffer = await QRCode.toBuffer(payload, {
type: "png",
errorCorrectionLevel: "L",
margin: 2,
scale: 8,
});
await bot.sendPhoto({
chat_id: chatId,
photo: pngBuffer,
caption: `QR готов ✅\nСодержимое: ${payload.length > 80 ? payload.slice(0, 80) + "…" : payload}`,
reply_parameters: {
message_id: msg.message_id,
}
});
} catch (e) {
await sendMessage({chatId: chatId, text: `Не получилось сгенерировать QR: ${e?.message ?? String(e)}`}).catch(logError);
}
}
}
+432
View File
@@ -0,0 +1,432 @@
import axios from "axios";
import sharp from "sharp";
import twemoji from "twemoji";
import emojiRegex from "emoji-regex";
import {createCanvas, GlobalFonts, type Image as CanvasImage, loadImage, SKRSContext2D} from "@napi-rs/canvas";
import {Message, PhotoSize} from "typescript-telegram-bot-api";
import {ChatCommand} from "../base/chat-command";
import {bot, botUser} from "../index";
import {
getChatAvatar,
getFileUrl,
getUserAvatar,
logError,
makeDarkGradientBgFancy,
oldSendMessage,
replyToMessage
} from "../util/utils";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
try {
GlobalFonts.registerFromPath("./assets/Inter_18pt-ExtraThin.ttf", "InterExtraThin");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Thin.ttf", "InterThin");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Light.ttf", "InterLight");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Regular.ttf", "Inter");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Medium.ttf", "InterMedium");
GlobalFonts.registerFromPath("./assets/Inter_18pt-SemiBold.ttf", "InterSemiBold");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Bold.ttf", "InterBold");
GlobalFonts.registerFromPath("./assets/Inter_18pt-ExtraBold.ttf", "InterExtraBold");
GlobalFonts.registerFromPath("./assets/Inter_18pt-Black.ttf", "InterBlack");
} catch (e) {
console.error(e);
}
export class Quote extends ChatCommand {
regexp = /^\/(cit|q|quote)$/i;
title = "/quote";
description = "Make quote from text (or quote)";
requirements = Requirements.Build(Requirement.REPLY);
async execute(msg: Message): Promise<void> {
const chatId = msg.chat.id;
const reply = msg.reply_to_message;
if (!reply) {
await replyToMessage(msg, "Сделай /quote реплаем на сообщение 🙂").catch(logError);
return;
}
try {
const quoteRaw = (msg.quote?.text ?? reply.text ?? reply.caption ?? "").trim();
if (quoteRaw.length === 0) {
await replyToMessage(msg, "Не нашёл в сообщении текста 😢").catch(logError);
return;
}
let quote = quoteRaw.length ? quoteRaw : "…";
if (quote.length > 2500) quote = quote.slice(0, 2497) + "…";
const png = await renderQuoteCard(quote, reply);
await bot.sendPhoto({
chat_id: chatId,
photo: png,
reply_parameters: {
message_id: msg.message_id,
},
}).catch(logError);
} catch (e) {
console.error(e);
await oldSendMessage(msg, "Не смог собрать цитату 😢").catch(logError);
}
}
}
// ===== Emoji cache & helpers =====
const emojiCache = new Map<string, CanvasImage>();
function twemojiUrl(emoji: string) {
const code = twemoji.convert.toCodePoint(emoji);
return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/${code}.png`;
}
async function loadEmoji(emoji: string): Promise<CanvasImage> {
const url = twemojiUrl(emoji);
const cached = emojiCache.get(url);
if (cached) return cached;
const res = await axios.get<ArrayBuffer>(url, {responseType: "arraybuffer"});
const img = await loadImage(Buffer.from(res.data));
emojiCache.set(url, img);
return img;
}
type Segment = { type: "text"; v: string } | { type: "emoji"; v: string };
function splitSegments(text: string): Segment[] {
const re = emojiRegex();
const out: Segment[] = [];
let last = 0;
for (const m of text.matchAll(re)) {
const i = m.index ?? 0;
if (i > last) out.push({type: "text", v: text.slice(last, i)});
out.push({type: "emoji", v: m[0]});
last = i + m[0].length;
}
if (last < text.length) out.push({type: "text", v: text.slice(last)});
return out;
}
function measure(ctx: SKRSContext2D, s: string) {
return ctx.measureText(s).width;
}
function wrapSegments(ctx: SKRSContext2D, segments: Segment[], maxW: number, emojiW: number) {
const lines: { segments: Segment[]; width: number }[] = [];
let cur: Segment[] = [];
let w = 0;
const push = () => {
lines.push({segments: cur, width: w});
cur = [];
w = 0;
};
const add = (seg: Segment, segW: number) => {
if (cur.length && w + segW > maxW) push();
cur.push(seg);
w += segW;
};
for (const seg of segments) {
if (seg.type === "emoji") {
add(seg, emojiW);
continue;
}
// переносы/пробелы
const parts = seg.v.split(/(\s+)/);
for (const p of parts) {
if (!p) continue;
const sub = p.split("\n");
for (let si = 0; si < sub.length; si++) {
const chunk = sub[si];
if (chunk) add({type: "text", v: chunk}, measure(ctx, chunk));
if (si !== sub.length - 1) push();
}
}
}
if (cur.length) push();
return lines;
}
function lineWidth(ctx: SKRSContext2D, segments: Segment[], fontSize: number) {
const emojiSize = Math.round(fontSize * 1.05);
let w = 0;
for (const s of segments) {
w += s.type === "emoji" ? emojiSize : ctx.measureText(s.v).width;
}
return w;
}
function addEllipsisToFit(ctx: SKRSContext2D, segments: Segment[], maxW: number, fontSize: number): Segment[] {
const emojiSize = Math.round(fontSize * 1.05);
const ell: Segment = {type: "text", v: "…"};
const ellW = ctx.measureText("…").width;
const out = segments.map((s) => ({...s})) as Segment[];
const widthOf = (arr: Segment[]) => {
let w = 0;
for (const s of arr) w += s.type === "emoji" ? emojiSize : ctx.measureText(s.v).width;
return w;
};
while (out.length && widthOf(out) + ellW > maxW) {
const last = out[out.length - 1];
if (last.type === "emoji") {
out.pop();
continue;
}
if (last.v.length <= 1) {
out.pop();
continue;
}
last.v = last.v.slice(0, -1);
}
return [...out, ell];
}
async function drawLine(ctx: SKRSContext2D, line: Segment[], x: number, baselineY: number, fontSize: number) {
const emojiSize = Math.round(fontSize * 1.05);
let cx = x;
for (const seg of line) {
if (seg.type === "text") {
ctx.fillText(seg.v, cx, baselineY);
cx += measure(ctx, seg.v);
} else {
const img = await loadEmoji(seg.v);
const y = baselineY - emojiSize + Math.round(fontSize * 0.2);
ctx.drawImage(img, cx, y, emojiSize, emojiSize);
cx += emojiSize;
}
}
}
type Fitted = {
fontSize: number;
lineH: number;
lines: { segments: Segment[]; width: number }[];
truncated: boolean;
};
function fitQuoteToBox(ctx: SKRSContext2D, quoteWithOpen: string, boxW: number, boxH: number): Fitted {
const MAX_FONT = 64;
const MIN_FONT = 18;
const endSuffix = " »";
const segments = splitSegments(quoteWithOpen);
for (let fontSize = MAX_FONT; fontSize >= MIN_FONT; fontSize -= 2) {
const emojiSize = Math.round(fontSize * 1.05);
ctx.font = `${fontSize}px Inter, sans-serif`;
const lines = wrapSegments(ctx, segments, boxW, emojiSize);
const lineH = Math.round(fontSize * 1.20);
const totalH = lines.length * lineH;
if (!lines.length) continue;
const endW = ctx.measureText(endSuffix).width;
const last = lines[lines.length - 1];
if (totalH <= boxH && last.width + endW <= boxW) {
last.segments = [...last.segments, {type: "text", v: endSuffix}];
last.width += endW;
return {fontSize: fontSize, lineH, lines, truncated: false};
}
}
const fontSize = MIN_FONT;
const emojiSize = Math.round(fontSize * 1.05);
ctx.font = `${fontSize}px Inter, sans-serif`;
const lineH = Math.round(fontSize * 1.20);
const maxLinesByHeight = Math.max(1, Math.floor(boxH / lineH));
let lines = wrapSegments(ctx, segments, boxW, emojiSize);
const endW = ctx.measureText(endSuffix).width;
if (lines.length > maxLinesByHeight) {
lines = lines.slice(0, maxLinesByHeight);
const last = lines[lines.length - 1];
if (last.width + endW > boxW) {
last.segments = addEllipsisToFit(ctx, last.segments, boxW - endW, fontSize);
last.width = lineWidth(ctx, last.segments, fontSize);
} else {
last.segments = addEllipsisToFit(ctx, last.segments, boxW - endW, fontSize);
last.width = lineWidth(ctx, last.segments, fontSize);
}
} else {
const last = lines[lines.length - 1];
if (last && last.width + endW > boxW) {
last.segments = addEllipsisToFit(ctx, last.segments, boxW - endW, fontSize);
last.width = lineWidth(ctx, last.segments, fontSize);
}
}
if (lines.length) {
const last = lines[lines.length - 1];
last.segments = [...last.segments, {type: "text", v: endSuffix}];
last.width += endW;
}
return {fontSize: fontSize, lineH, lines, truncated: true};
}
async function getBackground(
reply: Message,
W: number,
H: number,
author: QuoteAuthor,
isForwarded: boolean
): Promise<Buffer> {
let src: Buffer | null = null;
const photoArr = reply.photo as PhotoSize[] | undefined;
const msgPhoto = photoArr && photoArr.length ? photoArr[photoArr.length - 1] : undefined;
if (msgPhoto?.file_id) {
const url = await getFileUrl(bot, msgPhoto.file_id);
const res = await axios.get<ArrayBuffer>(url, {responseType: "arraybuffer"});
src = Buffer.from(res.data);
} else {
if (author.userId) {
src = await getUserAvatar(bot, author.userId);
} else if (author.chatId) {
src = await getChatAvatar(bot, author.chatId);
} else if (!isForwarded && reply.from?.id) {
src = await getUserAvatar(bot, reply.from.id);
}
}
if (!src) {
return makeDarkGradientBgFancy(W, H, `${reply.message_id}-${reply.date ?? ""}`);
// return sharp({create: {width: W, height: H, channels: 3, background: "#1f1f1f"}})
// .png()
// .toBuffer();
}
return sharp(src)
.resize(W, H, {fit: "cover"})
.blur(18)
.modulate({brightness: 0.75, saturation: 1.1})
.png()
.toBuffer();
}
async function renderQuoteCard(quote: string, reply: Message) {
const W = 1280;
const H = 720;
const author = getQuoteAuthor(reply);
const forwarded = !!reply.forward_origin;
const name = author.name;
const userTag = author.username ? `@${author.username}` : "";
const me = botUser;
const botTag = me.username ? `@${me.username}` : "@bot";
const date = new Date((reply.date ?? Math.floor(Date.now() / 1000)) * 1000);
const dd = String(date.getDate()).padStart(2, "0");
const mm = String(date.getMonth() + 1).padStart(2, "0");
const yyyy = String(date.getFullYear());
const dateStr = `${dd}.${mm}.${yyyy}`;
const bgBuf = await getBackground(reply, W, H, author, forwarded);
const canvas = createCanvas(W, H);
const c = canvas.getContext("2d");
const bgImg = await loadImage(bgBuf);
c.drawImage(bgImg, 0, 0, W, H);
c.fillStyle = "rgba(0,0,0,0.35)";
c.fillRect(0, 0, W, H);
const edgePad = 56;
const reservedBottom = 140;
const quoteBoxX = edgePad;
const quoteBoxW = W - edgePad * 2;
const quoteTop = 90;
const quoteBottom = H - reservedBottom;
const quoteH = quoteBottom - quoteTop;
c.fillStyle = "rgba(255,255,255,0.92)";
c.textBaseline = "alphabetic";
c.shadowColor = "rgba(0,0,0,0.55)";
c.shadowBlur = 10;
c.shadowOffsetY = 2;
const quoteForFit = `« ${quote}`;
const fitted = fitQuoteToBox(c, quoteForFit, quoteBoxW, quoteH);
c.font = `${fitted.fontSize}px InterSemiBold, sans-serif`;
const totalTextH = fitted.lines.length * fitted.lineH;
let y = quoteTop + (quoteH - totalTextH) / 2 + fitted.fontSize;
for (const ln of fitted.lines) {
const x = quoteBoxX + (quoteBoxW - ln.width) / 2;
await drawLine(c, ln.segments, x, y, fitted.fontSize);
y += fitted.lineH;
}
c.shadowBlur = 0;
c.shadowOffsetY = 0;
c.fillStyle = "rgba(255,255,255,0.70)";
c.font = "28px InterLight, Inter, sans-serif";
c.textAlign = "center";
c.fillText(userTag ? `${name} | ${userTag}` : name, W / 2, H - 86);
c.font = "22px InterMedium, sans-serif";
c.fillStyle = "rgba(255,255,255,0.45)";
c.textAlign = "left";
c.fillText(botTag, edgePad, H - 34);
c.textAlign = "right";
c.fillText(dateStr, W - edgePad, H - 34);
return canvas.toBuffer("image/png");
}
type QuoteAuthor = {
name: string;
username?: string;
userId?: number;
chatId?: number;
};
function getQuoteAuthor(reply: Message): QuoteAuthor {
const origin = reply.forward_origin;
if (origin) {
switch (origin.type) {
case "user": {
const u = origin.sender_user;
const name = [u.first_name, u.last_name].filter(Boolean).join(" ") || u.username || "Unknown";
return {name, username: u.username, userId: u.id};
}
case "hidden_user": {
const name = origin.sender_user_name || "Unknown";
return {name};
}
}
}
const u = reply.from!;
const name = [u.first_name, u.last_name].filter(Boolean).join(" ") || u.username || "Unknown";
return {name, username: u.username, userId: u.id};
}
+24
View File
@@ -0,0 +1,24 @@
import {ChatCommand} from "../base/chat-command";
import {getRandomInt, getRangedRandomInt, logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
export class RandomInt extends ChatCommand {
regexp = /^\/randomInt/i;
title = "/randomInt [min] [max]";
description = "Ranged random integer from parameters";
async execute(msg: Message) {
const split = msg.text.split(" ");
const min = parseInt(split[1]);
const max = parseInt(split[2]);
const good = max > min;
const sufficient = !!(min && max) && good;
const random = !sufficient ? getRandomInt(Math.pow(2, 60)) : getRangedRandomInt(min, max);
const randomText = !sufficient ? random.toString() : `[${min}; ${max}]: ${random}`;
await oldSendMessage(msg, randomText).catch(logError);
}
}
+26
View File
@@ -0,0 +1,26 @@
import {ChatCommand} from "../base/chat-command";
import {getRandomInt, logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
export class RandomString implements ChatCommand {
regexp = /^\/randomString/i;
title = "/randomString [length]";
description = "literally random string (up to 4096 symbols)";
async execute(msg: Message) {
const split = msg.text.split(" ");
const l = parseInt(split.length > 1 ? split[1] : "1");
const length = (l <= 0 || l > 4096) ? 1 : l;
let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя0123456789";
for (let i = 0; i < length; i++) {
result += characters.charAt(getRandomInt(characters.length));
}
await oldSendMessage(msg, result).catch(logError);
}
}
+46
View File
@@ -0,0 +1,46 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {bot} from "../index";
import {delay, logError, randomValue} from "../util/utils";
const texts = [
"ну что-же, господа",
"приятно было с вами пообщаться",
"но мне пора на покой",
"всего хорошего"
];
const timings = [1500, 2500];
const timer = [3, 2, 1];
export class Shutdown extends ChatCommand {
regexp = /^\/shutdown/i;
title = "/shutdown";
description = "Self-destruction sequence for bot (shutdown)";
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message): Promise<void> {
await bot.sendMessage({chat_id: msg.chat.id, text: "..."}).catch(logError);
if (msg.chat.type !== "private" && !msg.text.toLowerCase().startsWith("/shutdown now")) {
for (const text of texts) {
await delay(randomValue(timings));
await bot.sendMessage({chat_id: msg.chat.id, text: text}).catch(logError);
}
await delay(randomValue(timings));
for (const t of timer) {
await bot.sendMessage({chat_id: msg.chat.id, text: `${t}`}).catch(logError);
await delay(1000);
}
}
await bot.sendMessage({chat_id: msg.chat.id, text: "*R.I.P*"}).catch(logError);
delay(2000).then(() => process.exit(0));
}
}
+14
View File
@@ -0,0 +1,14 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {chatCommands} from "../index";
import {Help} from "./help";
export class Start extends ChatCommand {
regexp = /^\/start/i;
title = "/start";
description = "Start the bot";
async execute(msg: Message): Promise<void> {
await chatCommands.find(e => e instanceof Help).execute(msg);
}
}
+14
View File
@@ -0,0 +1,14 @@
import {ChatCommand} from "../base/chat-command";
import {logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
import {systemSpecsText} from "../index";
export class SystemSpecs implements ChatCommand {
regexp = /^\/systemspecs/i;
title = "/systemSpecs";
description = "System specifications of system";
async execute(msg: Message) {
await oldSendMessage(msg, systemSpecsText).catch(logError);
}
}
+14
View File
@@ -0,0 +1,14 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {logError, randomValue, replyToMessage} from "../util/utils";
import {testAnswers} from "../db/database";
export class Test implements ChatCommand {
regexp = /^(test|тест|еуые|ntcn|инноке(нтий|ш|нтич))/i;
title = "тест";
description = "System functionality check";
async execute(msg: Message) {
await replyToMessage(msg, randomValue(testAnswers) || "а").catch(logError);
}
}
+28
View File
@@ -0,0 +1,28 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {logError, replyToMessage} from "../util/utils";
import {bot} from "../index";
export class Title extends ChatCommand {
regexp = /^\/title\s([^]+)/;
title = "/title [title]";
description = "Change group title";
requirements = Requirements.Build(
Requirement.CHAT,
Requirement.BOT_ADMIN,
Requirement.BOT_CHAT_ADMIN
);
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const title = (match?.[1] ?? "").trim();
if (title.length === 0) {
await replyToMessage(msg, "Не нашёл название...").catch(logError);
return;
}
await bot.setChatTitle({chat_id: msg.chat.id, title: title}).catch(logError);
}
}
+50
View File
@@ -0,0 +1,50 @@
import {ChatCommand} from "../base/chat-command";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {bot, botUser} from "../index";
import {fullName, logError, oldSendMessage, replyToMessage} from "../util/utils";
import {Environment} from "../common/environment";
export class Unban extends ChatCommand {
regexp = /^\/unban/i;
title = "/unban [reply]";
description = "unban user from chat";
requirements = Requirements.Build(
Requirement.CHAT,
Requirement.BOT_CHAT_ADMIN,
Requirement.REPLY,
Requirement.BOT_ADMIN
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
const user = msg.reply_to_message.from;
const userId = user.id;
if (userId === botUser.id) {
await replyToMessage(msg, "Бот и так не в бане сам у себя.").catch(logError);
return;
}
if (userId === Environment.CREATOR_ID) {
await replyToMessage(msg, "Создатель бота и так не в бане и никогда не будет.").catch(logError);
return;
}
if (msg.from.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
await replyToMessage(msg, "Админимтраторы бота и так не в бане.").catch(logError);
return;
}
bot.unbanChatMember({chat_id: msg.chat.id, user_id: userId})
.then(async () => {
await oldSendMessage(msg, `${fullName(user)} разбанен ⛓️‍💥`).catch(logError);
})
.catch(async () => {
await oldSendMessage(msg, `Не смог разбанить ${fullName(user)} ☹️`).catch(logError);
});
}
}
+38
View File
@@ -0,0 +1,38 @@
import {removeMute} from "../db/database";
import {ChatCommand} from "../base/chat-command";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {fullName, logError, oldSendMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
import {botUser} from "../index";
import {Environment} from "../common/environment";
export class Unmute implements ChatCommand {
regexp = /^\/unmute/i;
title = "/unmute";
description = "Bot will start responding to the user";
requirements = Requirements.Build(Requirement.BOT_ADMIN, Requirement.CHAT, Requirement.REPLY);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
if (id === botUser.id) {
await oldSendMessage(msg, "Бот и так всегда к себе прислушивается").catch(logError);
return;
}
if (id === Environment.CREATOR_ID) {
await oldSendMessage(msg, "Бот всегда слушает своего создателя").catch(logError);
return;
}
if (await removeMute(id)) {
await oldSendMessage(msg, text + " больше не в муте! 🔈").catch(logError);
} else {
await oldSendMessage(msg, text + " не был в муте 🤔").catch(logError);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import {ChatCommand} from "../base/chat-command";
import {Message} from "typescript-telegram-bot-api";
import {getUptime, logError, oldSendMessage} from "../util/utils";
export class Uptime extends ChatCommand {
regexp = /^\/uptime/i;
title = "/uptime";
description = "Bot's uptime";
async execute(msg: Message): Promise<void> {
await oldSendMessage(msg, getUptime()).catch(logError);
}
}
+19
View File
@@ -0,0 +1,19 @@
import {ChatCommand} from "../base/chat-command";
import {logError, randomValue, oldSendMessage} from "../util/utils";
import {betterAnswers} from "../db/database";
import {Message} from "typescript-telegram-bot-api";
export class WhatBetter extends ChatCommand {
regexp = /^\/(what|что)\s(better|лучше)\s([^]+)\s(or|или)\s([^]+)/i;
title = "/what better [a] or [b]";
description = "either a or b randomly (50% chance)";
async execute(msg: Message, match?: RegExpExecArray) {
const a = match[3];
const b = match[5].trimStart();
const text = `${randomValue(betterAnswers)} ${randomValue([a, b])}`;
await oldSendMessage(msg, text).catch(logError);
}
}
+90
View File
@@ -0,0 +1,90 @@
import {ChatCommand} from "../base/chat-command";
import {getRandomInt, getRangedRandomInt, logError, replyToMessage} from "../util/utils";
import {Message} from "typescript-telegram-bot-api";
export class When extends ChatCommand {
regexp = /^\/(when|когда)\s([^]+)/i;
title = "/when [value]";
description = "random date";
async execute(msg: Message) {
let text = "через ";
const type = getRandomInt(8);
switch (type) {
case 0:
text = "сейчас";
break;
case 1:
text = "никогда";
break;
case 2: //seconds
{
const seconds = getRangedRandomInt(1, 60);
text += `${seconds} `;
text += (
(seconds == 1 || seconds % 10 == 1) ? "секунду" :
((seconds > 1 && seconds < 5) || (seconds % 10 > 1 && seconds % 10 < 5)) ? "секунды" : "секунд"
);
break;
}
case 3: {
const minutes = getRangedRandomInt(1, 60);
text += `${minutes} `;
text += (
(minutes == 1 || minutes % 10 == 1) ? "минуту" :
((minutes > 1 && minutes < 5) || (minutes % 10 > 1 && minutes % 10 < 5)) ? "минуты" : "минут"
);
break;
}
case 4: {
const hours = getRangedRandomInt(1, 24);
text += `${hours} `;
text += (
(hours == 1 || hours % 10 == 1) ? "час" :
((hours > 1 && hours < 5) || (hours % 10 > 1 && hours % 10 < 5)) ? "часа" : "часов"
);
break;
}
case 5: {
const weeks = getRangedRandomInt(1, 4);
text += `${weeks} `;
text += (weeks == 1 ? "неделю" : "недель");
break;
}
case 6: {
const months = getRandomInt(12);
text += `${months} `;
text += (
(months == 1 || months % 10 == 1) ? "месяц" :
((months > 1 && months < 5) || (months % 10 > 1 && months % 10 < 5)) ? "месяца" : "месяцев"
);
break;
}
case 7: {
const years = getRangedRandomInt(1, 100);
text += `${years} `;
text += (
(years == 1 || years % 10 == 1) ? "год" :
((years > 1 && years < 5) || (years % 10 > 1 && years % 10 < 5)) ? "года" : "лет"
);
break;
}
}
await replyToMessage(msg, text).catch(logError);
}
}