diff --git a/sql/update_2023-07-04_01.sql b/sql/update_2023-07-04_01.sql new file mode 100644 index 0000000..a10a821 --- /dev/null +++ b/sql/update_2023-07-04_01.sql @@ -0,0 +1,19 @@ +CREATE TABLE `ada`.`bans` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_id` INT UNSIGNED NOT NULL, + `chat_id` INT UNSIGNED NOT NULL, + `reason` VARCHAR(50) NULL, + `date` INT UNSIGNED NOT NULL, + PRIMARY KEY (`id`), + INDEX `idx_user_id_chat_id` (`chat_id` ASC, `user_id` ASC) VISIBLE, + INDEX `fk_bans_user_id_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_bans_user_id` + FOREIGN KEY (`user_id`) + REFERENCES `ada`.`users` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_bans_chat_id` + FOREIGN KEY (`chat_id`) + REFERENCES `ada`.`chats` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION); diff --git a/src/action/AdaShield.ts b/src/action/AdaShield.ts index 9ae21c9..8cba209 100644 --- a/src/action/AdaShield.ts +++ b/src/action/AdaShield.ts @@ -142,14 +142,14 @@ export default class AdaShield extends Action { */ private async updateRelationship(): Promise { - const user = await UserHelper.getUserByTelegramId(this.context.newChatMember!.getId()); - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const user = await UserHelper.getByTelegramId(this.context.newChatMember!.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); const relUserChat = new RelUsersChats(); relUserChat .update() .set("joined", 0) .where("user_id").equal(user.id) - .and("chat_id").equal(chat.id); + .and("chat_id").equal(chat!.id); return relUserChat.execute(); } diff --git a/src/action/AskToAsk.ts b/src/action/AskToAsk.ts index c428660..40f1b9b 100644 --- a/src/action/AskToAsk.ts +++ b/src/action/AskToAsk.ts @@ -42,11 +42,11 @@ export default class AskToAsk extends Action { return; } - const chat = await ChatHelper.getChatByTelegramId( + const chat = await ChatHelper.getByTelegramId( this.context.chat.getId() ); - if (!chat.warn_ask_to_ask) { + if (!chat?.warn_ask_to_ask) { return; } if (this.context.message.getReplyToMessage()) { diff --git a/src/action/Captcha.ts b/src/action/Captcha.ts index 51479e5..77328d4 100644 --- a/src/action/Captcha.ts +++ b/src/action/Captcha.ts @@ -40,7 +40,7 @@ export default class Captcha extends Action { return; } - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat?.id) { return; } diff --git a/src/action/Greetings.ts b/src/action/Greetings.ts index 284e979..ed8a32e 100644 --- a/src/action/Greetings.ts +++ b/src/action/Greetings.ts @@ -68,7 +68,7 @@ export default class Greetings extends Action { return; } - this.chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + this.chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!this.chat || !this.chat?.id) { return; } @@ -81,7 +81,7 @@ export default class Greetings extends Action { return; } - this.user = await UserHelper.getUserByTelegramId(this.context.user.getId()); + this.user = await UserHelper.getByTelegramId(this.context.user.getId()); if (!await this.isUserJoined()) { return; } diff --git a/src/action/LeftChatMember.ts b/src/action/LeftChatMember.ts index 7d6e998..da594ba 100644 --- a/src/action/LeftChatMember.ts +++ b/src/action/LeftChatMember.ts @@ -43,11 +43,11 @@ export default class LeftChatMember extends Action { return; } - const user = await UserHelper.getUserByTelegramId( + const user = await UserHelper.getByTelegramId( this.context.user.getId() ); - const chat = await ChatHelper.getChatByTelegramId( + const chat = await ChatHelper.getByTelegramId( this.context.chat.getId() ); diff --git a/src/action/NewChatMember.ts b/src/action/NewChatMember.ts index d080ca0..8ddd8fd 100644 --- a/src/action/NewChatMember.ts +++ b/src/action/NewChatMember.ts @@ -41,7 +41,7 @@ export default class NewChatMember extends Action { return; } - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat?.id) { return; } diff --git a/src/action/Ping.ts b/src/action/Ping.ts index 74c417b..1bd2b78 100644 --- a/src/action/Ping.ts +++ b/src/action/Ping.ts @@ -40,7 +40,7 @@ export default class Ping extends Action { return; } - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat) { return; } diff --git a/src/action/SaveMessage.ts b/src/action/SaveMessage.ts index 941297e..57c7810 100644 --- a/src/action/SaveMessage.ts +++ b/src/action/SaveMessage.ts @@ -42,17 +42,17 @@ export default class saveUserAndChat extends Action { } const contextUser = this.context.newChatMember || this.context.leftChatMember || this.context.user; - const user = await UserHelper.getUserByTelegramId(contextUser.getId()); - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const user = await UserHelper.getByTelegramId(contextUser.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); switch (this.context.type) { case "editedMessage": - this.updateMessage(user, chat); + this.updateMessage(user, chat!); break; default: - this.saveNewMessage(user, chat); + this.saveNewMessage(user, chat!); } return Promise.resolve(); diff --git a/src/action/SaveUserAndChat.ts b/src/action/SaveUserAndChat.ts index 257c9c2..f257ceb 100644 --- a/src/action/SaveUserAndChat.ts +++ b/src/action/SaveUserAndChat.ts @@ -38,11 +38,11 @@ export default class saveUserAndChat extends Action { public async run(): Promise { const contextUser = this.context.newChatMember || this.context.leftChatMember || this.context.user; - const user = await UserHelper.getUserByTelegramId(contextUser.getId()); + const user = await UserHelper.getByTelegramId(contextUser.getId()); const userId = user === null ? await UserHelper.createUser(contextUser) : user.id; - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); - const chatId = chat === null ? await ChatHelper.createChat(this.context.chat) : chat.id; + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); + const chatId = chat === null ? await ChatHelper.createChat(this.context.chat) : chat!.id; UserHelper.updateUser(contextUser); ChatHelper.updateChat(this.context.chat); diff --git a/src/callback/CaptchaConfirmation.ts b/src/callback/CaptchaConfirmation.ts index afaaa9c..990814f 100644 --- a/src/callback/CaptchaConfirmation.ts +++ b/src/callback/CaptchaConfirmation.ts @@ -44,11 +44,11 @@ export default class CaptchaConfirmation extends Callback { return; } - const user = await UserHelper.getUserByTelegramId( + const user = await UserHelper.getByTelegramId( this.context.user.getId() ); - const chat = await ChatHelper.getChatByTelegramId( + const chat = await ChatHelper.getByTelegramId( this.context.chat.getId() ); diff --git a/src/command/AdaShield.ts b/src/command/AdaShield.ts index 4fc1c8b..14698e4 100644 --- a/src/command/AdaShield.ts +++ b/src/command/AdaShield.ts @@ -68,7 +68,7 @@ export default class AdaShield extends Command { */ public async index(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -112,7 +112,7 @@ export default class AdaShield extends Command { */ public async change(status: number) { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Ask.ts b/src/command/Ask.ts index 8d3d0e8..dccb23a 100644 --- a/src/command/Ask.ts +++ b/src/command/Ask.ts @@ -45,8 +45,8 @@ export default class Ask extends Command { this.context.message.delete(); - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); - Lang.set(chat.language || "us"); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); + Lang.set(chat?.language || "us"); const replyToMessage = this.context.message.getReplyToMessage(); if (replyToMessage) { diff --git a/src/command/Ban.ts b/src/command/Ban.ts index 8bd758e..272a7fb 100644 --- a/src/command/Ban.ts +++ b/src/command/Ban.ts @@ -11,12 +11,27 @@ import Command from "./Command.js"; import Context from "../library/telegram/context/Context.js"; -import Message from "src/library/telegram/context/Message.js"; -import User from "src/library/telegram/context/User.js"; +import Message from "../library/telegram/context/Message.js"; +import User from "../library/telegram/context/User.js"; import CommandContext from "../library/telegram/context/Command.js"; +import UserHelper from "../helper/User.js"; +import ChatHelper from "../helper/Chat.js"; +import UserContext from "../library/telegram/context/User.js"; +import Bans from "../model/Bans.js"; +import Lang from "../helper/Lang.js"; +import Log from "../helper/Log.js"; +import { User as UserType } from "../library/telegram/type/User.js"; export default class Ban extends Command { + /** + * Command context. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private command?: CommandContext; + /** * The constructor. * @@ -37,8 +52,6 @@ export default class Ban extends Command { * @since 2023-06-07 * * @param command - * - * @returns */ public async run(command: CommandContext): Promise { @@ -46,21 +59,29 @@ export default class Ban extends Command { return; } + this.command = command; this.context.message.delete(); + let params = command.getParams() || []; + const replyToMessage = this.context.message.getReplyToMessage(); if (replyToMessage) { - this.banByReply(replyToMessage); + this.banByReply(replyToMessage, params.join(" ").trim()); return; } const mentions = await this.context.message.getMentions(); - if (!mentions.length) { - return; + if (mentions.length) { + params = params.filter((param) => param.indexOf("@") !== 0); + mentions.forEach((mention) => { + this.banByMention(mention, params.join(" ").trim()); + }); } - for (const mention of mentions) { - this.banByMention(mention); + const userId = parseInt(params[0]); + if (userId === Number(params[0])) { + params.shift(); + this.banByUserId(userId, params.join(" ").trim()); } } @@ -72,7 +93,8 @@ export default class Ban extends Command { * * @returns void */ - private async banByReply(replyToMessage: Message): Promise> { + private async banByReply(replyToMessage: Message, reason: string): Promise> { + this.saveBan(replyToMessage.getUser(), reason); return replyToMessage.getUser().ban(); } @@ -84,7 +106,79 @@ export default class Ban extends Command { * * @returns void */ - private async banByMention(mention: User): Promise> { + private async banByMention(mention: User, reason: string): Promise> { + this.saveBan(mention, reason); return mention.ban(); } + + /** + * Bans the user by Telegram ID. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param userId + * @param reason + */ + private async banByUserId(userId: number, reason: string): Promise|undefined> { + + const user = await UserHelper.getByTelegramId(userId); + + const userType: UserType = { + id: userId, + isBot: user?.is_bot, + firstName: user?.first_name, + lastName: user?.last_name, + username: user?.username || userId + }; + + const contextUser = new UserContext(userType, this.context.chat); + this.saveBan(contextUser, reason); + return contextUser.ban(); + } + + /** + * Saves the ban. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param {User} contextUser User object. + */ + private async saveBan(contextUser: User, reason: string): Promise { + + const user = await UserHelper.getByTelegramId(contextUser.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); + + if (!user || !chat) { + return; + } + + Lang.set(chat.language || "us"); + + const ban = new Bans(); + const insert = ban.insert(); + insert + .set("user_id", user.id) + .set("chat_id", chat.id) + .set("date", Math.floor(Date.now() / 1000)); + + if (reason.length) { + insert.set("reason", reason); + } + + try { + + await ban.execute(); + const message = Lang.get("bannedMessage") + .replace("{userId}", contextUser.getId()) + .replace("{username}", contextUser.getFirstName() || contextUser.getUsername()) + .replace("{reason}", reason.length ? reason : "Unknown"); + + this.context.chat.sendMessage(message, { parseMode: "HTML" }); + + } catch (err: any) { + Log.error(err.toString()); + } + } } diff --git a/src/command/Greetings.ts b/src/command/Greetings.ts index 637870d..781324d 100644 --- a/src/command/Greetings.ts +++ b/src/command/Greetings.ts @@ -74,7 +74,7 @@ export default class GreetingsCommand extends Command { */ private async index(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -111,7 +111,7 @@ export default class GreetingsCommand extends Command { */ private async on(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -135,7 +135,7 @@ export default class GreetingsCommand extends Command { */ private async off(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -170,7 +170,7 @@ export default class GreetingsCommand extends Command { params.shift(); - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Npm.ts b/src/command/Npm.ts index 84cf2d6..7153ff5 100644 --- a/src/command/Npm.ts +++ b/src/command/Npm.ts @@ -50,7 +50,7 @@ export default class Npm extends Command { return; } - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Report.ts b/src/command/Report.ts index 70b4df2..bb42b37 100644 --- a/src/command/Report.ts +++ b/src/command/Report.ts @@ -42,7 +42,7 @@ export default class Report extends Command { */ public async run(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Restrict.ts b/src/command/Restrict.ts index 877ed27..79a9215 100644 --- a/src/command/Restrict.ts +++ b/src/command/Restrict.ts @@ -65,7 +65,7 @@ export default class Restrict extends Command { */ private async index(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -97,7 +97,7 @@ export default class Restrict extends Command { */ private async on(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -116,7 +116,7 @@ export default class Restrict extends Command { */ private async off(): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Start.ts b/src/command/Start.ts index 9dc4d19..acfd654 100644 --- a/src/command/Start.ts +++ b/src/command/Start.ts @@ -39,7 +39,7 @@ export default class Start extends Command { */ public async run(payload: Record): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/Warn.ts b/src/command/Warn.ts index aa541af..9d121e8 100644 --- a/src/command/Warn.ts +++ b/src/command/Warn.ts @@ -132,8 +132,8 @@ export default class Warn extends Command { */ private async warn(contextUser: User, reason: string): Promise { - const user = await UserHelper.getUserByTelegramId(contextUser.getId()); - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const user = await UserHelper.getByTelegramId(contextUser.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!user || !chat) { return; diff --git a/src/command/Yarn.ts b/src/command/Yarn.ts index 71db01b..e67cbdb 100644 --- a/src/command/Yarn.ts +++ b/src/command/Yarn.ts @@ -50,7 +50,7 @@ export default class Yarn extends Command { return; } - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } @@ -125,7 +125,7 @@ export default class Yarn extends Command { */ async sendNewMessage(yarnPackage: YarnPackage): Promise { - const chat = await ChatHelper.getChatByTelegramId(this.context.chat.getId()); + const chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); if (!chat || !chat.id) { return; } diff --git a/src/command/federation/Ban.ts b/src/command/federation/Ban.ts new file mode 100644 index 0000000..1889886 --- /dev/null +++ b/src/command/federation/Ban.ts @@ -0,0 +1,193 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import Federation from "./Federation.js"; +import Context from "../../library/telegram/context/Context.js"; +import Message from "../../library/telegram/context/Message.js"; +import User from "../../library/telegram/context/User.js"; +import UserHelper from "../../helper/User.js"; +import ChatHelper from "../../helper/Chat.js"; +import FederationHelper from "../../helper/Federation.js"; +import Lang from "../../helper/Lang.js"; +import Log from "../../helper/Log.js"; +import Bans from "../../model/Bans.js"; + +export default class Ban extends Federation { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param app App instance. + */ + public constructor(context: Context) { + super(context); + this.setCommands(["fban"]); + } + + /** + * Bans an user in the federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @return + */ + private async ban(): Promise { + + if (!await this.context.user.isAdmin()) { + return; + } + + const isUserAdmin = await FederationHelper.isUserAdmin(Number(this.user!.id), this.federation!); + if (!isUserAdmin) { + this.context.message.reply(Lang.get("fedBanOnlyAdminError")); + return; + } + + this.context.message.delete(); + let params = this.command!.getParams() || []; + + const replyToMessage = this.context.message.getReplyToMessage(); + if (replyToMessage) { + this.banByReply(replyToMessage!, params.join(" ").trim()); + return; + } + + const mentions = await this.context.message.getMentions(); + if (mentions.length) { + params = params.filter((param) => param.indexOf("@") !== 0); + mentions.forEach((mention) => { + this.banByMention(mention, params.join(" ").trim()); + }); + } + + const userId = parseInt(params[0]); + if (userId === Number(params[0])) { + params.shift(); + this.banByUserId(userId, params.join(" ").trim()); + } + } + + /** + * Bans an user by message reply. + * + * @author Marcos Leandro + * @since 2023-06-07 + * + * @return void + */ + private async banByReply(replyToMessage: Message, reason: string): Promise { + + const user = UserHelper.getByTelegramId(replyToMessage.getUser().getId()); + const federationChats = await FederationHelper.getChats(this.federation!); + + for (const chat of federationChats) { + const context = this.getContext(user, chat); + this.saveBan(context, reason); + context.user.ban(); + } + } + + /** + * Bans an user by mention reply. + * + * @author Marcos Leandro + * @since 2023-06-07 + * + * @return void + */ + private async banByMention(mention: User, reason: string): Promise { + + const user = await UserHelper.getByTelegramId(mention.getId()); + const federationChats = await FederationHelper.getChats(this.federation!); + + for (const chat of federationChats) { + const context = this.getContext(user, chat); + this.saveBan(context, reason); + context.user.ban(); + } + } + + /** + * Bans the user by Telegram ID. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param userId + * @param reason + * + * @return {Promise|undefined>} + */ + private async banByUserId(userId: number, reason: string): Promise { + + const user = await UserHelper.getByTelegramId(userId); + const federationChats = await FederationHelper.getChats(this.federation!); + + for (const chat of federationChats) { + const context = this.getContext(user, chat); + this.saveBan(context, reason); + context.user.ban(); + } + } + + /** + * Saves the ban. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param context + * @param reason + * + * @return void + */ + private async saveBan(context: Context, reason: string): Promise { + + const user = await UserHelper.getByTelegramId(context.user.getId()); + const chat = await ChatHelper.getByTelegramId(context.chat.getId()); + + if (!user || !chat) { + return; + } + + Lang.set(chat.language || "us"); + + const ban = new Bans(); + const insert = ban.insert(); + insert + .set("user_id", user.id) + .set("chat_id", chat.id) + .set("federation_id", chat.federation_id) + .set("date", Math.floor(Date.now() / 1000)); + + if (reason.length) { + insert.set("reason", reason); + } + + try { + + await ban.execute(); + const message = Lang.get("fedBannedMessage") + .replace("{userId}", context.user.getId()) + .replace("{username}", context.user.getFirstName() || context.user.getUsername()) + .replace("{reason}", reason.length ? reason : "Unknown"); + + this.context.chat.sendMessage(message, { parseMode: "HTML" }); + + } catch (err: any) { + Log.error(err.toString()); + } + } +} diff --git a/src/command/federation/Federation.ts b/src/command/federation/Federation.ts new file mode 100644 index 0000000..634e1e9 --- /dev/null +++ b/src/command/federation/Federation.ts @@ -0,0 +1,141 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import ChatHelper from "../../helper/Chat.js"; +import Command from "../Command.js"; +import Context from "../../library/telegram/context/Context.js"; +import CommandContext from "../../library/telegram/context/Command.js"; +import Lang from "../../helper/Lang.js"; +import UserHelper from "../../helper/User.js"; +import FederationHelper from "../../helper/Federation.js"; +import { Message as MessageType } from "../../library/telegram/type/Message.js"; +import { Chat as ChatType } from "../../library/telegram/type/Chat.js"; +import { User as UserType } from "../../library/telegram/type/User.js"; + +export default class Federation extends Command { + + /** + * User object. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + protected user?: Record; + + /** + * Chat object. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + protected chat?: Record; + + /** + * Federation object. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + protected federation?: Record; + + /** + * Command context. + * + * @author Marcos Leandro + * @since 2023-06-13 + */ + protected command?: CommandContext; + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param app App instance. + */ + public constructor(context: Context) { + super(context); + } + + /** + * Runs the command. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param command + */ + public async run(command: CommandContext): Promise { + + this.user = await UserHelper.getByTelegramId(this.context.user.getId()); + this.chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); + + if (!this.user?.id || !this.chat?.id) { + return; + } + + if (!this.chat?.federation_id) { + return; + } + + this.federation = await FederationHelper.getById(Number(this.chat?.federation_id)); + if (!this.federation) { + return; + } + + Lang.set(this.chat!.language || "us"); + + this.command = command; + const action = this.command!.getCommand().substring(1); + this[action as keyof typeof Federation.prototype](true as never); + } + + /** + * Creates a context. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param user + * @param chat + * + * @return {Message} + */ + protected getContext(user: Record, chat: Record): Context { + + const userType: UserType = { + id: user.user_id, + isBot: user.is_bot, + firstName: user.first_name, + lastName: user.last_name, + username: user.username + }; + + const chatType: ChatType = { + id: chat.chat_id, + type: chat.type, + title: chat.title, + username: chat.username, + firstName: chat.first_name, + lastName: chat.last_name + }; + + const messageType: MessageType = { + messageId: 0, + from: userType, + chat: chatType, + date: Math.floor(Date.now() / 1000) + }; + + return new Context({ message: messageType }); + } +} diff --git a/src/command/federation/Group.ts b/src/command/federation/Group.ts new file mode 100644 index 0000000..309bba1 --- /dev/null +++ b/src/command/federation/Group.ts @@ -0,0 +1,137 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import Federation from "./Federation.js"; +import Chats from "../../model/Chats.js"; +import Context from "../../library/telegram/context/Context.js"; +import FederationsHelper from "../../helper/Federation.js"; +import Lang from "../../helper/Lang.js"; +import Log from "../../helper/Log.js"; + +export default class Group extends Federation { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param app App instance. + */ + public constructor(context: Context) { + super(context); + this.setCommands(["fjoin", "fleave"]); + } + + /** + * Joins a federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private async join(): Promise { + + if (this.context.chat.getType() === "private") { + this.context.message.reply(Lang.get("federationCommandOnlyGroupError")); + return; + } + + if (!await this.context.user.isAdmin()) { + this.context.message.reply(Lang.get("federationJoinOnlyAdminError")); + return; + } + + const params = this.command?.getParams() || []; + if (!params.length) { + this.context.message.reply(Lang.get("federationJoinNoHashError")); + return; + } + + const hash = params[0].trim(); + const federation = await FederationsHelper.getByHash(hash); + + if (!federation) { + this.context.message.reply(Lang.get("federationInvalidHashError")); + return; + } + + if (this.chat?.federation_id?.length) { + this.context.message.reply(Lang.get("federationJoinHasFederationError")); + return; + } + + if (this.chat?.federation_id === federation.id) { + this.context.message.reply(Lang.get("federationJoinAlreadyJoinedError")); + return; + } + + const chat = new Chats(); + chat + .update() + .set("federation_id", federation.id) + .where("id").equal(this.chat!.id); + + try { + + chat.execute(); + const message = Lang.get("federationJoinSuccess") + .replace("{federation}", federation.description); + + this.context.message.reply(message); + + } catch (err: any) { + this.context.message.reply(Lang.get("federationJoinError")); + Log.error(err.toString()); + return; + } + } + + /** + * Leaves a federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private async leave(): Promise { + + if (this.context.chat.getType() === "private") { + this.context.message.reply(Lang.get("federationCommandOnlyGroupError")); + return; + } + + if (!await this.context.user.isAdmin()) { + this.context.message.reply(Lang.get("federationJoinOnlyAdminError")); + return; + } + + if (!this.chat?.federation_id) { + this.context.message.reply(Lang.get("federationLeaveNoFederationError")); + return; + } + + const chat = new Chats(); + chat + .update() + .set("federation_id", null) + .where("id").equal(this.chat!.id); + + try { + + chat.execute(); + this.context.message.reply(Lang.get("federationLeaveSuccess")); + + } catch (err: any) { + this.context.message.reply(Lang.get("federationLeaveError")); + Log.error(err.toString()); + return; + } + } +} diff --git a/src/command/federation/Manage.ts b/src/command/federation/Manage.ts new file mode 100644 index 0000000..b394923 --- /dev/null +++ b/src/command/federation/Manage.ts @@ -0,0 +1,317 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import Federation from "./Federation.js"; +import ChatHelper from "../../helper/Chat.js"; +import Chats from "../../model/Chats.js"; +import Context from "../../library/telegram/context/Context.js"; +import CommandContext from "../../library/telegram/context/Command.js"; +import FederationHelper from "../../helper/Federation.js"; +import Federations from "../../model/Federations.js"; +import Lang from "../../helper/Lang.js"; +import Log from "../../helper/Log.js"; +import Text from "../../helper/Text.js"; +import UserHelper from "../../helper/User.js"; + +export default class Manage extends Federation { + + /** + * User object. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + protected user?: Record; + + /** + * Chat object. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + protected chat?: Record; + + /** + * Command context. + * + * @author Marcos Leandro + * @since 2023-06-13 + */ + protected command?: CommandContext; + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param app App instance. + */ + public constructor(context: Context) { + super(context); + this.setCommands(["fcreate", "flist", "fdelete"]); + } + + /** + * Runs the command. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param command + */ + public async run(command: CommandContext): Promise { + + this.user = await UserHelper.getByTelegramId(this.context.user.getId()); + this.chat = await ChatHelper.getByTelegramId(this.context.chat.getId()); + + if (!this.user?.id || !this.chat?.id) { + return; + } + + Lang.set(this.chat!.language || "us"); + + this.command = command; + const action = this.command.getCommand().substring(1); + this[action as keyof typeof Federation.prototype](true as never); + } + + /** + * Creates a new federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private async create(): Promise { + + if (this.context.chat.getType() !== "private") { + this.context.message.reply(Lang.get("federationCreateOnlyPrivate")); + return; + } + + const params = this.command?.getParams() || []; + const description = params.join(" ").trim(); + + const federationHash = await this.generateFederationHash(); + const federations = new Federations(); + const insert = federations.insert(); + + insert + .set("user_id", this.user!.id) + .set("hash", federationHash); + + if (description.length) { + insert.set("description", description); + } + + try { + + const result = await federations.execute(); + if (!result) { + this.context.message.reply(Lang.get("federationCreateError")); + return; + } + + const message = Lang.get("federationCreateSuccess") + .replace(/{name}/g, description.length ? description : federationHash) + .replace(/{hash}/g, federationHash); + + this.context.message.reply(message, { parseMode: "HTML" }); + + } catch (err: any) { + this.context.message.reply(Lang.get("federationCreateError")); + Log.error(err.toString()); + } + } + + /** + * Lists the user's federations. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private async list(): Promise { + + if (this.context.chat.getType() !== "private") { + this.context.message.reply(Lang.get("federationCommandOnlyPrivateError")); + return; + } + + const federations = new Federations(); + federations + .select(["id", "hash", "description"]) + .where("user_id").equal(this.user!.id) + .orderBy("description", "asc"); + + const result = await federations.execute(); + if (!result.length) { + this.context.message.reply(Lang.get("federationListEmpty")); + return; + } + + let message = Lang.get("federationListHeader"); + for (const federation of result) { + message += Lang.get("federationListRow") + .replace("{hash}", federation.hash) + .replace("{description}", federation.description) + .replace("{groups}", (await this.countGroups(federation.id)).toString()); + } + + this.context.message.reply(message, { parseMode: "HTML" }); + } + + /** + * Deletes a federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + private async delete(): Promise { + + if (this.context.chat.getType() !== "private") { + this.context.message.reply(Lang.get("federationCommandOnlyPrivateError")); + return; + } + + const params = this.command?.getParams() || []; + if (!params.length) { + this.context.message.reply(Lang.get("federationDeleteNoHashError")); + return; + } + + const hash = params[0].trim(); + const federation = await FederationHelper.getByHash(hash); + if (!federation) { + this.context.message.reply(Lang.get("federationInvalidHashError")); + return; + } + + if (federation.user_id !== this.user!.id) { + this.context.message.reply(Lang.get("federationNotOwnerError")); + return; + } + + const groups = await this.countGroups(federation.id); + if (groups === 0 || (!!params[1] && params[1] === "force")) { + await this.deleteFederation(federation.id); + return; + } + + const message = Lang.get("federationDeleteConfirm") + .replace("{name}", federation.description) + .replace("{hash}", federation.hash) + .replace("{groups}", groups.toString()); + + this.context.message.reply(message, { parseMode: "HTML" }); + } + + /** + * Returns the federation hash. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @returns {string} + */ + private async generateFederationHash(): Promise { + + let federationHash; + let federationHashExists: boolean; + + do { + + federationHash = Text.generateRandomString(32); + + const federations = new Federations(); + federations + .select(["id"]) + .where("hash").equal(federationHash) + .offset(0) + .limit(1); + + const result = await federations.execute(); + federationHashExists = result.length > 0; + + } while (federationHashExists); + + return federationHash; + } + + /** + * Counts the number of groups in a federation. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param federationId + * + * @return {Promise} + */ + private async countGroups(federationId: number): Promise { + + const chats = new Chats(); + chats + .select(["count(id) total"]) + .where("federation_id").equal(federationId) + .offset(0) + .limit(1); + + try { + + const result = await chats.execute(); + return parseInt(result[0].total); + + } catch (err: any) { + Log.error(err.toString(), true); + return 0; + } + } + + /** + * Deletes the federation. + * + * @author Marcos Leandro + * @since 2023-07-02 + * + * @param {number} federationId + */ + private async deleteFederation(federationId: number): Promise { + + const chats = new Chats(); + chats + .update() + .set("federation_id", null) + .where("federation_id").equal(federationId); + + try { + await chats.execute(); + + } catch (err: any) { + this.context.message.reply(Lang.get("federationDeleteError")); + Log.error(err.toString(), true); + return; + } + + const federations = new Federations(); + federations + .delete() + .where("id").equal(federationId); + + try { + await federations.execute(); + this.context.message.reply(Lang.get("federationDeleteSuccess")); + + } catch (err: any) { + this.context.message.reply(Lang.get("federationDeleteError")); + Log.error(err.toString(), true); + } + } +} diff --git a/src/command/federation/User.ts b/src/command/federation/User.ts new file mode 100644 index 0000000..039ba3b --- /dev/null +++ b/src/command/federation/User.ts @@ -0,0 +1,38 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import Federation from "./Federation.js"; +import Context from "../../library/telegram/context/Context.js"; + +export default class User extends Federation { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param app App instance. + */ + public constructor(context: Context) { + super(context); + this.setCommands([ + "fpromote", + "fdemote", + ]); + } + + private async promote(): Promise { + } + + private async demote(): Promise { + } +} diff --git a/src/config/commands.ts b/src/config/commands.ts index 678ef89..2ca601c 100644 --- a/src/config/commands.ts +++ b/src/config/commands.ts @@ -12,6 +12,10 @@ import AdaShield from "../command/AdaShield.js"; import Ask from "../command/Ask.js"; import Ban from "../command/Ban.js"; +import FederationBan from "../command/federation/Ban.js"; +import FederationGroup from "../command/federation/Group.js"; +import FederationManage from "../command/federation/Manage.js"; +import FederationUser from "../command/federation/User.js"; import Greetings from "../command/Greetings.js"; import Kick from "../command/Kick.js"; import Npm from "../command/Npm.js"; @@ -27,6 +31,10 @@ export const commands = [ AdaShield, Ask, Ban, + FederationBan, + FederationGroup, + FederationManage, + FederationUser, Greetings, Kick, Npm, diff --git a/src/helper/Chat.ts b/src/helper/Chat.ts index 218c731..164b2eb 100644 --- a/src/helper/Chat.ts +++ b/src/helper/Chat.ts @@ -15,16 +15,54 @@ import ChatConfigs from "../model/ChatConfigs.js"; export default class ChatHelper { /** - * Returns the user by the Telegram ID. + * Returns the chat by it's ID. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param id + * + * @return {Promise|undefined>} + */ + public static async getById(id: number): Promise|undefined> { + + const fields = [ + "chats.*", + "chat_configs.greetings", + "chat_configs.goodbye", + "chat_configs.warn_name_changing", + "chat_configs.remove_event_messages", + "chat_configs.restrict_new_users", + "chat_configs.captcha", + "chat_configs.warn_ask_to_ask", + "chat_configs.adashield" + ]; + + const chats = new Chats(); + chats + .select(fields) + .leftOuterJoin("chat_configs", "chat_configs.chat_id = chats.id") + .where("chats.id").equal(id); + + const chat = await chats.execute(); + if (chat.length) { + return chat[0]; + } + + return undefined; + } + + /** + * Returns the chat by the Telegram ID. * * @author Marcos Leandro * @since 1.0.0 * * @param chatId * - * @returns {Promise} + * @return {Promise|undefined>} */ - public static async getChatByTelegramId(chatId: number): Promise { + public static async getByTelegramId(chatId: number): Promise|undefined> { const fields = [ "chats.*", @@ -49,7 +87,7 @@ export default class ChatHelper { return chat[0]; } - return null; + return undefined; } /** @@ -96,7 +134,7 @@ export default class ChatHelper { */ public static async updateChat(chat: Record): Promise { - const row = await ChatHelper.getChatByTelegramId(chat.getId()); + const row = await ChatHelper.getByTelegramId(chat.getId()); if (!row) { return; } diff --git a/src/helper/Federation.ts b/src/helper/Federation.ts new file mode 100644 index 0000000..1469118 --- /dev/null +++ b/src/helper/Federation.ts @@ -0,0 +1,120 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import Chats from "../model/Chats.js"; +import Federations from "../model/Federations.js"; +import RelUsersFederations from "../model/RelUsersFederations.js"; + +export default class FederationHelper { + + /** + * Returns the federation by it's ID. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param {number} id + * + * @return {Promise>} + */ + public static async getById(id: number): Promise|undefined> { + + const federations = new Federations(); + federations + .select() + .where("id").equal(id); + + const federation = await federations.execute(); + if (federation.length) { + return federation[0]; + } + + return undefined; + } + + /** + * Returns the federation by it's hash. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param {string} hash + * + * @return {Promise>} + */ + public static async getByHash(hash: string): Promise|undefined> { + + const federations = new Federations(); + federations + .select() + .where("hash").equal(hash); + + const federation = await federations.execute(); + if (federation.length) { + return federation[0]; + } + + return undefined; + } + + /** + * Returns the federation chats. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param {Record} federation + */ + public static async getChats(federation: Record): Promise[]> { + + const chats = new Chats(); + chats + .select() + .where("federation_id").equal(federation.id); + + return await chats.execute(); + } + + /** + * Returns whether the user is an admin in the federation or not. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param userId + * @param federation + * + * @return {Promise} + */ + public static async isUserAdmin(userId: number, federation: Record): Promise { + + if (userId === Number(federation.user_id)) { + return Promise.resolve(true); + } + + const federationAdmins = new RelUsersFederations(); + federationAdmins + .select(["user_id"]) + .where("federation_id").equal(federation.id); + + const admins = await federationAdmins.execute(); + if (!admins.length) { + return Promise.resolve(false); + } + + let isAdmin = false; + for (const admin of admins) { + isAdmin = userId === Number(admin.user_id) ? true : isAdmin; + } + + return Promise.resolve(isAdmin); + } +} diff --git a/src/helper/Text.ts b/src/helper/Text.ts index ca9c2a2..391a0a4 100644 --- a/src/helper/Text.ts +++ b/src/helper/Text.ts @@ -41,4 +41,25 @@ export default class Text { public static markdownEscape(text: string): string { return text.replace(/[_*[\]()~`>#+-=|{}.!]/g, "\\$&"); } + + /** + * Generates a random string. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @param {number} length + * + * @return {string} + */ + public static generateRandomString(length: number): string { + const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + + return result; + } } diff --git a/src/helper/User.ts b/src/helper/User.ts index b0823c9..9dcf9d7 100644 --- a/src/helper/User.ts +++ b/src/helper/User.ts @@ -25,7 +25,7 @@ export default class UserHelper { * * @returns {Promise} */ - public static async getUserByTelegramId(userId: number): Promise { + public static async getByTelegramId(userId: number): Promise { const users = new Users(); @@ -116,7 +116,7 @@ export default class UserHelper { */ public static async updateUser(user: User): Promise { - const row = await UserHelper.getUserByTelegramId(user.getId()); + const row = await UserHelper.getByTelegramId(user.getId()); if (!row) { return; } diff --git a/src/lang/br.ts b/src/lang/br.ts index 22e68c8..8378578 100644 --- a/src/lang/br.ts +++ b/src/lang/br.ts @@ -10,52 +10,79 @@ */ export default { - startMessage : "Hey! My name is Ada Lovelace. I used to be a programmer. The first programmer in the history, in fact.\n\nNow I'm here to help you get around and keep the order in your groups.\n\nI have lots of features, such as greetings, a warning system, a flood control system and even more!\n\n", - startButton : "Adicione-me ao seu grupo", - helpButton : "Ajuda", - defaultGreetings : "Olá {username}, te desejo as boas-vindas ao grupo! Em caso de dúvidas, por favor contate um administrador.", - greetingsStatus : "Status das saudações: {status}", - warnNameChanging : "{oldname} mudou seu nome para {newname}", - unauthorizedCommand : "{username}, você não está autorizado a enviar este comando aqui.\nEste incidente será reportado.", - unauthorizedCommandReport : "O usuário {username} está tentando enviar um comando não autorizado em {chatname}:\n
{content}
", - groupStartMessage : "Olá {username}! Me chame no privado se precisar de ajuda.", - askToAskRegex : /^(algu[eé]?m|preciso)\s(tem|a[kqu]+[ií]\s)?([doesnt]+ grupo\s)?(.*)?(ajuda|duvida|sabe|manja|entende|conhe[cs]|usa|help|m[aã]o|for[cç]a|me[xch]+e)(.*)\??/i, - askToAskLink : "https://dontasktoask.com/pt-br", - adaShieldMessage : "{username} banido. Motivo: AdaShield.", - casMessage : "{username} banido. Motivo: CAS.", - adaShieldStatus : "Status do AdaShield: {status}", - restrictStatus : "Status da restrição de novos usuários: {status}", - textEnabled : "ativado", - textDisabled : "desativado", - captchaButton : "Pressione aqui para confirmar que não é um robô", - emptyGreetingsMessage : "Não existe uma mensagem de boas-vindas configurada.", - greetingsMessageDemo : "A mensagem de saudação atual é:\n\n{greetings}", - pongMessage : "Esta sou eu!", - packageName : "📜 {name}", - packageVersion : "📂 {version}", - packageSize : "🗂️ {size}", - packageDescription : "📝 {description}", - packageDate : "📆 {date}", - packageLinks : "🔗 Links:", - packageLink : " • {linkname}", - packageHomepage : "Página Inicial", - packageRepository : "🔗 Repository:", - packageAuthor : "👤 Autor:", - packagePublisher : "👤 Publicação:", - packageMaintainers : "👥 Mantenedores:", - packagePerson : " • {person}", - packageKeywords : "🏷 Palavras-Chave:", - packageDependencies : "🖇 Dependências:", - packageDevDependencies : "🖇 Dependências Dev:", - npmPackageInstall : "⌨️ Instalação:\nnpm install {package}", - yarnPackageInstall : "⌨️ Instalação:\nyarn add {package}", - playgroundLink : "🧪 Experimentos:\nhttps://npm.runkit.com/{package}", - selfReportMessage : "Por que eu me reportaria?", - adminReportMessage : "Por que eu reportaria um administrador?", - selfWarnMessage : "Por que eu me daria advertência?", - adminWarnMessage : "Por que eu daria advertência em um administrador?", - warningSigleMessage : "⚠️ {username} tem {warns} advertências.\n\nMotivo:\n", - warningPluralMessage : "⚠️ {username} tem {warns} advertências.\n\nMotivos:\n", - warningBanMessage : "❌ {username} levou ban por ter {warns} advertências.\n\nMotivos:\n", - reportMessage: "Reportado aos administradores." + startMessage: "Hey! My name is Ada Lovelace. I used to be a programmer. The first programmer in the history, in fact.\n\nNow I'm here to help you get around and keep the order in your groups.\n\nI have lots of features, such as greetings, a warning system, a flood control system and even more!\n\n", + startButton: "Adicione-me ao seu grupo", + helpButton: "Ajuda", + defaultGreetings: "Olá {username}, te desejo as boas-vindas ao grupo! Em caso de dúvidas, por favor contate um administrador.", + greetingsStatus: "Status das saudações: {status}", + warnNameChanging: "{oldname} mudou seu nome para {newname}", + unauthorizedCommand: "{username}, você não está autorizado a enviar este comando aqui.\nEste incidente será reportado.", + unauthorizedCommandReport: "O usuário {username} está tentando enviar um comando não autorizado em {chatname}:\n
{content}
", + groupStartMessage: "Olá {username}! Me chame no privado se precisar de ajuda.", + askToAskRegex: /^(algu[eé]?m|preciso)\s(tem|a[kqu]+[ií]\s)?([doesnt]+ grupo\s)?(.*)?(ajuda|duvida|sabe|manja|entende|conhe[cs]|usa|help|m[aã]o|for[cç]a|me[xch]+e)(.*)\??/i, + askToAskLink: "https://dontasktoask.com/pt-br", + bannedMessage: "{username} banido.\nMotivo: {reason}", + adaShieldMessage: "{username} banido.\nMotivo: AdaShield.", + casMessage: "{username} banido.\nMotivo: CAS.", + adaShieldStatus: "Status do AdaShield: {status}", + restrictStatus: "Status da restrição de novos usuários: {status}", + textEnabled: "ativado", + textDisabled: "desativado", + captchaButton: "Pressione aqui para confirmar que não é um robô", + emptyGreetingsMessage: "Não existe uma mensagem de boas-vindas configurada.", + greetingsMessageDemo: "A mensagem de saudação atual é:\n\n{greetings}", + pongMessage: "Esta sou eu!", + packageName: "📜 {name}", + packageVersion: "📂 {version}", + packageSize: "🗂️ {size}", + packageDescription: "📝 {description}", + packageDate: "📆 {date}", + packageLinks: "🔗 Links:", + packageLink: " • {linkname}", + packageHomepage: "Página Inicial", + packageRepository: "🔗 Repository:", + packageAuthor: "👤 Autor:", + packagePublisher: "👤 Publicação:", + packageMaintainers: "👥 Mantenedores:", + packagePerson: " • {person}", + packageKeywords: "🏷 Palavras-Chave:", + packageDependencies: "🖇 Dependências:", + packageDevDependencies: "🖇 Dependências Dev:", + npmPackageInstall: "⌨️ Instalação:\nnpm install {package}", + yarnPackageInstall: "⌨️ Instalação:\nyarn add {package}", + playgroundLink: "🧪 Experimentos:\nhttps://npm.runkit.com/{package}", + selfReportMessage: "Por que eu me reportaria?", + adminReportMessage: "Por que eu reportaria um administrador?", + selfWarnMessage: "Por que eu me daria advertência?", + adminWarnMessage: "Por que eu daria advertência em um administrador?", + warningSigleMessage: "⚠️ {username} tem {warns} advertências.\n\nMotivo:\n", + warningPluralMessage: "⚠️ {username} tem {warns} advertências.\n\nMotivos:\n", + warningBanMessage: "❌ {username} levou ban por ter {warns} advertências.\n\nMotivos:\n", + reportMessage: "Reportado aos administradores.", + federationCreateOnlyPrivate: "Me chame no privado pra criar uma federação.", + federationCreateSuccess: "Federação {name} criada com sucesso!\nVocê já pode adicionar grupos usando o comando /fjoin {hash}", + federationCreateError: "Ocorreu um erro ao criar a federação. Por favor, tente novamente mais tarde", + federationJoinOnlyAdminError: "Somente administradores podem adicionar este grupo a uma federação.", + federationJoinHasFederationError: "Este grupo já faz parte de uma federação. Para ingressar em outra, use o comando /fleave para sair da atual primeiro.", + federationJoinNoHashError: "Você precisa informar o hash da federação que deseja ingressar.", + federationJoinAlreadyJoinedError: "Este grupo já faz parte desta federação.", + federationJoinError: "Ocorreu um erro ao ingressar na federação. Por favor, tente novamente mais tarde.", + federationJoinSuccess: "Grupo adicionado à federação {federation} com sucesso!", + federationLeaveNoFederationError: "Este grupo não faz parte de nenhuma federação.", + federationLeaveError: "Ocorreu um erro ao sair da federação. Por favor, tente novamente mais tarde.", + federationLeaveSuccess: "Grupo removido da federação com sucesso!", + federationCommandOnlyGroupError: "Este comando só pode ser executado em grupos.", + federationCommandOnlyPrivateError: "Este comando só pode ser usado no privado.", + federationListEmpty: "Você ainda não criou uma federação.", + federationListHeader: "Suas federações:\n\n", + federationListRow: " • {hash} - {description} ({groups} grupos)\n", + federationDeleteNoHashError: "Você precisa informar o hash da federação que deseja excluir.", + federationInvalidHashError: "O hash informado não é válido.", + federationNotOwnerError: "Você não é o dono desta federação.", + federationDeleteConfirm: "A federação {name} possui {groups} grupos vinculados.\nPara excluir esta federação, envie force como segundo parâmetro.\n\nEx.: /fdelete {hash} force", + federationDeleteError: "Ocorreu um erro ao excluir a federação. Por favor, tente novamente mais tarde.", + federationDeleteSuccess: "Federação excluída com sucesso!", + fedBannedMessage: "{username} banido na federação.\nMotivo: {reason}", + fedBanOnlyAdminError: "Somente administradores podem banir usuários da federação.", + fedBanAdminError: "Você não pode banir administradores da federação.", }; diff --git a/src/lang/us.ts b/src/lang/us.ts index ac3937b..1547a55 100644 --- a/src/lang/us.ts +++ b/src/lang/us.ts @@ -10,52 +10,79 @@ */ export default { - startMessage : "Hey! My name is Ada Lovelace. I used to be a programmer. The first programmer in the history, in fact.\n\nNow I'm here to help you get around and keep the order in your groups.\n\nI have lots of features, such as greetings, a warning system, a flood control system and even more!\n\n", - startButton : "Add me to your group", - helpButton : "Help", - defaultGreetings : "Hey {username}, welcome to this group! If you have any questions, please contact an admin.", - greetingsStatus : "Greetings status: {status}", - warnNameChanging : "{oldname} changed their name to {newname}", - unauthorizedCommand : "{username}, you are not supposed to send this command here.\nThis incident will be reported.", - unauthorizedCommandReport : "The user {username} is trying to send an unauthorized command in {chatname}:\n
{content}
", - groupStartMessage : "Hey {username}! PM me if you want some help.", - askToAskRegex : /(Any)\s(.*)\s(expert\s|dev\s)?(can\s)?(here|help)(.*)\??/i, - askToAskLink : "https://dontasktoask.com", - adaShieldMessage : "{username} banned. Reason: AdaShield banned.", - casMessage : "{username} banned. Reason: CAS banned.", - adaShieldStatus : "AdaShield status: {status}", - restrictStatus : "New users restriction status: {status}", - textEnabled : "enabled", - textDisabled : "disabled", - captchaButton : "Press here to confirm you are not a robot", - emptyGreetingsMessage : "There is no greetings message configured.", - greetingsMessageDemo : "The current greetings message is:\n\n{greetings}", - pongMessage : "Hey! It's me!", - packageName : "📜 {name}", - packageVersion : "📂 {version}", - packageSize : "🗂️ {size}", - packageDescription : "📝 {description}", - packageDate : "📆 {date}", - packageLinks : "🔗 Links:", - packageLink : " • {linkname}", - packageHomepage : "Home Page", - packageRepository : "🔗 Repositório:", - packageAuthor : "👤 Author:", - packagePublisher : "👤 Publisher:", - packageMaintainers : "👥 Maintainers:", - packagePerson : " • {person}", - packageKeywords : "🏷 Keywords:", - packageDependencies : "🖇 Dependencies:", - packageDevDependencies : "🖇 Dev Dependencies:", - npmPackageInstall : "⌨️ Install:\nnpm install {package}", - yarnPackageInstall : "⌨️ Install:\nyarn add {package}", - playgroundLink : "🧪 Playground:\nhttps://npm.runkit.com/{package}", - selfReportMessage : "Why would I report myself?", - adminReportMessage : "Why would I report an admin?", - selfWarnMessage : "Why would I warn myself?", - adminWarnMessage : "Why would I warn an admin?", - warningSigleMessage : "⚠️ {username} has {warns} warnings.\n\nReason:\n", - warningPluralMessage : "⚠️ {username} has {warns} warnings.\n\nReasons:\n", - warningBanMessage : "❌ {username} has {warns} warnings and has been banned.\n\nReasons:\n", - reportMessage: "Reported to the admins." + startMessage: "Hey! My name is Ada Lovelace. I used to be a programmer. The first programmer in the history, in fact.\n\nNow I'm here to help you get around and keep the order in your groups.\n\nI have lots of features, such as greetings, a warning system, a flood control system and even more!\n\n", + startButton: "Add me to your group", + helpButton: "Help", + defaultGreetings: "Hey {username}, welcome to this group! If you have any questions, please contact an admin.", + greetingsStatus: "Greetings status: {status}", + warnNameChanging: "{oldname} changed their name to {newname}", + unauthorizedCommand: "{username}, you are not supposed to send this command here.\nThis incident will be reported.", + unauthorizedCommandReport: "The user {username} is trying to send an unauthorized command in {chatname}:\n
{content}
", + groupStartMessage: "Hey {username}! PM me if you want some help.", + askToAskRegex: /(Any)\s(.*)\s(expert\s|dev\s)?(can\s)?(here|help)(.*)\??/i, + askToAskLink: "https://dontasktoask.com", + bannedMessage: "{username} banned.\nReason: {reason}", + adaShieldMessage: "{username} banned.\nReason: AdaShield banned.", + casMessage: "{username} banned.\nReason: CAS banned.", + adaShieldStatus: "AdaShield status: {status}", + restrictStatus: "New users restriction status: {status}", + textEnabled: "enabled", + textDisabled: "disabled", + captchaButton: "Press here to confirm you are not a robot", + emptyGreetingsMessage: "There is no greetings message configured.", + greetingsMessageDemo: "The current greetings message is:\n\n{greetings}", + pongMessage: "Hey! It's me!", + packageName: "📜 {name}", + packageVersion: "📂 {version}", + packageSize: "🗂️ {size}", + packageDescription: "📝 {description}", + packageDate: "📆 {date}", + packageLinks: "🔗 Links:", + packageLink: " • {linkname}", + packageHomepage: "Home Page", + packageRepository: "🔗 Repositório:", + packageAuthor: "👤 Author:", + packagePublisher: "👤 Publisher:", + packageMaintainers: "👥 Maintainers:", + packagePerson: " • {person}", + packageKeywords: "🏷 Keywords:", + packageDependencies: "🖇 Dependencies:", + packageDevDependencies: "🖇 Dev Dependencies:", + npmPackageInstall: "⌨️ Install:\nnpm install {package}", + yarnPackageInstall: "⌨️ Install:\nyarn add {package}", + playgroundLink: "🧪 Playground:\nhttps://npm.runkit.com/{package}", + selfReportMessage: "Why would I report myself?", + adminReportMessage: "Why would I report an admin?", + selfWarnMessage: "Why would I warn myself?", + adminWarnMessage: "Why would I warn an admin?", + warningSigleMessage: "⚠️ {username} has {warns} warnings.\n\nReason:\n", + warningPluralMessage: "⚠️ {username} has {warns} warnings.\n\nReasons:\n", + warningBanMessage: "❌ {username} has {warns} warnings and has been banned.\n\nReasons:\n", + reportMessage: "Reported to the admins.", + federationCreateOnlyPrivate: "PM me if you want to create a federation.", + federationCreateSuccess: "Federation {name} successfully created.\nYou can now add groups to your federation using the command /fjoin {hash}.", + federationCreateError: "An error occurred while creating the federation. Please try again later.", + federationJoinOnlyAdminError: "Only admins can add this group to a federation.", + federationJoinHasFederationError: "This group is already part of a federation. Please leave the federation with /fleave before joining another one.", + federationJoinNoHashError: "You must provide a federation hash to join.", + federationJoinAlreadyJoinedError: "This group is already part of this federation.", + federationJoinError: "An error occurred while joining the federation. Please try again later.", + federationJoinSuccess: "This group has been successfully added to the federation {federation}.", + federationLeaveNoFederationError: "This group is not part of any federation.", + federationLeaveError: "An error occurred while leaving the federation. Please try again later.", + federationLeaveSuccess: "This group has been successfully removed from the federation.", + federationCommandOnlyGroupError: "This command can only be used in groups.", + federationCommandOnlyPrivateError: "This command can only be used in private.", + federationListEmpty: "You have no federations.", + federationListHeader: "Your federations:\n\n", + federationListRow: " • {hash} - {description} ({groups} groups)\n", + federationDeleteNoHashError: "You must provide a federation hash to delete.", + federationInvalidHashError: "The provided hash is invalid.", + federationNotOwnerError: "You are not the owner of this federation.", + federationDeleteConfirm: "The federation {name} has {groups} attached groups.\nTo delete it, send force as the second parameter.\n\n/fdelete {hash} force", + federationDeleteError: "An error occurred while deleting the federation. Please try again later.", + federationDeleteSuccess: "The federation has been successfully deleted.", + fedBannedMessage: "{username} banned in federation.\nReason: {reason}", + fedBanOnlyAdminError: "Only admins can ban users in a federation.", + fedBanAdminError: "You can't ban admins in a federation.", }; diff --git a/src/library/telegram/context/Message.ts b/src/library/telegram/context/Message.ts index 730dc57..3ab229e 100644 --- a/src/library/telegram/context/Message.ts +++ b/src/library/telegram/context/Message.ts @@ -62,7 +62,7 @@ export default class Message { * * @var {User[]} */ - private mentions: User[] = []; + private mentions?: User[]; /** * The message commands. diff --git a/src/model/Bans.ts b/src/model/Bans.ts new file mode 100644 index 0000000..6cca167 --- /dev/null +++ b/src/model/Bans.ts @@ -0,0 +1,25 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import DefaultModel from "./Model.js"; + +export default class Bans extends DefaultModel { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + public constructor() { + super("bans"); + } +} diff --git a/src/model/Federations.ts b/src/model/Federations.ts new file mode 100644 index 0000000..922b479 --- /dev/null +++ b/src/model/Federations.ts @@ -0,0 +1,25 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import DefaultModel from "./Model.js"; + +export default class Federations extends DefaultModel { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + public constructor() { + super("federations"); + } +} diff --git a/src/model/RelUsersFederations.ts b/src/model/RelUsersFederations.ts new file mode 100644 index 0000000..c9f6597 --- /dev/null +++ b/src/model/RelUsersFederations.ts @@ -0,0 +1,25 @@ +/** + * Ada Lovelace Telegram Bot + * + * This file is part of Ada Lovelace Telegram Bot. + * You are free to modify and share this project or its files. + * + * @package mslovelace_bot + * @author Marcos Leandro + * @license GPLv3 + */ + +import DefaultModel from "./Model.js"; + +export default class RelUsersFederations extends DefaultModel { + + /** + * The constructor. + * + * @author Marcos Leandro + * @since 2023-07-04 + */ + public constructor() { + super("rel_users_federations"); + } +} diff --git a/src/model/mysql/DB.ts b/src/model/mysql/DB.ts index f77f821..5578630 100644 --- a/src/model/mysql/DB.ts +++ b/src/model/mysql/DB.ts @@ -100,6 +100,16 @@ export default class DB { */ private operation?: Builder; + /** + * Last executed query. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @property {string} + */ + private lastQuery?: string; + /** * Active connection. * @@ -207,8 +217,8 @@ export default class DB { */ public execute(): any { if (this.operation) { - const query = this.operation.build(); - return this.query(query); + this.lastQuery = this.operation.build(); + return this.query(this.lastQuery); } return null; @@ -232,6 +242,18 @@ export default class DB { }); } + /** + * Returns the last query. + * + * @author Marcos Leandro + * @since 2023-07-04 + * + * @return {string} + */ + public getLastQuery(): string { + return this.lastQuery || ""; + } + /** * Returns the active connection. *