From b034dd2bdc153fbfd25d5a5a73abff61150beaf1 Mon Sep 17 00:00:00 2001 From: Grigoriy Date: Thu, 5 May 2022 22:15:13 +0500 Subject: [PATCH 1/5] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client_classes/DiscordClient.py | 19 ++ client_classes/Message.py | 27 +++ client_classes/TelegramClient.py | 29 +++ client_classes/VkClient.py | 48 +++++ error_log.txt => data/error_log.txt | 0 graph.txt => data/graph.txt | 0 data/users_inf.db | Bin 0 -> 28672 bytes main_classes/Graph.py | 84 ++++++++ main_classes/UsersHandler.py | 302 ++++++++++++++++++++++++++++ 9 files changed, 509 insertions(+) create mode 100644 client_classes/DiscordClient.py create mode 100644 client_classes/Message.py create mode 100644 client_classes/TelegramClient.py create mode 100644 client_classes/VkClient.py rename error_log.txt => data/error_log.txt (100%) rename graph.txt => data/graph.txt (100%) create mode 100644 data/users_inf.db create mode 100644 main_classes/Graph.py create mode 100644 main_classes/UsersHandler.py diff --git a/client_classes/DiscordClient.py b/client_classes/DiscordClient.py new file mode 100644 index 0000000..8b59058 --- /dev/null +++ b/client_classes/DiscordClient.py @@ -0,0 +1,19 @@ +import discord +from client_classes.Message import Message + + +class DiscordClient(discord.Client): + def __init__(self, compute_message_func): + super(DiscordClient, self).__init__() + self.compute_massage = compute_message_func + + async def on_message(self, message): + if message.author != self.user: + author_name = message.author.name + chat_name = None + if type(message.channel) != discord.channel.DMChannel: + chat_name = message.author.guild.name + "/" + message.channel.name + self.compute_massage(Message((message.channel.id, "DS"), message.content, message.author.id, author_name, chat_name)) + + def send_msg(self, id, text): + self.loop.create_task(self.get_channel(id).send(text)) diff --git a/client_classes/Message.py b/client_classes/Message.py new file mode 100644 index 0000000..2f01b68 --- /dev/null +++ b/client_classes/Message.py @@ -0,0 +1,27 @@ +class Message: + def __init__(self, from_id, text, author_id, author_name, chat_name): + self.from_id = from_id + self.text = text + self.author_id = author_id + self.author_name = author_name + self.chat_name = chat_name + + def is_chat_command(self): + return len(self.text) > 0 and self.text[0] == "!" + + def get_author_id(self): + return str(self.author_id) + self.from_id[1] + + def get_chat_command(self): + if not self.is_chat_command(): + return None + return self.text[1:].strip().lower() + + def get_user_command(self): + return self.text.split(" ") + + def get_text_to_forwarding(self): + description = "" + if self.author_name is not None: + description += self.author_name + ":\n" + return description + self.text diff --git a/client_classes/TelegramClient.py b/client_classes/TelegramClient.py new file mode 100644 index 0000000..5bf2e01 --- /dev/null +++ b/client_classes/TelegramClient.py @@ -0,0 +1,29 @@ +import threading +import telebot +from client_classes.Message import Message + + +class TelegramClient: + def __init__(self, compute_message_func): + self.client = None + self.handler_thread = None + self.compute_massage = compute_message_func + + def __handler(self): + @self.client.message_handler(content_types=["text"]) + def on_message(message): + author_name = message.from_user.last_name + " " + message.from_user.first_name + chat_name = None + if message.from_user.id != message.chat.id: + chat_name = message.chat.title + self.compute_massage(Message((message.chat.id, "TG"), message.text, message.from_user.id, author_name, chat_name)) + + self.client.infinity_polling() + + def send_msg(self, id, text): + self.client.send_message(id, text) + + def run(self, token): + self.client = telebot.TeleBot(token) + self.handler_thread = threading.Thread(target=self.__handler) + self.handler_thread.start() diff --git a/client_classes/VkClient.py b/client_classes/VkClient.py new file mode 100644 index 0000000..26b3c5c --- /dev/null +++ b/client_classes/VkClient.py @@ -0,0 +1,48 @@ +import threading +import vk_api +from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType +from client_classes.Message import Message + + +class VkClient: + def __init__(self, compute_message_func): + self.client = None + self.group_id = None + self.handler_thread = None + self.compute_massage = compute_message_func + + def __get_user(self, id): + return self.client.method("users.get", {"user_ids": id})[0] + + def __get_chat(self, id): + chat = self.client.method("messages.getConversationsById", {"peer_ids": id + 2000000000}) + if chat["count"] == 0: + return {"chat_settings": {"title": "NULL"}} + return chat["items"][0] + + def __handler(self): + longpoll = VkBotLongPoll(self.client, self.group_id) + + for event in longpoll.listen(): + if event.type == VkBotEventType.MESSAGE_NEW: + author = self.__get_user(event.object["message"]["from_id"]) + author_name = author["last_name"] + " " + author["first_name"] + chat_id = event.object["message"]["from_id"] + chat_name = None + if event.from_chat: + chat_id = event.chat_id + chat = self.__get_chat(event.chat_id) + chat_name = chat["chat_settings"]["title"] + self.compute_massage(Message((chat_id, "VK"), event.object["message"]["text"], event.object["message"]["from_id"], author_name, chat_name)) + + def send_msg(self, id, text, to_chat): + if to_chat: + self.client.method("messages.send", {"chat_id": id, "message": text, "random_id": 0}) + else: + self.client.method("messages.send", {"user_id": id, "message": text, "random_id": 0}) + + def run(self, token, group_id): + self.client = vk_api.VkApi(token=token) + self.group_id = group_id + self.handler_thread = threading.Thread(target=self.__handler) + self.handler_thread.start() diff --git a/error_log.txt b/data/error_log.txt similarity index 100% rename from error_log.txt rename to data/error_log.txt diff --git a/graph.txt b/data/graph.txt similarity index 100% rename from graph.txt rename to data/graph.txt diff --git a/data/users_inf.db b/data/users_inf.db new file mode 100644 index 0000000000000000000000000000000000000000..7c3bdf5f44186bba0f97cb5fb9190aa4fe96b200 GIT binary patch literal 28672 zcmeI(Z)?*)90%~b-j-&QHG4ARlR;n%HW-@AtNyCB;fx1wbpL!@u3LP1{k~z?Tg#O6`l&u1 zy*tSB+D?2l9+dB$NsJDkjH*wWVcRx+U+%D2epEIyQ+q3?*8T*a-qZAc*VktZ`+tuZ~~2Dt@_GhnHQE%bUe7Y9X)8 z`bezr*4Mm4fdB*`009U<00Izz00bZa0SG|g#tPVsNmE8r@IXYaA95+BFN4QTtHM@I z?t76ZJ?XlwCpF4eOyLKSkU`+S+$k>kne~ZS->e^bhXMfzKmY;|fB*y_009U<00Izz zz|9r7%Ow3<{?8WKD!rKhS88l!?n3@w-2YSSC&?ca2tWV=5P$##AOHafKmY;|fB*z; zmq3-WC3EjZqfy-d)BOMW`GW!h2tWV=5P$##AOHafKmY;|fWR#isIsNGBL6?T{~r=- zc*_om<3Ioc5P$##AOHafKmY;|fB*!pRp8_fHO!?C3$#eTwd?x62zU|+&l7Ri*Ipc| zRED0AS}5tIz7ky}c_L+!NFEDcCjobrOygK7%~ha%6{^I~lnBzmm6_07XUb1~PX2F?7wRl=RnU#x)Qh7a;YsLbipT8i{{NI%r`L*t;1GZS1Rwwb2tWV=5P$##AOHaf K{38Lz%(>sI7~LNL literal 0 HcmV?d00001 diff --git a/main_classes/Graph.py b/main_classes/Graph.py new file mode 100644 index 0000000..11e7a13 --- /dev/null +++ b/main_classes/Graph.py @@ -0,0 +1,84 @@ +import queue + + +class Graph: + def __init__(self, graph_storage_name): + graph_storage = open(graph_storage_name, "r") + self.graph_storage_name = graph_storage_name + self.adjacency_list = dict() + self.graph_storage_size = 0 + for line in graph_storage.readlines(): + type_operation, vertex1, vertex2 = self.__convert_text_to_operation(line) + + if type_operation == "+": + self.add_edge(vertex1, vertex2, save_operation=False) + else: + self.erase_edge(vertex1, vertex2, save_operation=False) + self.graph_storage_size += 1 + + def __convert_text_to_operation(self, text): + text = text.split() + return text[0], (int(text[1]), text[2]), (int(text[3]), text[4]) + + def __convert_operation_to_text(self, type_operation, vertex1, vertex2): + return type_operation + " " + str(vertex1[0]) + " " + vertex1[1] + " " + str(vertex2[0]) + " " + vertex2[1] + "\n" + + def __reset_graph_storage(self): + graph_storage = open(self.graph_storage_name, "w") + used = set() + self.graph_storage_size = 0 + for vertex1 in self.adjacency_list: + for vertex2 in self.adjacency_list[vertex1]: + if (vertex1, vertex2) in used or (vertex2, vertex1) in used: + continue + + used.add((vertex1, vertex2)) + graph_storage.write(self.__convert_operation_to_text("+", vertex1, vertex2)) + self.graph_storage_size += 1 + graph_storage.close() + + def __add_operation_to_storage(self, type_operation, vertex1, vertex2): + graph_storage = open(self.graph_storage_name, "a") + graph_storage.write(self.__convert_operation_to_text(type_operation, vertex1, vertex2)) + graph_storage.close() + + self.graph_storage_size += 1 + if self.graph_storage_size >= len(self.adjacency_list) ** 2: + self.__reset_graph_storage() + + def get_reachable_vertices(self, vertex_start): + used = set() + used.add(vertex_start) + q = queue.Queue() + q.put(vertex_start) + while not q.empty(): + v = q.get() + for to in self.adjacency_list[v]: + if to in used: + continue + + used.add(to) + q.put(to) + + used.discard(vertex_start) + return list(used) + + def add_vertex(self, vertex): + if not (vertex in self.adjacency_list): + self.adjacency_list[vertex] = set() + + def add_edge(self, vertex1, vertex2, save_operation=True): + self.add_vertex(vertex1) + self.add_vertex(vertex2) + self.adjacency_list[vertex1].add(vertex2) + self.adjacency_list[vertex2].add(vertex1) + if save_operation: + self.__add_operation_to_storage("+", vertex1, vertex2) + + def erase_edge(self, vertex1, vertex2, save_operation=True): + if vertex1 in self.adjacency_list: + self.adjacency_list[vertex1].discard(vertex2) + if vertex2 in self.adjacency_list: + self.adjacency_list[vertex2].discard(vertex1) + if save_operation: + self.__add_operation_to_storage("-", vertex1, vertex2) diff --git a/main_classes/UsersHandler.py b/main_classes/UsersHandler.py new file mode 100644 index 0000000..b6358cc --- /dev/null +++ b/main_classes/UsersHandler.py @@ -0,0 +1,302 @@ +import threading +import sqlite3 +import hashlib +import random +from main_classes.Graph import Graph +from client_classes.VkClient import VkClient +from client_classes.TelegramClient import TelegramClient +from client_classes.DiscordClient import DiscordClient + + +class UsersHandler: + def __init__(self, graph_storage_name, users_information_db_name, token_length=1, error_log_name=None): + self.error_log_name = error_log_name + self.graph = Graph(graph_storage_name) + self.select_chat = {"chat_name": None, "chat_id": (None, None)} + self.token_length = token_length + + self.vk_client = VkClient(self.compute_message) + self.discord_client = DiscordClient(self.compute_message) + self.telegram_client = TelegramClient(self.compute_message) + + self.users_information_db_name = users_information_db_name + conn = sqlite3.connect(users_information_db_name) + cursor = conn.cursor() + cursor.execute("CREATE TABLE IF NOT EXISTS users(user_id TEXT PRIMARY KEY, account_id INT);") + cursor.execute("CREATE TABLE IF NOT EXISTS names(name TEXT PRIMARY KEY, account_id INT);") + cursor.execute("CREATE TABLE IF NOT EXISTS accounts(account_id INT PRIMARY KEY, name TEXT, token TEXT, count INT);") + conn.commit() + + self.free_id = 0 + cursor.execute("SELECT * FROM accounts;") + for account in cursor.fetchall(): + self.free_id = max(self.free_id, account[0] + 1) + + def __create_token(self): + token = "" + for i in range(self.token_length): + cur = random.randint(0, 63) + if cur <= 9: + token += str(cur) + elif cur <= 35: + token += chr(ord("a") + cur - 10) + elif cur <= 61: + token += chr(ord("A") + cur - 36) + elif cur == 62: + token += "_" + else: + token += "-" + return token + + def __add_user(self, msg): + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + if cursor.fetchone() is None: + entry = (msg.get_author_id(), -1) + cursor.execute("INSERT INTO users VALUES(?, ?);", entry) + conn.commit() + + def __get_user_name(self, msg): + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + info = cursor.fetchone() + if info[1] == -1: + return None + cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(info[1]) + ";") + return cursor.fetchone()[1] + + def __is_login(self, msg): + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + return cursor.fetchone()[1] != -1 + + def __add_error_to_log(self, text): + if self.error_log_name is not None: + error_log = open(self.error_log_name, "a") + error_log.write(text + "\n\n") + error_log.close() + + def __send(self, id, text, to_chat=True): + try: + if id[1] == "VK": + self.vk_client.send_msg(id[0], text, to_chat) + elif id[1] == "DS": + self.discord_client.send_msg(id[0], text) + elif id[1] == "TG": + self.telegram_client.send_msg(id[0], text) + else: + self.__add_error_to_log("Error: Unknown system to send message.") + except Exception as error: + self.__add_error_to_log("Error: Unknown error while sending the message.\nDescription:\n" + str(error)) + + def __compute_command_select(self, msg): + self.select_chat["chat_id"] = msg.from_id + self.select_chat["chat_name"] = msg.chat_name + self.__send(msg.from_id, "Chat is selected.") + + def __compute_command_connect(self, msg): + select_id = self.select_chat["chat_id"] + if select_id == (None, None): + self.__send(msg.from_id, "Error: No selected chat.") + elif select_id == msg.from_id: + self.__send(msg.from_id, "Error: Attempting to connect a chat with itself.") + elif select_id in self.graph.adjacency_list[msg.from_id]: + self.__send(msg.from_id, "Error: Chats already connected.") + else: + self.graph.add_edge(msg.from_id, select_id) + self.__send(msg.from_id, select_id[1] + " chat with name " + self.select_chat["chat_name"] + " is connected.") + self.__send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is connected.") + + def __compute_command_disconnect(self, msg): + select_id = self.select_chat["chat_id"] + if select_id == (None, None): + self.__send(msg.from_id, "Error: No selected chat.") + elif not (select_id in self.graph.adjacency_list[msg.from_id]): + self.__send(msg.from_id, "Error: Chats are not connected.") + else: + self.graph.erase_edge(msg.from_id, select_id) + self.__send(msg.from_id, select_id[1] + " chat with name " + self.select_chat["chat_name"] + " is disconnected.") + self.__send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is disconnected.") + + def __compute_command_create(self, msg): + if self.__is_login(msg): + return self.__send(msg.from_id, "To create a new account, you must log out of your current account.", to_chat=False) + + command = msg.get_user_command() + if len(command) == 1: + return self.__send(msg.from_id, "Error: To create an account, you need to provide an account name.", to_chat=False) + name = " ".join(command[1:]) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM names WHERE name='" + name + "';") + if cursor.fetchone() is not None: + return self.__send(msg.from_id, "Error: An account with the name '" + name + "' already exists.", to_chat=False) + + cursor.execute("UPDATE users SET account_id=" + str(self.free_id) + " WHERE user_id='" + msg.get_author_id() + "';") + entry = (name, self.free_id) + cursor.execute("INSERT INTO names VALUES(?, ?);", entry) + entry = (self.free_id, name, "?", 1) + cursor.execute("INSERT INTO accounts VALUES(?, ?, ?, ?);", entry) + conn.commit() + self.free_id += 1 + self.__send(msg.from_id, "An account with the name '" + name + "' has been created.", to_chat=False) + + def __compute_command_login(self, msg): + if self.__is_login(msg): + return self.__send(msg.from_id, "To login, you must log out of your current account.", to_chat=False) + + command = msg.get_user_command() + if len(command) == 1: + return self.__send(msg.from_id, "Error: To enter an account, you need to provide a token.", to_chat=False) + if len(command) == 2: + return self.__send(msg.from_id, "Error: To enter an account, you need to provide an account name.", to_chat=False) + token = command[1] + name = " ".join(command[2:]) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM names WHERE name='" + name + "';") + names_info = cursor.fetchone() + if names_info is None: + return self.__send(msg.from_id, "Error: There is no account with the name '" + name + "'.", to_chat=False) + cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(names_info[1]) + ";") + account_info = cursor.fetchone() + if account_info[2] == "?" or account_info[2] != hashlib.sha512(token.encode()).hexdigest(): + return self.__send(msg.from_id, "Error: Incorrect token.", to_chat=False) + + cursor.execute("UPDATE users SET account_id=" + str(account_info[0]) + " WHERE user_id='" + msg.get_author_id() + "';") + cursor.execute("UPDATE accounts SET count=" + str(account_info[3] + 1) + " WHERE account_id=" + str(account_info[0]) + ";") + conn.commit() + self.__send(msg.from_id, "You are connected to the '" + name + "' account.", to_chat=False) + + def __compute_command_logout(self, msg): + if not self.__is_login(msg): + return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + user_info = cursor.fetchone() + cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(user_info[1]) + ";") + account_info = cursor.fetchone() + + cursor.execute("UPDATE users SET account_id=-1 WHERE user_id='" + msg.get_author_id() + "';") + cursor.execute("UPDATE accounts SET count=" + str(account_info[3] - 1) + " WHERE account_id=" + str(account_info[0]) + ";") + conn.commit() + self.__send(msg.from_id, "You are disconnected from the account '" + account_info[1] + "'.", to_chat=False) + if account_info[3] == 1: + cursor.execute("DELETE FROM accounts WHERE account_id=" + str(account_info[0]) + ";") + cursor.execute("DELETE FROM names WHERE name='" + str(account_info[1]) + "';") + conn.commit() + self.__send(msg.from_id, "Account '" + account_info[1] + "' has been deleted.", to_chat=False) + + def __compute_command_rename(self, msg): + if not self.__is_login(msg): + return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + + command = msg.get_user_command() + if len(command) == 1: + return self.__send(msg.from_id, "Error: To rename an account, you need to provide an new account name.", to_chat=False) + name = " ".join(command[1:]) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + user_info = cursor.fetchone() + cursor.execute("SELECT * FROM names WHERE name='" + name + "';") + if cursor.fetchone() is not None: + return self.__send(msg.from_id, "Error: An account with the name '" + name + "' already exists.", to_chat=False) + cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(user_info[1]) + ";") + account_info = cursor.fetchone() + + cursor.execute("UPDATE names SET name='" + name + "' WHERE name='" + account_info[1] + "';") + cursor.execute("UPDATE accounts SET name='" + name + "' WHERE account_id=" + str(account_info[0]) + ";") + conn.commit() + self.__send(msg.from_id, "The account name has been changed from '" + account_info[1] + "' to '" + name + "'.", to_chat=False) + + def __compute_command_get_token(self, msg): + if not self.__is_login(msg): + return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + user_info = cursor.fetchone() + + token = self.__create_token() + cursor.execute("UPDATE accounts SET token='" + hashlib.sha512(token.encode()).hexdigest() + "' WHERE account_id=" + str(user_info[1]) + ";") + conn.commit() + self.__send(msg.from_id, "Token to connect to the account: " + token, to_chat=False) + + def __compute_command_delete_token(self, msg): + if not self.__is_login(msg): + return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + user_info = cursor.fetchone() + + cursor.execute("UPDATE accounts SET token='?' WHERE account_id=" + str(user_info[1]) + ";") + conn.commit() + self.__send(msg.from_id, "The token for connecting to the account has been deleted.", to_chat=False) + + def __compute_chat_command(self, msg): + command = msg.get_chat_command() + if command == "select": + self.__compute_command_select(msg) + elif command == "connect": + self.__compute_command_connect(msg) + elif command == "disconnect": + self.__compute_command_disconnect(msg) + else: + self.__send(msg.from_id, "Error: Unknown instruction.") + + def __compute_user_command(self, msg): + self.__add_user(msg) + command = msg.get_user_command() + if len(command) >= 1 and command[0].lower() == "create": + self.__compute_command_create(msg) + elif len(command) >= 1 and command[0].lower() == "login": + self.__compute_command_login(msg) + elif len(command) >= 1 and command[0].lower() == "logout": + self.__compute_command_logout(msg) + elif len(command) >= 1 and command[0].lower() == "rename": + self.__compute_command_rename(msg) + elif len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "token": + self.__compute_command_get_token(msg) + elif len(command) >= 2 and command[0].lower() == "delete" and command[1].lower() == "token": + self.__compute_command_delete_token(msg) + else: + self.__send(msg.from_id, "Error: Unknown instruction.", to_chat=False) + + def run(self, vk_token=None, vk_group_id=None, telegram_token=None, discord_token=None): + if vk_token is not None and vk_group_id is not None: + self.vk_client.run(vk_token, vk_group_id) + if telegram_token is not None: + self.telegram_client.run(telegram_token) + if discord_token is not None: + self.discord_client.run(discord_token) + + def compute_message(self, msg): + if msg.text == "": + return None + + if msg.chat_name is None: + thread = threading.Thread(target=self.__compute_user_command, args=(msg, )) + thread.start() + return None + + self.graph.add_vertex(msg.from_id) + if msg.is_chat_command(): + return self.__compute_chat_command(msg) + + name = self.__get_user_name(msg) + if name is not None: + msg.author_name = name + for send_id in self.graph.get_reachable_vertices(msg.from_id): + self.__send(send_id, msg.get_text_to_forwarding()) From 2b93d0ad7daa21fcadd02f09359b095c155dbc81 Mon Sep 17 00:00:00 2001 From: Grigoriy Date: Mon, 9 May 2022 11:22:00 +0500 Subject: [PATCH 2/5] Code rework. --- data/users_inf.db | Bin 28672 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 data/users_inf.db diff --git a/data/users_inf.db b/data/users_inf.db deleted file mode 100644 index 7c3bdf5f44186bba0f97cb5fb9190aa4fe96b200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI(Z)?*)90%~b-j-&QHG4ARlR;n%HW-@AtNyCB;fx1wbpL!@u3LP1{k~z?Tg#O6`l&u1 zy*tSB+D?2l9+dB$NsJDkjH*wWVcRx+U+%D2epEIyQ+q3?*8T*a-qZAc*VktZ`+tuZ~~2Dt@_GhnHQE%bUe7Y9X)8 z`bezr*4Mm4fdB*`009U<00Izz00bZa0SG|g#tPVsNmE8r@IXYaA95+BFN4QTtHM@I z?t76ZJ?XlwCpF4eOyLKSkU`+S+$k>kne~ZS->e^bhXMfzKmY;|fB*y_009U<00Izz zz|9r7%Ow3<{?8WKD!rKhS88l!?n3@w-2YSSC&?ca2tWV=5P$##AOHafKmY;|fB*z; zmq3-WC3EjZqfy-d)BOMW`GW!h2tWV=5P$##AOHafKmY;|fWR#isIsNGBL6?T{~r=- zc*_om<3Ioc5P$##AOHafKmY;|fB*!pRp8_fHO!?C3$#eTwd?x62zU|+&l7Ri*Ipc| zRED0AS}5tIz7ky}c_L+!NFEDcCjobrOygK7%~ha%6{^I~lnBzmm6_07XUb1~PX2F?7wRl=RnU#x)Qh7a;YsLbipT8i{{NI%r`L*t;1GZS1Rwwb2tWV=5P$##AOHaf K{38Lz%(>sI7~LNL From bf73eb3f1d145c64b7de67f2d8c9e9a47bbde1c6 Mon Sep 17 00:00:00 2001 From: Grigoriy Date: Mon, 9 May 2022 11:37:12 +0500 Subject: [PATCH 3/5] Code rework. --- client_classes/Message.py | 3 - client_classes/VkClient.py | 5 +- config.py | 7 +- main.py | 285 +---------------------------------- main_classes/UsersHandler.py | 8 +- 5 files changed, 16 insertions(+), 292 deletions(-) diff --git a/client_classes/Message.py b/client_classes/Message.py index 2f01b68..c302741 100644 --- a/client_classes/Message.py +++ b/client_classes/Message.py @@ -17,9 +17,6 @@ def get_chat_command(self): return None return self.text[1:].strip().lower() - def get_user_command(self): - return self.text.split(" ") - def get_text_to_forwarding(self): description = "" if self.author_name is not None: diff --git a/client_classes/VkClient.py b/client_classes/VkClient.py index 26b3c5c..8b40e5b 100644 --- a/client_classes/VkClient.py +++ b/client_classes/VkClient.py @@ -11,11 +11,14 @@ def __init__(self, compute_message_func): self.handler_thread = None self.compute_massage = compute_message_func + def __get_peer_id_by_id(self, id): + return id + 2000000000 + def __get_user(self, id): return self.client.method("users.get", {"user_ids": id})[0] def __get_chat(self, id): - chat = self.client.method("messages.getConversationsById", {"peer_ids": id + 2000000000}) + chat = self.client.method("messages.getConversationsById", {"peer_ids": self.__get_peer_id_by_id(id)}) if chat["count"] == 0: return {"chat_settings": {"title": "NULL"}} return chat["items"][0] diff --git a/config.py b/config.py index fa48ddd..e8e7e2b 100644 --- a/config.py +++ b/config.py @@ -5,5 +5,8 @@ TELEGRAM_TOKEN = "" -GRAPH_STORAGE_NAME = "graph.txt" -ERROR_LOG_NAME = "error_log.txt" +TOKEN_LENGTH = 10 + +GRAPH_STORAGE_NAME = "data/graph.txt" +ERROR_LOG_NAME = "data/error_log.txt" +USERS_INFORMATION_DB_NAME = "data/users_inf.db" diff --git a/main.py b/main.py index ee2b743..0a5a709 100644 --- a/main.py +++ b/main.py @@ -1,289 +1,10 @@ -import threading -import queue -import vk_api -from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType -import discord -import telebot import config - - -class Graph: - def __init__(self, graph_storage_name): - graph_storage = open(graph_storage_name, "r") - self.graph_storage_name = graph_storage_name - self.adjacency_list = dict() - self.graph_storage_size = 0 - for line in graph_storage.readlines(): - type_operation, vertex1, vertex2 = self.__convert_text_to_operation(line) - - if type_operation == "+": - self.add_edge(vertex1, vertex2, save_operation=False) - else: - self.erase_edge(vertex1, vertex2, save_operation=False) - self.graph_storage_size += 1 - - def __convert_text_to_operation(self, text): - text = text.split() - return text[0], (int(text[1]), text[2]), (int(text[3]), text[4]) - - def __convert_operation_to_text(self, type_operation, vertex1, vertex2): - return type_operation + " " + str(vertex1[0]) + " " + vertex1[1] + " " + str(vertex2[0]) + " " + vertex2[ - 1] + "\n" - - def __reset_graph_storage(self): - graph_storage = open(self.graph_storage_name, "w") - used = set() - self.graph_storage_size = 0 - for vertex1 in self.adjacency_list: - for vertex2 in self.adjacency_list[vertex1]: - if (vertex1, vertex2) in used or (vertex2, vertex1) in used: - continue - - used.add((vertex1, vertex2)) - graph_storage.write(self.__convert_operation_to_text("+", vertex1, vertex2)) - self.graph_storage_size += 1 - graph_storage.close() - - def __add_operation_to_storage(self, type_operation, vertex1, vertex2): - graph_storage = open(self.graph_storage_name, "a") - graph_storage.write(self.__convert_operation_to_text(type_operation, vertex1, vertex2)) - graph_storage.close() - - self.graph_storage_size += 1 - if self.graph_storage_size >= len(self.adjacency_list) ** 2: - self.__reset_graph_storage() - - def get_reachable_vertices(self, vertex_start): - used = set() - used.add(vertex_start) - q = queue.Queue() - q.put(vertex_start) - while not q.empty(): - v = q.get() - for to in self.adjacency_list[v]: - if to in used: - continue - - used.add(to) - q.put(to) - - used.discard(vertex_start) - return list(used) - - def add_vertex(self, vertex): - if not (vertex in self.adjacency_list): - self.adjacency_list[vertex] = set() - - def add_edge(self, vertex1, vertex2, save_operation=True): - self.add_vertex(vertex1) - self.add_vertex(vertex2) - self.adjacency_list[vertex1].add(vertex2) - self.adjacency_list[vertex2].add(vertex1) - if save_operation: - self.__add_operation_to_storage("+", vertex1, vertex2) - - def erase_edge(self, vertex1, vertex2, save_operation=True): - if vertex1 in self.adjacency_list: - self.adjacency_list[vertex1].discard(vertex2) - if vertex2 in self.adjacency_list: - self.adjacency_list[vertex2].discard(vertex1) - if save_operation: - self.__add_operation_to_storage("-", vertex1, vertex2) - - -class Message: - def __init__(self, from_id, text, author_name, chat_name): - self.from_id = from_id - self.text = text - self.author_name = author_name - self.chat_name = chat_name - - def is_command(self): - return len(self.text) > 0 and self.text[0] == "!" - - def get_command(self): - if not self.is_command(): - return None - return self.text[1:].strip().lower() - - def get_text_to_forwarding(self): - description = "" - if self.chat_name != None: - description += " [" + self.chat_name + "]" - if self.author_name != None: - description += ", " + self.author_name - if self.chat_name != None or self.author_name != None: - description = self.from_id[1] + description + ":\n" - return description + self.text - - -class VkClient: - def __init__(self): - self.vk_client = None - self.group_id = None - self.handler_thread = None - - def __get_user(self, id): - return self.vk_client.method("users.get", {"user_ids": id})[0] - - def __get_chat(self, id): - chat = self.vk_client.method("messages.getConversationsById", {"peer_ids": id + 2000000000}) - if chat["count"] == 0: - return {"chat_settings": {"title": None}} - return chat["items"][0] - - def __handler(self): - longpoll = VkBotLongPoll(self.vk_client, self.group_id) - - for event in longpoll.listen(): - if event.type == VkBotEventType.MESSAGE_NEW and event.from_chat: - author = self.__get_user(event.object["message"]["from_id"]) - author_name = author["last_name"] + " " + author["first_name"] - chat = self.__get_chat(event.chat_id) - chat_name = chat["chat_settings"]["title"] - compute_message(Message((event.chat_id, "VK"), event.object["message"]["text"], author_name, chat_name)) - - def send_msg(self, id, text): - self.vk_client.method("messages.send", {"chat_id": id, "message": text, "random_id": 0}) - - def run(self, token, group_id): - self.vk_client = vk_api.VkApi(token=token) - self.group_id = group_id - self.handler_thread = threading.Thread(target=self.__handler) - self.handler_thread.start() - - -class TelegramClient: - def __init__(self): - self.telegram_client = None - self.handler_thread = None - - def __handler(self): - @self.telegram_client.message_handler(content_types=["text"]) - def on_message(message): - author_name = message.from_user.last_name + " " + message.from_user.first_name - chat_name = message.chat.title - compute_message(Message((message.chat.id, "TG"), message.text, author_name, chat_name)) - - self.telegram_client.infinity_polling() - - def send_msg(self, id, text): - self.telegram_client.send_message(id, text) - - def run(self, token): - self.telegram_client = telebot.TeleBot(token) - self.handler_thread = threading.Thread(target=self.__handler) - self.handler_thread.start() - - -class DiscordClient(discord.Client): - async def on_message(self, message): - if message.author != self.user: - author_name = message.author.name - chat_name = message.author.guild.name + "/" + message.channel.name - compute_message(Message((message.channel.id, "DS"), message.content, author_name, chat_name)) - - def send_msg(self, id, text): - self.loop.create_task(discord_client.get_channel(id).send(text)) - - -def add_error_to_log(text): - error_log = open(config.ERROR_LOG_NAME, "a") - error_log.write(text + "\n\n") - error_log.close() - - -def send(id, text): - try: - if id[1] == "VK": - vk_client.send_msg(id[0], text) - elif id[1] == "DS": - discord_client.send_msg(id[0], text) - elif id[1] == "TG": - telegram_client.send_msg(id[0], text) - else: - add_error_to_log("Error: Unknown system to send message.") - except Exception as error: - add_error_to_log("Error: Unknown error while sending the message.\nDescription:\n" + str(error)) - - -def compute_command_select(msg): - global select_chat - - select_chat["chat_id"] = msg.from_id - select_chat["chat_name"] = msg.chat_name - send(msg.from_id, "Chat is selected.") - - -def compute_command_connect(msg): - global graph, select_chat - - select_id = select_chat["chat_id"] - if select_id == (None, None): - send(msg.from_id, "Error: No selected chat.") - elif select_id == msg.from_id: - send(msg.from_id, "Error: Attempting to connect a chat with itself.") - elif select_id in graph.adjacency_list[msg.from_id]: - send(msg.from_id, "Error: Chats already connected.") - else: - graph.add_edge(msg.from_id, select_id) - send(msg.from_id, select_id[1] + " chat with name " + select_chat["chat_name"] + " is connected.") - send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is connected.") - - -def compute_command_disconnect(msg): - global graph, select_chat - - select_id = select_chat["chat_id"] - if select_id == (None, None): - send(msg.from_id, "Error: No selected chat.") - elif not (select_id in graph.adjacency_list[msg.from_id]): - send(msg.from_id, "Error: Chats are not connected.") - else: - graph.erase_edge(msg.from_id, select_id) - send(msg.from_id, select_id[1] + " chat with name " + select_chat["chat_name"] + " is disconnected.") - send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is disconnected.") - - -def compute_command(msg): - command = msg.get_command() - if command == "select": - compute_command_select(msg) - elif command == "connect": - compute_command_connect(msg) - elif command == "disconnect": - compute_command_disconnect(msg) - else: - send(msg.from_id, "Error: Unknown instruction.") - - -def compute_message(msg): - global graph - - if msg.text == "": - return None - - graph.add_vertex(msg.from_id) - if msg.is_command(): - return compute_command(msg) - - for send_id in graph.get_reachable_vertices(msg.from_id): - send(send_id, msg.get_text_to_forwarding()) +from main_classes.UsersHandler import UsersHandler def main(): - global graph, select_chat, vk_client, discord_client, telegram_client - - graph = Graph(config.GRAPH_STORAGE_NAME) - select_chat = {"chat_name": None, "chat_id": (None, None)} - - vk_client = VkClient() - discord_client = DiscordClient() - telegram_client = TelegramClient() - - vk_client.run(config.VK_TOKEN, config.VK_GROUP_ID) - telegram_client.run(config.TELEGRAM_TOKEN) - discord_client.run(config.DISCORD_TOKEN) + bot = UsersHandler(config.GRAPH_STORAGE_NAME, config.USERS_INFORMATION_DB_NAME, token_length=config.TOKEN_LENGTH, error_log_name=config.ERROR_LOG_NAME) + bot.run(vk_token=config.VK_TOKEN, vk_group_id=config.VK_GROUP_ID, telegram_token=config.TELEGRAM_TOKEN, discord_token=config.DISCORD_TOKEN) if __name__ == "__main__": diff --git a/main_classes/UsersHandler.py b/main_classes/UsersHandler.py index b6358cc..4322c79 100644 --- a/main_classes/UsersHandler.py +++ b/main_classes/UsersHandler.py @@ -125,7 +125,7 @@ def __compute_command_create(self, msg): if self.__is_login(msg): return self.__send(msg.from_id, "To create a new account, you must log out of your current account.", to_chat=False) - command = msg.get_user_command() + command = msg.text.split() if len(command) == 1: return self.__send(msg.from_id, "Error: To create an account, you need to provide an account name.", to_chat=False) name = " ".join(command[1:]) @@ -149,7 +149,7 @@ def __compute_command_login(self, msg): if self.__is_login(msg): return self.__send(msg.from_id, "To login, you must log out of your current account.", to_chat=False) - command = msg.get_user_command() + command = msg.text.split() if len(command) == 1: return self.__send(msg.from_id, "Error: To enter an account, you need to provide a token.", to_chat=False) if len(command) == 2: @@ -198,7 +198,7 @@ def __compute_command_rename(self, msg): if not self.__is_login(msg): return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) - command = msg.get_user_command() + command = msg.text.split() if len(command) == 1: return self.__send(msg.from_id, "Error: To rename an account, you need to provide an new account name.", to_chat=False) name = " ".join(command[1:]) @@ -258,7 +258,7 @@ def __compute_chat_command(self, msg): def __compute_user_command(self, msg): self.__add_user(msg) - command = msg.get_user_command() + command = msg.text.split() if len(command) >= 1 and command[0].lower() == "create": self.__compute_command_create(msg) elif len(command) >= 1 and command[0].lower() == "login": From 8ba0fffba767ae53c1235b6282ba603aabb53fc0 Mon Sep 17 00:00:00 2001 From: Grigoriy Date: Mon, 9 May 2022 18:51:39 +0500 Subject: [PATCH 4/5] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=8B=D0=BB=D0=BA=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client_classes/DiscordClient.py | 24 +++ client_classes/Message.py | 28 +++ client_classes/TelegramClient.py | 35 ++++ client_classes/VkClient.py | 55 ++++++ config.py | 5 +- error_log.txt => data/error_log.txt | 0 graph.txt => data/graph.txt | 0 main.py | 285 +--------------------------- main_classes/Graph.py | 98 ++++++++++ main_classes/UsersHandler.py | 234 +++++++++++++++++++++++ 10 files changed, 480 insertions(+), 284 deletions(-) create mode 100644 client_classes/DiscordClient.py create mode 100644 client_classes/Message.py create mode 100644 client_classes/TelegramClient.py create mode 100644 client_classes/VkClient.py rename error_log.txt => data/error_log.txt (100%) rename graph.txt => data/graph.txt (100%) create mode 100644 main_classes/Graph.py create mode 100644 main_classes/UsersHandler.py diff --git a/client_classes/DiscordClient.py b/client_classes/DiscordClient.py new file mode 100644 index 0000000..61094de --- /dev/null +++ b/client_classes/DiscordClient.py @@ -0,0 +1,24 @@ +import discord +from client_classes.Message import Message + + +class DiscordClient(discord.Client): + def __init__(self, compute_message_func): + super(DiscordClient, self).__init__() + self.compute_massage = compute_message_func + + async def on_message(self, message): + if message.author != self.user: + from_id = message.channel.id + text = message.content + author_id = message.author.id + author_name = message.author.name + if type(message.channel) != discord.channel.DMChannel: + chat_name = message.author.guild.name + "/" + message.channel.name + is_owner = message.author.guild_permissions.administrator + self.compute_massage(Message((from_id, "DS"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + self.compute_massage(Message((from_id, "DS"), text, author_id, author_name)) + + def send_msg(self, id, text): + self.loop.create_task(self.get_channel(id).send(text)) diff --git a/client_classes/Message.py b/client_classes/Message.py new file mode 100644 index 0000000..e006b45 --- /dev/null +++ b/client_classes/Message.py @@ -0,0 +1,28 @@ +class Message: + def __init__(self, from_id, text, author_id, author_name, chat_name=None, is_owner=None): + self.from_id = from_id + self.text = text + self.author_id = author_id + self.author_name = author_name + self.chat_name = chat_name + self.is_owner = is_owner + + def is_chat_command(self): + return len(self.text) > 0 and self.text[0] == "!" + + def get_chat_id(self): + return str(self.from_id[0]) + self.from_id[1] + + def get_author_id(self): + return str(self.author_id) + self.from_id[1] + + def get_chat_command(self): + if not self.is_chat_command(): + return None + return self.text[1:].strip() + + def get_text_to_forwarding(self): + description = "" + if self.author_name is not None: + description += self.author_name + ":\n" + return description + self.text diff --git a/client_classes/TelegramClient.py b/client_classes/TelegramClient.py new file mode 100644 index 0000000..db852f3 --- /dev/null +++ b/client_classes/TelegramClient.py @@ -0,0 +1,35 @@ +import threading +import telebot +from client_classes.Message import Message + + +class TelegramClient: + def __init__(self, compute_message_func): + self.client = None + self.handler_thread = None + self.compute_massage = compute_message_func + + def __handler(self): + @self.client.message_handler(content_types=["text"]) + def on_message(message): + from_id = message.chat.id + text = message.text + author_id = message.from_user.id + author = self.client.get_chat_member(from_id, author_id) + author_name = message.from_user.last_name + " " + message.from_user.first_name + if from_id != author_id: + chat_name = message.chat.title + is_owner = author.status == "creator" + self.compute_massage(Message((from_id, "TG"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + self.compute_massage(Message((from_id, "TG"), text, author_id, author_name)) + + self.client.infinity_polling() + + def send_msg(self, id, text): + self.client.send_message(id, text) + + def run(self, token): + self.client = telebot.TeleBot(token) + self.handler_thread = threading.Thread(target=self.__handler) + self.handler_thread.start() diff --git a/client_classes/VkClient.py b/client_classes/VkClient.py new file mode 100644 index 0000000..ccf5cc3 --- /dev/null +++ b/client_classes/VkClient.py @@ -0,0 +1,55 @@ +import threading +import vk_api +from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType +from client_classes.Message import Message + + +class VkClient: + def __init__(self, compute_message_func): + self.client = None + self.group_id = None + self.handler_thread = None + self.compute_massage = compute_message_func + + def __get_peer_id_by_id(self, id): + return id + 2000000000 + + def __get_user(self, id): + return self.client.method("users.get", {"user_ids": id})[0] + + def __get_chat(self, id): + chat = self.client.method("messages.getConversationsById", {"peer_ids": self.__get_peer_id_by_id(id)}) + if chat["count"] == 0: + return {"chat_settings": {"title": "NULL"}} + return chat["items"][0] + + def __handler(self): + longpoll = VkBotLongPoll(self.client, self.group_id) + + for event in longpoll.listen(): + if event.type == VkBotEventType.MESSAGE_NEW: + text = event.object["message"]["text"] + author = self.__get_user(event.object["message"]["from_id"]) + author_id = event.object["message"]["from_id"] + author_name = author["last_name"] + " " + author["first_name"] + if event.from_chat: + from_id = event.chat_id + chat = self.__get_chat(event.chat_id) + chat_name = chat["chat_settings"]["title"] + is_owner = author_id == chat["chat_settings"]["owner_id"] + self.compute_massage(Message((from_id, "VK"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + from_id = event.object["message"]["from_id"] + self.compute_massage(Message((from_id, "VK"), text, author_id, author_name)) + + def send_msg(self, id, text, to_chat): + if to_chat: + self.client.method("messages.send", {"chat_id": id, "message": text, "random_id": 0}) + else: + self.client.method("messages.send", {"user_id": id, "message": text, "random_id": 0}) + + def run(self, token, group_id): + self.client = vk_api.VkApi(token=token) + self.group_id = group_id + self.handler_thread = threading.Thread(target=self.__handler) + self.handler_thread.start() diff --git a/config.py b/config.py index fa48ddd..49e36d8 100644 --- a/config.py +++ b/config.py @@ -5,5 +5,6 @@ TELEGRAM_TOKEN = "" -GRAPH_STORAGE_NAME = "graph.txt" -ERROR_LOG_NAME = "error_log.txt" +GRAPH_STORAGE_NAME = "data/graph.txt" +ERROR_LOG_NAME = "data/error_log.txt" +USERS_INFORMATION_DB_NAME = "data/users_inf.db" diff --git a/error_log.txt b/data/error_log.txt similarity index 100% rename from error_log.txt rename to data/error_log.txt diff --git a/graph.txt b/data/graph.txt similarity index 100% rename from graph.txt rename to data/graph.txt diff --git a/main.py b/main.py index ee2b743..c71efa4 100644 --- a/main.py +++ b/main.py @@ -1,289 +1,10 @@ -import threading -import queue -import vk_api -from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType -import discord -import telebot import config - - -class Graph: - def __init__(self, graph_storage_name): - graph_storage = open(graph_storage_name, "r") - self.graph_storage_name = graph_storage_name - self.adjacency_list = dict() - self.graph_storage_size = 0 - for line in graph_storage.readlines(): - type_operation, vertex1, vertex2 = self.__convert_text_to_operation(line) - - if type_operation == "+": - self.add_edge(vertex1, vertex2, save_operation=False) - else: - self.erase_edge(vertex1, vertex2, save_operation=False) - self.graph_storage_size += 1 - - def __convert_text_to_operation(self, text): - text = text.split() - return text[0], (int(text[1]), text[2]), (int(text[3]), text[4]) - - def __convert_operation_to_text(self, type_operation, vertex1, vertex2): - return type_operation + " " + str(vertex1[0]) + " " + vertex1[1] + " " + str(vertex2[0]) + " " + vertex2[ - 1] + "\n" - - def __reset_graph_storage(self): - graph_storage = open(self.graph_storage_name, "w") - used = set() - self.graph_storage_size = 0 - for vertex1 in self.adjacency_list: - for vertex2 in self.adjacency_list[vertex1]: - if (vertex1, vertex2) in used or (vertex2, vertex1) in used: - continue - - used.add((vertex1, vertex2)) - graph_storage.write(self.__convert_operation_to_text("+", vertex1, vertex2)) - self.graph_storage_size += 1 - graph_storage.close() - - def __add_operation_to_storage(self, type_operation, vertex1, vertex2): - graph_storage = open(self.graph_storage_name, "a") - graph_storage.write(self.__convert_operation_to_text(type_operation, vertex1, vertex2)) - graph_storage.close() - - self.graph_storage_size += 1 - if self.graph_storage_size >= len(self.adjacency_list) ** 2: - self.__reset_graph_storage() - - def get_reachable_vertices(self, vertex_start): - used = set() - used.add(vertex_start) - q = queue.Queue() - q.put(vertex_start) - while not q.empty(): - v = q.get() - for to in self.adjacency_list[v]: - if to in used: - continue - - used.add(to) - q.put(to) - - used.discard(vertex_start) - return list(used) - - def add_vertex(self, vertex): - if not (vertex in self.adjacency_list): - self.adjacency_list[vertex] = set() - - def add_edge(self, vertex1, vertex2, save_operation=True): - self.add_vertex(vertex1) - self.add_vertex(vertex2) - self.adjacency_list[vertex1].add(vertex2) - self.adjacency_list[vertex2].add(vertex1) - if save_operation: - self.__add_operation_to_storage("+", vertex1, vertex2) - - def erase_edge(self, vertex1, vertex2, save_operation=True): - if vertex1 in self.adjacency_list: - self.adjacency_list[vertex1].discard(vertex2) - if vertex2 in self.adjacency_list: - self.adjacency_list[vertex2].discard(vertex1) - if save_operation: - self.__add_operation_to_storage("-", vertex1, vertex2) - - -class Message: - def __init__(self, from_id, text, author_name, chat_name): - self.from_id = from_id - self.text = text - self.author_name = author_name - self.chat_name = chat_name - - def is_command(self): - return len(self.text) > 0 and self.text[0] == "!" - - def get_command(self): - if not self.is_command(): - return None - return self.text[1:].strip().lower() - - def get_text_to_forwarding(self): - description = "" - if self.chat_name != None: - description += " [" + self.chat_name + "]" - if self.author_name != None: - description += ", " + self.author_name - if self.chat_name != None or self.author_name != None: - description = self.from_id[1] + description + ":\n" - return description + self.text - - -class VkClient: - def __init__(self): - self.vk_client = None - self.group_id = None - self.handler_thread = None - - def __get_user(self, id): - return self.vk_client.method("users.get", {"user_ids": id})[0] - - def __get_chat(self, id): - chat = self.vk_client.method("messages.getConversationsById", {"peer_ids": id + 2000000000}) - if chat["count"] == 0: - return {"chat_settings": {"title": None}} - return chat["items"][0] - - def __handler(self): - longpoll = VkBotLongPoll(self.vk_client, self.group_id) - - for event in longpoll.listen(): - if event.type == VkBotEventType.MESSAGE_NEW and event.from_chat: - author = self.__get_user(event.object["message"]["from_id"]) - author_name = author["last_name"] + " " + author["first_name"] - chat = self.__get_chat(event.chat_id) - chat_name = chat["chat_settings"]["title"] - compute_message(Message((event.chat_id, "VK"), event.object["message"]["text"], author_name, chat_name)) - - def send_msg(self, id, text): - self.vk_client.method("messages.send", {"chat_id": id, "message": text, "random_id": 0}) - - def run(self, token, group_id): - self.vk_client = vk_api.VkApi(token=token) - self.group_id = group_id - self.handler_thread = threading.Thread(target=self.__handler) - self.handler_thread.start() - - -class TelegramClient: - def __init__(self): - self.telegram_client = None - self.handler_thread = None - - def __handler(self): - @self.telegram_client.message_handler(content_types=["text"]) - def on_message(message): - author_name = message.from_user.last_name + " " + message.from_user.first_name - chat_name = message.chat.title - compute_message(Message((message.chat.id, "TG"), message.text, author_name, chat_name)) - - self.telegram_client.infinity_polling() - - def send_msg(self, id, text): - self.telegram_client.send_message(id, text) - - def run(self, token): - self.telegram_client = telebot.TeleBot(token) - self.handler_thread = threading.Thread(target=self.__handler) - self.handler_thread.start() - - -class DiscordClient(discord.Client): - async def on_message(self, message): - if message.author != self.user: - author_name = message.author.name - chat_name = message.author.guild.name + "/" + message.channel.name - compute_message(Message((message.channel.id, "DS"), message.content, author_name, chat_name)) - - def send_msg(self, id, text): - self.loop.create_task(discord_client.get_channel(id).send(text)) - - -def add_error_to_log(text): - error_log = open(config.ERROR_LOG_NAME, "a") - error_log.write(text + "\n\n") - error_log.close() - - -def send(id, text): - try: - if id[1] == "VK": - vk_client.send_msg(id[0], text) - elif id[1] == "DS": - discord_client.send_msg(id[0], text) - elif id[1] == "TG": - telegram_client.send_msg(id[0], text) - else: - add_error_to_log("Error: Unknown system to send message.") - except Exception as error: - add_error_to_log("Error: Unknown error while sending the message.\nDescription:\n" + str(error)) - - -def compute_command_select(msg): - global select_chat - - select_chat["chat_id"] = msg.from_id - select_chat["chat_name"] = msg.chat_name - send(msg.from_id, "Chat is selected.") - - -def compute_command_connect(msg): - global graph, select_chat - - select_id = select_chat["chat_id"] - if select_id == (None, None): - send(msg.from_id, "Error: No selected chat.") - elif select_id == msg.from_id: - send(msg.from_id, "Error: Attempting to connect a chat with itself.") - elif select_id in graph.adjacency_list[msg.from_id]: - send(msg.from_id, "Error: Chats already connected.") - else: - graph.add_edge(msg.from_id, select_id) - send(msg.from_id, select_id[1] + " chat with name " + select_chat["chat_name"] + " is connected.") - send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is connected.") - - -def compute_command_disconnect(msg): - global graph, select_chat - - select_id = select_chat["chat_id"] - if select_id == (None, None): - send(msg.from_id, "Error: No selected chat.") - elif not (select_id in graph.adjacency_list[msg.from_id]): - send(msg.from_id, "Error: Chats are not connected.") - else: - graph.erase_edge(msg.from_id, select_id) - send(msg.from_id, select_id[1] + " chat with name " + select_chat["chat_name"] + " is disconnected.") - send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is disconnected.") - - -def compute_command(msg): - command = msg.get_command() - if command == "select": - compute_command_select(msg) - elif command == "connect": - compute_command_connect(msg) - elif command == "disconnect": - compute_command_disconnect(msg) - else: - send(msg.from_id, "Error: Unknown instruction.") - - -def compute_message(msg): - global graph - - if msg.text == "": - return None - - graph.add_vertex(msg.from_id) - if msg.is_command(): - return compute_command(msg) - - for send_id in graph.get_reachable_vertices(msg.from_id): - send(send_id, msg.get_text_to_forwarding()) +from main_classes.UsersHandler import UsersHandler def main(): - global graph, select_chat, vk_client, discord_client, telegram_client - - graph = Graph(config.GRAPH_STORAGE_NAME) - select_chat = {"chat_name": None, "chat_id": (None, None)} - - vk_client = VkClient() - discord_client = DiscordClient() - telegram_client = TelegramClient() - - vk_client.run(config.VK_TOKEN, config.VK_GROUP_ID) - telegram_client.run(config.TELEGRAM_TOKEN) - discord_client.run(config.DISCORD_TOKEN) + bot = UsersHandler(config.GRAPH_STORAGE_NAME, config.USERS_INFORMATION_DB_NAME, error_log_name=config.ERROR_LOG_NAME) + bot.run(vk_token=config.VK_TOKEN, vk_group_id=config.VK_GROUP_ID, telegram_token=config.TELEGRAM_TOKEN, discord_token=config.DISCORD_TOKEN) if __name__ == "__main__": diff --git a/main_classes/Graph.py b/main_classes/Graph.py new file mode 100644 index 0000000..733e0fb --- /dev/null +++ b/main_classes/Graph.py @@ -0,0 +1,98 @@ +import queue + + +class Graph: + def __init__(self, graph_storage_name): + graph_storage = open(graph_storage_name, "r") + self.graph_storage_name = graph_storage_name + self.adjacency_list = dict() + self.rules = dict() + self.graph_storage_size = 0 + for line in graph_storage.readlines(): + type_operation, vertex1, vertex2 = self.__convert_text_to_operation(line) + + if type_operation == "+": + self.add_edge(vertex1, vertex2, save_operation=False) + else: + self.erase_edge(vertex1, vertex2, save_operation=False) + self.graph_storage_size += 1 + + def __check_rule(self, vertex, text): + rule = self.rules[vertex] + return len(text) >= len(rule) and text[:len(rule)] == rule + + def __convert_text_to_operation(self, text): + text = text.split() + return text[0], (int(text[1]), text[2]), (int(text[3]), text[4]) + + def __convert_operation_to_text(self, type_operation, vertex1, vertex2): + return type_operation + " " + str(vertex1[0]) + " " + vertex1[1] + " " + str(vertex2[0]) + " " + vertex2[1] + "\n" + + def __reset_graph_storage(self): + graph_storage = open(self.graph_storage_name, "w") + used = set() + self.graph_storage_size = 0 + for vertex1 in self.adjacency_list: + for vertex2 in self.adjacency_list[vertex1]: + if (vertex1, vertex2) in used or (vertex2, vertex1) in used: + continue + + used.add((vertex1, vertex2)) + graph_storage.write(self.__convert_operation_to_text("+", vertex1, vertex2)) + self.graph_storage_size += 1 + graph_storage.close() + + def __add_operation_to_storage(self, type_operation, vertex1, vertex2): + graph_storage = open(self.graph_storage_name, "a") + graph_storage.write(self.__convert_operation_to_text(type_operation, vertex1, vertex2)) + graph_storage.close() + + self.graph_storage_size += 1 + if self.graph_storage_size >= len(self.adjacency_list) ** 2: + self.__reset_graph_storage() + + def get_reachable_vertices(self, vertex_start, text): + used = set() + used.add(vertex_start) + q = queue.Queue() + q.put(vertex_start) + while not q.empty(): + v = q.get() + if not self.__check_rule(v, text): + continue + + for to in self.adjacency_list[v]: + if to in used: + continue + + used.add(to) + q.put(to) + + used.discard(vertex_start) + return list(used) + + def add_vertex(self, vertex): + if not (vertex in self.adjacency_list): + self.rules[vertex] = "" + self.adjacency_list[vertex] = set() + + def set_rule(self, id, rule): + vertex = (int(id[:-2]), id[-2:]) + self.add_vertex(vertex) + self.rules[vertex] = rule + + def add_edge(self, vertex1, vertex2, save_operation=True): + self.add_vertex(vertex1) + self.add_vertex(vertex2) + self.adjacency_list[vertex1].add(vertex2) + self.adjacency_list[vertex2].add(vertex1) + if save_operation: + self.__add_operation_to_storage("+", vertex1, vertex2) + + def erase_edge(self, vertex1, vertex2, save_operation=True): + if vertex1 in self.adjacency_list: + self.adjacency_list[vertex1].discard(vertex2) + if vertex2 in self.adjacency_list: + self.adjacency_list[vertex2].discard(vertex1) + if save_operation: + self.__add_operation_to_storage("-", vertex1, vertex2) diff --git a/main_classes/UsersHandler.py b/main_classes/UsersHandler.py new file mode 100644 index 0000000..0ad8103 --- /dev/null +++ b/main_classes/UsersHandler.py @@ -0,0 +1,234 @@ +import sqlite3 +from main_classes.Graph import Graph +from client_classes.VkClient import VkClient +from client_classes.TelegramClient import TelegramClient +from client_classes.DiscordClient import DiscordClient + + +class UsersHandler: + def __init__(self, graph_storage_name, users_information_db_name, error_log_name=None): + self.error_log_name = error_log_name + self.graph = Graph(graph_storage_name) + self.select_chat = {"chat_name": None, "chat_id": (None, None)} + + self.vk_client = VkClient(self.compute_message) + self.discord_client = DiscordClient(self.compute_message) + self.telegram_client = TelegramClient(self.compute_message) + + self.users_information_db_name = users_information_db_name + conn = sqlite3.connect(users_information_db_name) + cursor = conn.cursor() + cursor.execute("CREATE TABLE IF NOT EXISTS users(id TEXT PRIMARY KEY, name TEXT, is_admin INT);") + cursor.execute("CREATE TABLE IF NOT EXISTS chat_rules(id TEXT PRIMARY KEY, rule TEXT);") + conn.commit() + + cursor.execute("SELECT * FROM chat_rules;") + for rule in cursor.fetchall(): + self.graph.set_rule(rule[0], rule[1]) + + def __add_user(self, id): + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + id + "';") + if cursor.fetchone() is None: + entry = (id, "None", 0) + cursor.execute("INSERT INTO users VALUES(?, ?, ?);", entry) + conn.commit() + + def __get_user_name(self, msg): + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + msg.get_author_id() + str(msg.from_id[0]) + "';") + return cursor.fetchone()[1] + + def __is_admin(self, msg): + if msg.is_owner: + return True + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + msg.get_author_id() + str(msg.from_id[0]) + "';") + return cursor.fetchone()[2] + + def __add_error_to_log(self, text): + if self.error_log_name is not None: + error_log = open(self.error_log_name, "a") + error_log.write(text + "\n\n") + error_log.close() + + def __send(self, id, text, to_chat=True): + try: + if id[1] == "VK": + self.vk_client.send_msg(id[0], text, to_chat) + elif id[1] == "DS": + self.discord_client.send_msg(id[0], text) + elif id[1] == "TG": + self.telegram_client.send_msg(id[0], text) + else: + self.__add_error_to_log("Error: Unknown system to send message.") + except Exception as error: + self.__add_error_to_log("Error: Unknown error while sending the message.\nDescription:\n" + str(error)) + + def __compute_command_select(self, msg): + self.select_chat["chat_id"] = msg.from_id + self.select_chat["chat_name"] = msg.chat_name + self.__send(msg.from_id, "Chat is selected.") + + def __compute_command_connect(self, msg): + select_id = self.select_chat["chat_id"] + if select_id == (None, None): + self.__send(msg.from_id, "Error: No selected chat.") + elif select_id == msg.from_id: + self.__send(msg.from_id, "Error: Attempting to connect a chat with itself.") + elif select_id in self.graph.adjacency_list[msg.from_id]: + self.__send(msg.from_id, "Error: Chats already connected.") + else: + self.graph.add_edge(msg.from_id, select_id) + self.__send(msg.from_id, select_id[1] + " chat with name " + self.select_chat["chat_name"] + " is connected.") + self.__send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is connected.") + + def __compute_command_disconnect(self, msg): + select_id = self.select_chat["chat_id"] + if select_id == (None, None): + self.__send(msg.from_id, "Error: No selected chat.") + elif not (select_id in self.graph.adjacency_list[msg.from_id]): + self.__send(msg.from_id, "Error: Chats are not connected.") + else: + self.graph.erase_edge(msg.from_id, select_id) + self.__send(msg.from_id, select_id[1] + " chat with name " + self.select_chat["chat_name"] + " is disconnected.") + self.__send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is disconnected.") + + def __compute_command_get_id(self, msg): + self.__send(msg.from_id, "User id: " + msg.get_author_id(), to_chat=msg.chat_name is not None) + + def __compute_command_set_admin(self, msg): + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") + + command = msg.get_chat_command().split() + if len(command) == 2: + return self.__send(msg.from_id, "Error: An user id is required.") + user_id = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: The account with id '" + user_id + "' already has administrator rights.") + + cursor.execute("UPDATE users SET is_admin=1 WHERE id='" + user_id + str(msg.from_id[0]) + "';") + conn.commit() + self.__send(msg.from_id, "The account with id '" + user_id + "' has been granted administrative rights.") + + def __compute_command_delete_admin(self, msg): + if not msg.is_owner: + return self.__send(msg.from_id, "Error: You must be an owner to use this command.") + + command = msg.get_chat_command().split() + if len(command) == 2: + return self.__send(msg.from_id, "Error: An user id is required.") + user_id = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if not cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: The account with id '" + user_id + "' does not have administrative rights.") + + cursor.execute("UPDATE users SET is_admin=0 WHERE id='" + user_id + str(msg.from_id[0]) + "';") + conn.commit() + self.__send(msg.from_id, "The account with id '" + user_id + "' no longer has administrative rights.") + + def __compute_command_rename(self, msg): + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") + + command = msg.get_chat_command().split() + if len(command) == 1: + return self.__send(msg.from_id, "Error: An user id is required.") + if len(command) == 2: + return self.__send(msg.from_id, "Error: A new name is required.") + user_id = command[1] + if user_id == "self": + user_id = msg.get_author_id() + name = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if user_id != msg.get_author_id() and cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: Forbidden to rename administrators.") + + cursor.execute("UPDATE users SET name='" + name + "' WHERE id='" + user_id + str(msg.from_id[0]) + "';") + conn.commit() + self.__send(msg.from_id, "The account name has been changed to '" + name + "'.") + + def __compute_command_set_rule(self, msg): + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") + + command = msg.get_chat_command() + if len(command.split()) == 2 or command.count("\"") < 2: + return self.__send(msg.from_id, "Error: A new rule is required.") + rule = command[command.find("\"") + 1:command.rfind("\"")] + + conn = sqlite3.connect(self.users_information_db_name) + cursor = conn.cursor() + cursor.execute("UPDATE chat_rules SET rule='" + rule + "' WHERE id='" + msg.get_chat_id() + "';") + conn.commit() + self.graph.set_rule(msg.get_chat_id(), rule) + self.__send(msg.from_id, "The chat rule has been changed to '" + rule + "'.") + + def __compute_chat_command(self, msg): + command = msg.get_chat_command().split() + if len(command) >= 1 and command[0].lower() == "select": + self.__compute_command_select(msg) + elif len(command) >= 1 and command[0].lower() == "connect": + self.__compute_command_connect(msg) + elif len(command) >= 1 and command[0].lower() == "disconnect": + self.__compute_command_disconnect(msg) + elif len(command) >= 2 and command[0].lower() == "set" and command[1].lower() == "admin": + self.__compute_command_set_admin(msg) + elif len(command) >= 2 and command[0].lower() == "delete" and command[1].lower() == "admin": + self.__compute_command_delete_admin(msg) + elif len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "id": + self.__compute_command_get_id(msg) + elif len(command) >= 1 and command[0].lower() == "rename": + self.__compute_command_rename(msg) + elif len(command) >= 2 and command[0].lower() == "set" and command[1].lower() == "rule": + self.__compute_command_set_rule(msg) + else: + self.__send(msg.from_id, "Error: Unknown instruction.") + + def __compute_user_command(self, msg): + command = msg.text.split() + if len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "id": + self.__compute_command_get_id(msg) + else: + self.__send(msg.from_id, "Error: Unknown instruction.", to_chat=False) + + def run(self, vk_token=None, vk_group_id=None, telegram_token=None, discord_token=None): + if vk_token is not None and vk_group_id is not None: + self.vk_client.run(vk_token, vk_group_id) + if telegram_token is not None: + self.telegram_client.run(telegram_token) + if discord_token is not None: + self.discord_client.run(discord_token) + + def compute_message(self, msg): + if msg.chat_name is None: + return self.__compute_user_command(msg) + + self.__add_user(msg.get_author_id() + str(msg.from_id[0])) + self.graph.add_vertex(msg.from_id) + if msg.is_chat_command(): + return self.__compute_chat_command(msg) + + name = self.__get_user_name(msg) + if name != "None": + msg.author_name = name + for send_id in self.graph.get_reachable_vertices(msg.from_id, msg.text): + self.__send(send_id, msg.get_text_to_forwarding()) From 542dce415ae83ddc6e4d97fbb0bcb61fe1b9073e Mon Sep 17 00:00:00 2001 From: Grigoriy Date: Mon, 9 May 2022 18:51:39 +0500 Subject: [PATCH 5/5] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=8B=D0=BB=D0=BA=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client_classes/DiscordClient.py | 9 +- client_classes/Message.py | 8 +- client_classes/TelegramClient.py | 12 +- client_classes/VkClient.py | 12 +- config.py | 2 - main.py | 2 +- main_classes/Graph.py | 16 +- main_classes/UsersHandler.py | 254 +++++++++++-------------------- 8 files changed, 139 insertions(+), 176 deletions(-) diff --git a/client_classes/DiscordClient.py b/client_classes/DiscordClient.py index 8b59058..61094de 100644 --- a/client_classes/DiscordClient.py +++ b/client_classes/DiscordClient.py @@ -9,11 +9,16 @@ def __init__(self, compute_message_func): async def on_message(self, message): if message.author != self.user: + from_id = message.channel.id + text = message.content + author_id = message.author.id author_name = message.author.name - chat_name = None if type(message.channel) != discord.channel.DMChannel: chat_name = message.author.guild.name + "/" + message.channel.name - self.compute_massage(Message((message.channel.id, "DS"), message.content, message.author.id, author_name, chat_name)) + is_owner = message.author.guild_permissions.administrator + self.compute_massage(Message((from_id, "DS"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + self.compute_massage(Message((from_id, "DS"), text, author_id, author_name)) def send_msg(self, id, text): self.loop.create_task(self.get_channel(id).send(text)) diff --git a/client_classes/Message.py b/client_classes/Message.py index c302741..e006b45 100644 --- a/client_classes/Message.py +++ b/client_classes/Message.py @@ -1,21 +1,25 @@ class Message: - def __init__(self, from_id, text, author_id, author_name, chat_name): + def __init__(self, from_id, text, author_id, author_name, chat_name=None, is_owner=None): self.from_id = from_id self.text = text self.author_id = author_id self.author_name = author_name self.chat_name = chat_name + self.is_owner = is_owner def is_chat_command(self): return len(self.text) > 0 and self.text[0] == "!" + def get_chat_id(self): + return str(self.from_id[0]) + self.from_id[1] + def get_author_id(self): return str(self.author_id) + self.from_id[1] def get_chat_command(self): if not self.is_chat_command(): return None - return self.text[1:].strip().lower() + return self.text[1:].strip() def get_text_to_forwarding(self): description = "" diff --git a/client_classes/TelegramClient.py b/client_classes/TelegramClient.py index 5bf2e01..db852f3 100644 --- a/client_classes/TelegramClient.py +++ b/client_classes/TelegramClient.py @@ -12,11 +12,17 @@ def __init__(self, compute_message_func): def __handler(self): @self.client.message_handler(content_types=["text"]) def on_message(message): + from_id = message.chat.id + text = message.text + author_id = message.from_user.id + author = self.client.get_chat_member(from_id, author_id) author_name = message.from_user.last_name + " " + message.from_user.first_name - chat_name = None - if message.from_user.id != message.chat.id: + if from_id != author_id: chat_name = message.chat.title - self.compute_massage(Message((message.chat.id, "TG"), message.text, message.from_user.id, author_name, chat_name)) + is_owner = author.status == "creator" + self.compute_massage(Message((from_id, "TG"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + self.compute_massage(Message((from_id, "TG"), text, author_id, author_name)) self.client.infinity_polling() diff --git a/client_classes/VkClient.py b/client_classes/VkClient.py index 8b40e5b..ccf5cc3 100644 --- a/client_classes/VkClient.py +++ b/client_classes/VkClient.py @@ -28,15 +28,19 @@ def __handler(self): for event in longpoll.listen(): if event.type == VkBotEventType.MESSAGE_NEW: + text = event.object["message"]["text"] author = self.__get_user(event.object["message"]["from_id"]) + author_id = event.object["message"]["from_id"] author_name = author["last_name"] + " " + author["first_name"] - chat_id = event.object["message"]["from_id"] - chat_name = None if event.from_chat: - chat_id = event.chat_id + from_id = event.chat_id chat = self.__get_chat(event.chat_id) chat_name = chat["chat_settings"]["title"] - self.compute_massage(Message((chat_id, "VK"), event.object["message"]["text"], event.object["message"]["from_id"], author_name, chat_name)) + is_owner = author_id == chat["chat_settings"]["owner_id"] + self.compute_massage(Message((from_id, "VK"), text, author_id, author_name, chat_name=chat_name, is_owner=is_owner)) + else: + from_id = event.object["message"]["from_id"] + self.compute_massage(Message((from_id, "VK"), text, author_id, author_name)) def send_msg(self, id, text, to_chat): if to_chat: diff --git a/config.py b/config.py index e8e7e2b..49e36d8 100644 --- a/config.py +++ b/config.py @@ -5,8 +5,6 @@ TELEGRAM_TOKEN = "" -TOKEN_LENGTH = 10 - GRAPH_STORAGE_NAME = "data/graph.txt" ERROR_LOG_NAME = "data/error_log.txt" USERS_INFORMATION_DB_NAME = "data/users_inf.db" diff --git a/main.py b/main.py index 0a5a709..c71efa4 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ def main(): - bot = UsersHandler(config.GRAPH_STORAGE_NAME, config.USERS_INFORMATION_DB_NAME, token_length=config.TOKEN_LENGTH, error_log_name=config.ERROR_LOG_NAME) + bot = UsersHandler(config.GRAPH_STORAGE_NAME, config.USERS_INFORMATION_DB_NAME, error_log_name=config.ERROR_LOG_NAME) bot.run(vk_token=config.VK_TOKEN, vk_group_id=config.VK_GROUP_ID, telegram_token=config.TELEGRAM_TOKEN, discord_token=config.DISCORD_TOKEN) diff --git a/main_classes/Graph.py b/main_classes/Graph.py index 11e7a13..733e0fb 100644 --- a/main_classes/Graph.py +++ b/main_classes/Graph.py @@ -6,6 +6,7 @@ def __init__(self, graph_storage_name): graph_storage = open(graph_storage_name, "r") self.graph_storage_name = graph_storage_name self.adjacency_list = dict() + self.rules = dict() self.graph_storage_size = 0 for line in graph_storage.readlines(): type_operation, vertex1, vertex2 = self.__convert_text_to_operation(line) @@ -16,6 +17,10 @@ def __init__(self, graph_storage_name): self.erase_edge(vertex1, vertex2, save_operation=False) self.graph_storage_size += 1 + def __check_rule(self, vertex, text): + rule = self.rules[vertex] + return len(text) >= len(rule) and text[:len(rule)] == rule + def __convert_text_to_operation(self, text): text = text.split() return text[0], (int(text[1]), text[2]), (int(text[3]), text[4]) @@ -46,13 +51,16 @@ def __add_operation_to_storage(self, type_operation, vertex1, vertex2): if self.graph_storage_size >= len(self.adjacency_list) ** 2: self.__reset_graph_storage() - def get_reachable_vertices(self, vertex_start): + def get_reachable_vertices(self, vertex_start, text): used = set() used.add(vertex_start) q = queue.Queue() q.put(vertex_start) while not q.empty(): v = q.get() + if not self.__check_rule(v, text): + continue + for to in self.adjacency_list[v]: if to in used: continue @@ -65,8 +73,14 @@ def get_reachable_vertices(self, vertex_start): def add_vertex(self, vertex): if not (vertex in self.adjacency_list): + self.rules[vertex] = "" self.adjacency_list[vertex] = set() + def set_rule(self, id, rule): + vertex = (int(id[:-2]), id[-2:]) + self.add_vertex(vertex) + self.rules[vertex] = rule + def add_edge(self, vertex1, vertex2, save_operation=True): self.add_vertex(vertex1) self.add_vertex(vertex2) diff --git a/main_classes/UsersHandler.py b/main_classes/UsersHandler.py index 4322c79..0ad8103 100644 --- a/main_classes/UsersHandler.py +++ b/main_classes/UsersHandler.py @@ -1,7 +1,4 @@ -import threading import sqlite3 -import hashlib -import random from main_classes.Graph import Graph from client_classes.VkClient import VkClient from client_classes.TelegramClient import TelegramClient @@ -9,11 +6,10 @@ class UsersHandler: - def __init__(self, graph_storage_name, users_information_db_name, token_length=1, error_log_name=None): + def __init__(self, graph_storage_name, users_information_db_name, error_log_name=None): self.error_log_name = error_log_name self.graph = Graph(graph_storage_name) self.select_chat = {"chat_name": None, "chat_id": (None, None)} - self.token_length = token_length self.vk_client = VkClient(self.compute_message) self.discord_client = DiscordClient(self.compute_message) @@ -22,56 +18,37 @@ def __init__(self, graph_storage_name, users_information_db_name, token_length=1 self.users_information_db_name = users_information_db_name conn = sqlite3.connect(users_information_db_name) cursor = conn.cursor() - cursor.execute("CREATE TABLE IF NOT EXISTS users(user_id TEXT PRIMARY KEY, account_id INT);") - cursor.execute("CREATE TABLE IF NOT EXISTS names(name TEXT PRIMARY KEY, account_id INT);") - cursor.execute("CREATE TABLE IF NOT EXISTS accounts(account_id INT PRIMARY KEY, name TEXT, token TEXT, count INT);") + cursor.execute("CREATE TABLE IF NOT EXISTS users(id TEXT PRIMARY KEY, name TEXT, is_admin INT);") + cursor.execute("CREATE TABLE IF NOT EXISTS chat_rules(id TEXT PRIMARY KEY, rule TEXT);") conn.commit() - self.free_id = 0 - cursor.execute("SELECT * FROM accounts;") - for account in cursor.fetchall(): - self.free_id = max(self.free_id, account[0] + 1) - - def __create_token(self): - token = "" - for i in range(self.token_length): - cur = random.randint(0, 63) - if cur <= 9: - token += str(cur) - elif cur <= 35: - token += chr(ord("a") + cur - 10) - elif cur <= 61: - token += chr(ord("A") + cur - 36) - elif cur == 62: - token += "_" - else: - token += "-" - return token + cursor.execute("SELECT * FROM chat_rules;") + for rule in cursor.fetchall(): + self.graph.set_rule(rule[0], rule[1]) - def __add_user(self, msg): + def __add_user(self, id): conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") + cursor.execute("SELECT * FROM users WHERE id='" + id + "';") if cursor.fetchone() is None: - entry = (msg.get_author_id(), -1) - cursor.execute("INSERT INTO users VALUES(?, ?);", entry) + entry = (id, "None", 0) + cursor.execute("INSERT INTO users VALUES(?, ?, ?);", entry) conn.commit() def __get_user_name(self, msg): conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - info = cursor.fetchone() - if info[1] == -1: - return None - cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(info[1]) + ";") + cursor.execute("SELECT * FROM users WHERE id='" + msg.get_author_id() + str(msg.from_id[0]) + "';") return cursor.fetchone()[1] - def __is_login(self, msg): + def __is_admin(self, msg): + if msg.is_owner: + return True + conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - return cursor.fetchone()[1] != -1 + cursor.execute("SELECT * FROM users WHERE id='" + msg.get_author_id() + str(msg.from_id[0]) + "';") + return cursor.fetchone()[2] def __add_error_to_log(self, text): if self.error_log_name is not None: @@ -121,156 +98,115 @@ def __compute_command_disconnect(self, msg): self.__send(msg.from_id, select_id[1] + " chat with name " + self.select_chat["chat_name"] + " is disconnected.") self.__send(select_id, msg.from_id[1] + " chat with name " + msg.chat_name + " is disconnected.") - def __compute_command_create(self, msg): - if self.__is_login(msg): - return self.__send(msg.from_id, "To create a new account, you must log out of your current account.", to_chat=False) - - command = msg.text.split() - if len(command) == 1: - return self.__send(msg.from_id, "Error: To create an account, you need to provide an account name.", to_chat=False) - name = " ".join(command[1:]) - - conn = sqlite3.connect(self.users_information_db_name) - cursor = conn.cursor() - cursor.execute("SELECT * FROM names WHERE name='" + name + "';") - if cursor.fetchone() is not None: - return self.__send(msg.from_id, "Error: An account with the name '" + name + "' already exists.", to_chat=False) - - cursor.execute("UPDATE users SET account_id=" + str(self.free_id) + " WHERE user_id='" + msg.get_author_id() + "';") - entry = (name, self.free_id) - cursor.execute("INSERT INTO names VALUES(?, ?);", entry) - entry = (self.free_id, name, "?", 1) - cursor.execute("INSERT INTO accounts VALUES(?, ?, ?, ?);", entry) - conn.commit() - self.free_id += 1 - self.__send(msg.from_id, "An account with the name '" + name + "' has been created.", to_chat=False) + def __compute_command_get_id(self, msg): + self.__send(msg.from_id, "User id: " + msg.get_author_id(), to_chat=msg.chat_name is not None) - def __compute_command_login(self, msg): - if self.__is_login(msg): - return self.__send(msg.from_id, "To login, you must log out of your current account.", to_chat=False) + def __compute_command_set_admin(self, msg): + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") - command = msg.text.split() - if len(command) == 1: - return self.__send(msg.from_id, "Error: To enter an account, you need to provide a token.", to_chat=False) + command = msg.get_chat_command().split() if len(command) == 2: - return self.__send(msg.from_id, "Error: To enter an account, you need to provide an account name.", to_chat=False) - token = command[1] - name = " ".join(command[2:]) + return self.__send(msg.from_id, "Error: An user id is required.") + user_id = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM names WHERE name='" + name + "';") - names_info = cursor.fetchone() - if names_info is None: - return self.__send(msg.from_id, "Error: There is no account with the name '" + name + "'.", to_chat=False) - cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(names_info[1]) + ";") - account_info = cursor.fetchone() - if account_info[2] == "?" or account_info[2] != hashlib.sha512(token.encode()).hexdigest(): - return self.__send(msg.from_id, "Error: Incorrect token.", to_chat=False) - - cursor.execute("UPDATE users SET account_id=" + str(account_info[0]) + " WHERE user_id='" + msg.get_author_id() + "';") - cursor.execute("UPDATE accounts SET count=" + str(account_info[3] + 1) + " WHERE account_id=" + str(account_info[0]) + ";") + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: The account with id '" + user_id + "' already has administrator rights.") + + cursor.execute("UPDATE users SET is_admin=1 WHERE id='" + user_id + str(msg.from_id[0]) + "';") conn.commit() - self.__send(msg.from_id, "You are connected to the '" + name + "' account.", to_chat=False) + self.__send(msg.from_id, "The account with id '" + user_id + "' has been granted administrative rights.") - def __compute_command_logout(self, msg): - if not self.__is_login(msg): - return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + def __compute_command_delete_admin(self, msg): + if not msg.is_owner: + return self.__send(msg.from_id, "Error: You must be an owner to use this command.") + + command = msg.get_chat_command().split() + if len(command) == 2: + return self.__send(msg.from_id, "Error: An user id is required.") + user_id = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - user_info = cursor.fetchone() - cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(user_info[1]) + ";") - account_info = cursor.fetchone() + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if not cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: The account with id '" + user_id + "' does not have administrative rights.") - cursor.execute("UPDATE users SET account_id=-1 WHERE user_id='" + msg.get_author_id() + "';") - cursor.execute("UPDATE accounts SET count=" + str(account_info[3] - 1) + " WHERE account_id=" + str(account_info[0]) + ";") + cursor.execute("UPDATE users SET is_admin=0 WHERE id='" + user_id + str(msg.from_id[0]) + "';") conn.commit() - self.__send(msg.from_id, "You are disconnected from the account '" + account_info[1] + "'.", to_chat=False) - if account_info[3] == 1: - cursor.execute("DELETE FROM accounts WHERE account_id=" + str(account_info[0]) + ";") - cursor.execute("DELETE FROM names WHERE name='" + str(account_info[1]) + "';") - conn.commit() - self.__send(msg.from_id, "Account '" + account_info[1] + "' has been deleted.", to_chat=False) + self.__send(msg.from_id, "The account with id '" + user_id + "' no longer has administrative rights.") def __compute_command_rename(self, msg): - if not self.__is_login(msg): - return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") - command = msg.text.split() + command = msg.get_chat_command().split() if len(command) == 1: - return self.__send(msg.from_id, "Error: To rename an account, you need to provide an new account name.", to_chat=False) - name = " ".join(command[1:]) - - conn = sqlite3.connect(self.users_information_db_name) - cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - user_info = cursor.fetchone() - cursor.execute("SELECT * FROM names WHERE name='" + name + "';") - if cursor.fetchone() is not None: - return self.__send(msg.from_id, "Error: An account with the name '" + name + "' already exists.", to_chat=False) - cursor.execute("SELECT * FROM accounts WHERE account_id=" + str(user_info[1]) + ";") - account_info = cursor.fetchone() - - cursor.execute("UPDATE names SET name='" + name + "' WHERE name='" + account_info[1] + "';") - cursor.execute("UPDATE accounts SET name='" + name + "' WHERE account_id=" + str(account_info[0]) + ";") - conn.commit() - self.__send(msg.from_id, "The account name has been changed from '" + account_info[1] + "' to '" + name + "'.", to_chat=False) - - def __compute_command_get_token(self, msg): - if not self.__is_login(msg): - return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + return self.__send(msg.from_id, "Error: An user id is required.") + if len(command) == 2: + return self.__send(msg.from_id, "Error: A new name is required.") + user_id = command[1] + if user_id == "self": + user_id = msg.get_author_id() + name = " ".join(command[2:]) + self.__add_user(user_id + str(msg.from_id[0])) conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - user_info = cursor.fetchone() + cursor.execute("SELECT * FROM users WHERE id='" + user_id + str(msg.from_id[0]) + "';") + if user_id != msg.get_author_id() and cursor.fetchone()[2]: + return self.__send(msg.from_id, "Error: Forbidden to rename administrators.") - token = self.__create_token() - cursor.execute("UPDATE accounts SET token='" + hashlib.sha512(token.encode()).hexdigest() + "' WHERE account_id=" + str(user_info[1]) + ";") + cursor.execute("UPDATE users SET name='" + name + "' WHERE id='" + user_id + str(msg.from_id[0]) + "';") conn.commit() - self.__send(msg.from_id, "Token to connect to the account: " + token, to_chat=False) + self.__send(msg.from_id, "The account name has been changed to '" + name + "'.") + + def __compute_command_set_rule(self, msg): + if not self.__is_admin(msg): + return self.__send(msg.from_id, "Error: You must be an administrator to use this command.") - def __compute_command_delete_token(self, msg): - if not self.__is_login(msg): - return self.__send(msg.from_id, "Error: Your account has not been logged in.", to_chat=False) + command = msg.get_chat_command() + if len(command.split()) == 2 or command.count("\"") < 2: + return self.__send(msg.from_id, "Error: A new rule is required.") + rule = command[command.find("\"") + 1:command.rfind("\"")] conn = sqlite3.connect(self.users_information_db_name) cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE user_id='" + msg.get_author_id() + "';") - user_info = cursor.fetchone() - - cursor.execute("UPDATE accounts SET token='?' WHERE account_id=" + str(user_info[1]) + ";") + cursor.execute("UPDATE chat_rules SET rule='" + rule + "' WHERE id='" + msg.get_chat_id() + "';") conn.commit() - self.__send(msg.from_id, "The token for connecting to the account has been deleted.", to_chat=False) + self.graph.set_rule(msg.get_chat_id(), rule) + self.__send(msg.from_id, "The chat rule has been changed to '" + rule + "'.") def __compute_chat_command(self, msg): - command = msg.get_chat_command() - if command == "select": + command = msg.get_chat_command().split() + if len(command) >= 1 and command[0].lower() == "select": self.__compute_command_select(msg) - elif command == "connect": + elif len(command) >= 1 and command[0].lower() == "connect": self.__compute_command_connect(msg) - elif command == "disconnect": + elif len(command) >= 1 and command[0].lower() == "disconnect": self.__compute_command_disconnect(msg) + elif len(command) >= 2 and command[0].lower() == "set" and command[1].lower() == "admin": + self.__compute_command_set_admin(msg) + elif len(command) >= 2 and command[0].lower() == "delete" and command[1].lower() == "admin": + self.__compute_command_delete_admin(msg) + elif len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "id": + self.__compute_command_get_id(msg) + elif len(command) >= 1 and command[0].lower() == "rename": + self.__compute_command_rename(msg) + elif len(command) >= 2 and command[0].lower() == "set" and command[1].lower() == "rule": + self.__compute_command_set_rule(msg) else: self.__send(msg.from_id, "Error: Unknown instruction.") def __compute_user_command(self, msg): - self.__add_user(msg) command = msg.text.split() - if len(command) >= 1 and command[0].lower() == "create": - self.__compute_command_create(msg) - elif len(command) >= 1 and command[0].lower() == "login": - self.__compute_command_login(msg) - elif len(command) >= 1 and command[0].lower() == "logout": - self.__compute_command_logout(msg) - elif len(command) >= 1 and command[0].lower() == "rename": - self.__compute_command_rename(msg) - elif len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "token": - self.__compute_command_get_token(msg) - elif len(command) >= 2 and command[0].lower() == "delete" and command[1].lower() == "token": - self.__compute_command_delete_token(msg) + if len(command) >= 2 and command[0].lower() == "get" and command[1].lower() == "id": + self.__compute_command_get_id(msg) else: self.__send(msg.from_id, "Error: Unknown instruction.", to_chat=False) @@ -283,20 +219,16 @@ def run(self, vk_token=None, vk_group_id=None, telegram_token=None, discord_toke self.discord_client.run(discord_token) def compute_message(self, msg): - if msg.text == "": - return None - if msg.chat_name is None: - thread = threading.Thread(target=self.__compute_user_command, args=(msg, )) - thread.start() - return None + return self.__compute_user_command(msg) + self.__add_user(msg.get_author_id() + str(msg.from_id[0])) self.graph.add_vertex(msg.from_id) if msg.is_chat_command(): return self.__compute_chat_command(msg) name = self.__get_user_name(msg) - if name is not None: + if name != "None": msg.author_name = name - for send_id in self.graph.get_reachable_vertices(msg.from_id): + for send_id in self.graph.get_reachable_vertices(msg.from_id, msg.text): self.__send(send_id, msg.get_text_to_forwarding())