From 9d89aee5785a31b5e995efae31b407ba7711a684 Mon Sep 17 00:00:00 2001 From: armatik Date: Fri, 10 May 2024 18:18:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D1=80=D1=8B=D1=82=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=B1=D0=B5=D1=82=D0=B0=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20+=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D1=81=D1=8B=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B4=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- src/core/example_config.yaml | 21 ++++ src/core/logger.py | 24 +++++ src/core/routers.py | 13 ++- src/modules/external/yandexgpt/handlers.py | 10 +- src/modules/external/yandexgpt/routers.py | 2 +- src/modules/external/yandexgpt/yandexgpt.py | 89 +++++++++++------ src/modules/standard/admin/handlers.py | 35 ++++++- src/modules/standard/admin/routers.py | 8 +- src/modules/standard/config/config.py | 28 ++++++ src/modules/standard/database/db_api.py | 35 +++++-- src/modules/standard/filters/admin.py | 13 --- src/modules/standard/filters/filters.py | 29 ++++++ src/modules/standard/info/__init__.py | 0 src/modules/standard/info/handlers.py | 60 +++++++++++ src/modules/standard/info/routers.py | 8 ++ .../standard/message_processing/__init__.py | 0 .../message_processing/message_api.py | 99 +++++++++++++++++++ src/modules/standard/moderation/moderation.py | 90 +++++------------ src/modules/standard/welcome/handlers.py | 74 ++++++++++++++ src/modules/standard/welcome/routers.py | 10 ++ 21 files changed, 523 insertions(+), 128 deletions(-) create mode 100644 src/core/example_config.yaml create mode 100644 src/core/logger.py delete mode 100644 src/modules/standard/filters/admin.py create mode 100644 src/modules/standard/filters/filters.py create mode 100644 src/modules/standard/info/__init__.py create mode 100644 src/modules/standard/info/handlers.py create mode 100644 src/modules/standard/info/routers.py create mode 100644 src/modules/standard/message_processing/__init__.py create mode 100644 src/modules/standard/message_processing/message_api.py create mode 100644 src/modules/standard/welcome/handlers.py create mode 100644 src/modules/standard/welcome/routers.py diff --git a/.gitignore b/.gitignore index 344a92f..cac6286 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ env venv __pycache__ OCAB.db -paths.json \ No newline at end of file +src/paths.json +src/core/config.yaml \ No newline at end of file diff --git a/src/core/example_config.yaml b/src/core/example_config.yaml new file mode 100644 index 0000000..927ef62 --- /dev/null +++ b/src/core/example_config.yaml @@ -0,0 +1,21 @@ +TELEGRAM: + TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxx + APPROVED_CHAT_ID: "-123456789 | -012345678" + ADMINCHATID: -12345678 + DEFAULT_CHAT_TAG: "@alt_gnome_chat" + CHECK_BOT: True + +YANDEXGPT: + TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxx + TOKEN_FOR_REQUEST: 8000 + TOKEN_FOR_ANSWER: 2000 + CATALOGID: xxxxxxxxxxxxxxxxxxxxxxxxx + PROMPT: "Ты чат-бот ..." + STARTWORD: "Бот| Бот, | бот | бот," + INWORD: "помогите | не работает" + +ROLES: + ADMIN: 2 + MODERATOR: 1 + USER: 0 + BOT: 3 \ No newline at end of file diff --git a/src/core/logger.py b/src/core/logger.py new file mode 100644 index 0000000..4b190eb --- /dev/null +++ b/src/core/logger.py @@ -0,0 +1,24 @@ +import os +import time + +async def check_log_file(): + # Проверка наличия файла для логов в формате log-dd-mm-yyyy.log + # Если файл существует, то pass + # Если файл не существует, то создаём его. файл лежит в директории src.core.log + current_data = time.strftime("%d-%m-%Y") + log_file = os.path.join(os.path.dirname(__file__), "log/", f"log-{current_data}.log") + if not os.path.exists(log_file): + with open(log_file, 'w') as file: + file.write("Log file created\n") + file.close() + else: + pass + +async def log(message): + await check_log_file() + current_data = time.strftime("%d-%m-%Y") + log_file = os.path.join(os.path.dirname(__file__), "log/", f"log-{current_data}.log") + # print(log_file) + with open(log_file, 'a') as file: + file.write(f"{time.strftime('%H:%M:%S')} {message}\n") + file.close() diff --git a/src/core/routers.py b/src/core/routers.py index 5b67a74..2957e57 100644 --- a/src/core/routers.py +++ b/src/core/routers.py @@ -1,7 +1,10 @@ -from aiogram import Dispatcher +from aiogram import Dispatcher, Router, F, Bot +from aiogram.types import Message +from src.modules.standard.info.routers import router as info_router from src.modules.standard.admin.routers import router as admin_router -from src.modules.external.yandexgpt.routers import router as yandexgpt_router +from src.modules.standard.message_processing.message_api import router as process_message +from src.modules.standard.welcome.routers import router as welcome_router async def include_routers(dp: Dispatcher): @@ -9,5 +12,7 @@ async def include_routers(dp: Dispatcher): Подключение роутеров в бота dp.include_router() """ - dp.include_router(yandexgpt_router) - dp.include_router(admin_router) \ No newline at end of file + dp.include_router(info_router) + dp.include_router(admin_router) + dp.include_router(process_message) + \ No newline at end of file diff --git a/src/modules/external/yandexgpt/handlers.py b/src/modules/external/yandexgpt/handlers.py index b8696c3..7369be9 100644 --- a/src/modules/external/yandexgpt/handlers.py +++ b/src/modules/external/yandexgpt/handlers.py @@ -2,12 +2,18 @@ from aiogram import Bot from aiogram.types import Message from src.modules.external.yandexgpt.yandexgpt import * from src.modules.standard.config.config import get_yandexgpt_token, get_yandexgpt_catalog_id, get_yandexgpt_prompt +from src.modules.standard.database.db_api import add_message +from src.core.logger import log import asyncio async def answer_to_message(message: Message, bot: Bot): + # print("answer_to_message") + await log("answer_to_message") yagpt = YandexGPT(get_yandexgpt_token(), get_yandexgpt_catalog_id()) text = message.text prompt = get_yandexgpt_prompt() - response = await yagpt.async_yandexgpt(system_prompt=prompt, input_messages=text) - await message.reply(response, parse_mode="Markdown") + # response = await yagpt.async_yandexgpt(system_prompt=prompt, input_messages=text) + response = await yagpt.yandexgpt_request(chat_id = message.chat.id, message_id = message.message_id, type = "yandexgpt") + reply = await message.reply(response, parse_mode="Markdown") + add_message(reply, message_ai_model="yandexgpt") diff --git a/src/modules/external/yandexgpt/routers.py b/src/modules/external/yandexgpt/routers.py index b364b2b..881aab9 100644 --- a/src/modules/external/yandexgpt/routers.py +++ b/src/modules/external/yandexgpt/routers.py @@ -3,5 +3,5 @@ from aiogram import Router, F from src.modules.external.yandexgpt.handlers import answer_to_message router = Router() -# Если сообщение содержит в начале текст "Гномик" или "гномик", то вызывается функция answer_to_message +# Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message router.message.register(answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик")) \ No newline at end of file diff --git a/src/modules/external/yandexgpt/yandexgpt.py b/src/modules/external/yandexgpt/yandexgpt.py index e334311..3ded6dc 100644 --- a/src/modules/external/yandexgpt/yandexgpt.py +++ b/src/modules/external/yandexgpt/yandexgpt.py @@ -2,6 +2,7 @@ import requests import json import asyncio import aiohttp +from src.core.logger import log from ...standard.database import * from ...standard.config.config import * @@ -23,28 +24,44 @@ class YandexGPT: self.token = token self.catalog_id = catalog_id - async def async_token_check(self, messages, gpt, max_tokens, del_msg_id=1): - url = "https://llm.api.cloud.yandex.net/foundationModels/v1/tokenize" - while True: - text = "" - for message in messages: - text += message["text"] - try: - response = requests.post(url, json={"model": gpt, "text": text}) - except Exception as e: # TODO: Переделать обработку ошибок - print(e) - continue - if int(response.text) < max_tokens - 2000: - break - else: - messages.pop(del_msg_id) - return messages - async def async_request(self, url, headers, prompt) -> dict: async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers, json=prompt) as response: return await response.json() + async def async_token_check(self, messages, gpt, max_tokens, stream, temperature, del_msg_id=1): + url = "https://llm.api.cloud.yandex.net/foundationModels/v1/tokenizeCompletion" + headers = { + "Content-Type": "application/json", + "Authorization": f"Api-Key {self.token}" + } + answer_token = get_yandexgpt_token_for_answer() + while True: + try: + request = { + "modelUri": gpt, + "completionOptions": { + "stream": stream, + "temperature": temperature, + "maxTokens": max_tokens + }, + "messages": messages + } + response = await self.async_request(url=url, headers=headers, prompt=request) + except Exception as e: # TODO: Переделать обработку ошибок + # print(e) + await log(f"Error: {e}") + + continue + if int(len(response["tokens"])) < (max_tokens - answer_token): + break + else: + try: + messages.pop(del_msg_id) + except IndexError: + Exception("IndexError: list index out of range") + return messages + async def async_yandexgpt_lite(self, system_prompt, input_messages, stream=False, temperature=0.6, max_tokens=8000): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" gpt = f"gpt://{self.catalog_id}/yandexgpt-lite/latest" @@ -71,7 +88,14 @@ class YandexGPT: response = requests.post(url, headers=headers, json=prompt).text return json.loads(response)["result"]["alternatives"][0]["message"]["text"] - async def async_yandexgpt(self, system_prompt, input_messages, stream=False, temperature=0.6, max_tokens=8000): + async def async_yandexgpt( + self, + system_prompt, + input_messages, + stream=False, + temperature=0.6, + max_tokens=get_yandexgpt_token_for_request() + ): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" gpt = f"gpt://{self.catalog_id}/yandexgpt/latest" headers = { @@ -79,13 +103,14 @@ class YandexGPT: "Authorization": f"Api-Key {self.token}" } - messages = [{"role": "system", "text": system_prompt}] - messages.append({"role": "user", "text": input_messages}) - # for message in input_messages: - # messages.append(message) - # messages = await self.async_token_check(messages, gpt, max_tokens) + messages = [] + messages.append({"role": "system", "text": system_prompt}) + for message in input_messages: + messages.append(message) - prompt = { + messages = await self.async_token_check(messages, gpt, max_tokens, stream, temperature) + + request = { "modelUri": gpt, "completionOptions": { "stream": stream, @@ -94,7 +119,7 @@ class YandexGPT: }, "messages": messages } - response = await self.async_request(url=url, headers=headers, prompt=prompt) + response = await self.async_request(url=url, headers=headers, prompt=request) return response["result"]["alternatives"][0]["message"]["text"] @@ -153,21 +178,21 @@ class YandexGPT: # TODO: Сделать функцию Vision return 0 - async def collect_messages(self, message_id, chat_id, start_message_id): + async def collect_messages(self, message_id, chat_id): messages = [] # Собираем цепочку сообщений в формате: [{"role": "user", "text": "<Имя_пользователя>: Привет!"}, # {"role": "assistant", "text": "Привет!"}] while True: message = db_api.get_message_text(chat_id, message_id) - if db_api.get_message_ai_model(chat_id, start_message_id) != None: + if db_api.get_message_ai_model(chat_id, message_id) != None: messages.append({"role": "assistant", "text": message}) else: - sender_name = db_api.get_user_name(db_api.get_message_sender_id(chat_id, start_message_id)) + sender_name = db_api.get_user_name(db_api.get_message_sender_id(chat_id, message_id)) messages.append({"role": "user", "text": sender_name + ": " + message}) - message_id = db_api.get_message_answer_to_message_id(chat_id, message_id) + message_id = db_api.get_answer_to_message_id(chat_id, message_id) if message_id is None: break - return messages.reverse() + return list(reversed(messages)) async def collecting_messages_for_history(self, start_message_id, end_message_id, chat_id): messages = [] @@ -195,11 +220,13 @@ class YandexGPT: stream=False, temperature=0.6, max_tokens=8000 ) elif type == "yandexgpt": + # print("yandexgpt_request") + await log("yandexgpt_request") messages = await self.collect_messages(message_id, chat_id) return await self.async_yandexgpt( system_prompt=get_yandexgpt_prompt(), input_messages=messages, - stream=False, temperature=0.6, max_tokens=8000 + stream=False, temperature=0.6, max_tokens=get_yandexgpt_token_for_request() ) elif type == "yandexgpt-translate": return await self.async_yandexgpt_translate( diff --git a/src/modules/standard/admin/handlers.py b/src/modules/standard/admin/handlers.py index ef6afa2..4c197f8 100644 --- a/src/modules/standard/admin/handlers.py +++ b/src/modules/standard/admin/handlers.py @@ -1,5 +1,7 @@ from aiogram import Bot from aiogram.types import Message +from src.modules.standard.config.config import get_default_chat_tag +import time async def delete_message(message: Message, bot: Bot): @@ -10,4 +12,35 @@ async def error_access(message: Message, bot: Bot): await message.reply("Вы не админ/модератор") async def get_chat_id(message: Message, bot: Bot): - await message.reply(f"ID данного чата: `{message.chat.id}`", parse_mode="MarkdownV2") \ No newline at end of file + await message.reply(f"ID данного чата: `{message.chat.id}`", parse_mode="MarkdownV2") + +async def chat_not_in_approve_list(message: Message, bot: Bot): + await message.reply( + f"Бот недоступен в данном чате, пожалуйста," + f" обратитесь к администратору для добавления чата в список доступных или перейдите в чат " + f"{get_default_chat_tag()}" + ) + await get_chat_id(message, bot) + +async def mute_user(chat_id: int, user_id: int, time: int, bot: Bot): + # *, can_send_messages: bool | None = None, can_send_audios: bool | None = None, can_send_documents: bool | None = None, can_send_photos: bool | None = None, can_send_videos: bool | None = None, can_send_video_notes: bool | None = None, can_send_voice_notes: bool | None = None, can_send_polls: bool | None = None, can_send_other_messages: bool | None = None, can_add_web_page_previews: bool | None = None, can_change_info: bool | None = None, can_invite_users: bool | None = None, can_pin_messages: bool | None = None, can_manage_topics: bool | None = None, **extra_data: Any) + + mutePermissions = { + "can_send_messages": False, + "can_send_audios": False, + "can_send_documents": False, + "can_send_photos": False, + "can_send_videos": False, + "can_send_video_notes": False, + "can_send_voice_notes": False, + "can_send_polls": False, + "can_send_other_messages": False, + "can_add_web_page_previews": False, + "can_change_info": False, + "can_invite_users": False, + "can_pin_messages": False, + "can_manage_topics": False + } + end_time = time + int(time.time()) + await bot.restrict_chat_member(chat_id, user_id, until_date=end_time, **mutePermissions) + diff --git a/src/modules/standard/admin/routers.py b/src/modules/standard/admin/routers.py index 63df570..4d8c8d0 100644 --- a/src/modules/standard/admin/routers.py +++ b/src/modules/standard/admin/routers.py @@ -1,9 +1,13 @@ from aiogram import Router, F -from src.modules.standard.admin.handlers import delete_message, error_access, get_chat_id -from src.modules.standard.filters.admin import ChatModerOrAdminFilter +from src.modules.standard.admin.handlers import delete_message, error_access, get_chat_id, chat_not_in_approve_list +from src.modules.standard.filters.filters import ChatModerOrAdminFilter, ChatNotInApproveFilter router = Router() + +# Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list +router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text) + router.message.register(get_chat_id, ChatModerOrAdminFilter(), F.text == '/chatID') router.message.register(delete_message, ChatModerOrAdminFilter(), F.text == '/rm') diff --git a/src/modules/standard/config/config.py b/src/modules/standard/config/config.py index 3fc2f21..c851975 100644 --- a/src/modules/standard/config/config.py +++ b/src/modules/standard/config/config.py @@ -17,6 +17,20 @@ config = get_config() def get_telegram_token() -> str: return config["TELEGRAM"]["TOKEN"] +def get_telegram_check_bot() -> bool: + return config["TELEGRAM"]["CHECK_BOT"] + +def get_aproved_chat_id() -> list: + # Возваращем сплитованный список id чатов в формате int + return [int(chat_id) for chat_id in config["TELEGRAM"]["APPROVED_CHAT_ID"].split(" | ")] + +def get_user_role_name(role_number) -> dict: + # Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно + return config["ROLES"].get(role_number, "Неизвестно") + +def get_default_chat_tag() -> str: + return config["TELEGRAM"]["DEFAULT_CHAT_TAG"] + def get_yandexgpt_token() -> str: return config["YANDEXGPT"]["TOKEN"] @@ -26,7 +40,21 @@ def get_yandexgpt_catalog_id() -> str: def get_yandexgpt_prompt() -> str: return config["YANDEXGPT"]["PROMPT"] +def get_yandexgpt_start_words() -> list: + return config["YANDEXGPT"]["STARTWORD"].split(" | ") + +def get_yandexgpt_in_words() -> list: + return config["YANDEXGPT"]["INWORD"].split(" | ") + +def get_yandexgpt_token_for_request() -> int: + return config["YANDEXGPT"]["TOKEN_FOR_REQUEST"] + +def get_yandexgpt_token_for_answer() -> int: + return config["YANDEXGPT"]["TOKEN_FOR_ANSWER"] + def get_access_rights() -> dict: return get_config()["ACCESS_RIGHTS"] + + diff --git a/src/modules/standard/database/db_api.py b/src/modules/standard/database/db_api.py index 5663d6d..92f4527 100644 --- a/src/modules/standard/database/db_api.py +++ b/src/modules/standard/database/db_api.py @@ -6,6 +6,7 @@ from .models.chat_stats import ChatStats from ....service import paths from ..exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName import peewee as pw +from aiogram.types import Message def connect_database(is_test: bool = False, module: str | None = None): @@ -39,7 +40,7 @@ def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0): chat, created = Chats.get_or_create(id=chat_id, defaults={ 'chat_name': chat_name, 'chat_type': chat_type, - 'chat_stats': chat_stats, + 'chat_all_stat': chat_stats, }) if not created: # Обновить существующий чат, если он уже существует @@ -49,7 +50,12 @@ def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0): chat.save() -def add_user(user_id, user_name, user_tag=None, user_role=0, user_stats=0, user_rep=0): +def add_user(user_id, user_first_name, user_last_name=None, user_tag=None, user_role=0, user_stats=0, user_rep=0): + if user_last_name is None: + user_name = user_first_name + else: + user_name = user_first_name + " " + user_last_name + user, created = Users.get_or_create(id=user_id, defaults={ 'user_tag': user_tag, 'user_name': user_name, @@ -67,15 +73,18 @@ def add_user(user_id, user_name, user_tag=None, user_role=0, user_stats=0, user_ user.save() -def add_message(message_chat_id, message_id, message_sender_id, message_text, answer_to_message_id=None, - message_ai_model=None): +def add_message(message: Message, message_ai_model=None): + if message.reply_to_message: + answer_to_message_id = message.reply_to_message.message_id + else: + answer_to_message_id = None Messages.create( - message_chat_id=message_chat_id, - message_id=message_id, - message_sender_id=message_sender_id, + message_chat_id=message.chat.id, + message_id=message.message_id, + message_sender_id=message.from_user.id, answer_to_message_id=answer_to_message_id, message_ai_model=message_ai_model, - message_text=message_text + message_text=message.text ) @@ -127,6 +136,10 @@ def get_user_tag(user_id): user = Users.get_or_none(Users.id == user_id) return user.user_tag if user else None +def get_user_id(user_tag): + user = Users.get_or_none(Users.user_tag == user_tag) + return user.id if user else None + def get_user_name(user_id): user = Users.get_or_none(Users.id == user_id) @@ -148,7 +161,11 @@ def get_user_rep(user_id): return user.user_rep if user else None -def change_user_name(user_id, new_user_name): +def change_user_name(user_id, user_first_name, user_last_name=None): + if user_last_name is None: + new_user_name = user_first_name + else: + new_user_name = user_first_name + " " + user_last_name query = Users.update(user_name=new_user_name).where(Users.id == user_id) query.execute() diff --git a/src/modules/standard/filters/admin.py b/src/modules/standard/filters/admin.py deleted file mode 100644 index a6fa917..0000000 --- a/src/modules/standard/filters/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -from aiogram.filters import BaseFilter -from aiogram.types import Message -from aiogram import Bot - -from src.modules.standard.roles.roles import Roles - -class ChatModerOrAdminFilter(BaseFilter): - async def __call__(self, message: Message, bot: Bot) -> bool: - user_id = message.from_user.id - roles = Roles() - admins = await bot.get_chat_administrators(message.chat.id) - return await roles.check_admin_permission(user_id) or \ - await roles.check_moderator_permission(user_id) or any(user_id == admin.user.id for admin in admins) diff --git a/src/modules/standard/filters/filters.py b/src/modules/standard/filters/filters.py new file mode 100644 index 0000000..7bccb50 --- /dev/null +++ b/src/modules/standard/filters/filters.py @@ -0,0 +1,29 @@ +from aiogram.filters import BaseFilter +from aiogram.types import Message +from aiogram import Bot + +from src.modules.standard.roles.roles import Roles +from src.modules.standard.config.config import get_aproved_chat_id +from src.core.logger import log + +class ChatModerOrAdminFilter(BaseFilter): + async def __call__(self, message: Message, bot: Bot) -> bool: + user_id = message.from_user.id + roles = Roles() + admins = await bot.get_chat_administrators(message.chat.id) + return await roles.check_admin_permission(user_id) or \ + await roles.check_moderator_permission(user_id) or any(user_id == admin.user.id for admin in admins) + +class ChatNotInApproveFilter(BaseFilter): + async def __call__(self, message: Message, bot: Bot) -> bool: + # print("chat_check") + await log("chat_check") + chat_id = message.chat.id + if chat_id in get_aproved_chat_id(): + # print(f"Chat in approve list: {chat_id}") + await log(f"Chat in approve list: {chat_id}") + return False + else: + # print(f"Chat not in approve list: {chat_id}") + await log(f"Chat not in approve list: {chat_id}") + return True diff --git a/src/modules/standard/info/__init__.py b/src/modules/standard/info/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/standard/info/handlers.py b/src/modules/standard/info/handlers.py new file mode 100644 index 0000000..6a658b0 --- /dev/null +++ b/src/modules/standard/info/handlers.py @@ -0,0 +1,60 @@ +from aiogram import Bot +from aiogram.types import Message +from src.modules.standard.config.config import get_user_role_name +from src.modules.standard.roles.roles import Roles +from src.modules.standard.database.db_api import * +from src.core.logger import log + +async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int): + if get_message_ai_model(message.chat.id, message.message_id) is not None: + await message.reply("Это сообщение было сгенерировано ботом используя модель: " + get_message_ai_model(message.chat.id, message.message_id)) + elif user_id == bot.id: + await message.reply("Это сообщение было отправлено ботом") + elif get_user(user_id) is None: + await message.reply("Пользователь не найден") + # print(get_user(user_id)) + await log(f"Пользователь не найден: {user_id}, {get_user(user_id)}") + else: + roles = Roles() + answer = ( + f"Пользователь: {get_user_name(user_id)}\n" + f"Роль: {await roles.get_role_name(role_id=get_user_role(user_id))}\n" + f"Тег: @{get_user_tag(user_id)}\n" + f"Кол-во сообщений: {get_user_all_stats(user_id)}\n" + f"Репутация: {get_user_rep(user_id)}" + ) + await message.reply(answer) + + +async def get_user_info(message: Message, bot: Bot): + # Проверяем содержимое сообщения, если содержит вторым элементом тег пользователя, то выводим информацию о нем + # Если сообщение отвечает на другое сообщение, то выводим информацию о пользователе, на чье сообщение был ответ + # Если это бот то выводим информацию что это бот и какая модель yandexgpt используется + try: + if len(message.text.split()) > 1 and message.text.split()[1].startswith("@"): + user_tag = message.text.split()[1][1:] + user_id = get_user_id(user_tag) + if user_id: + await get_info_answer_by_id(message, bot, user_id) + else: + await message.reply(f"Пользователь с тегом @{user_tag} не найден") + elif message.reply_to_message: + user_id = message.reply_to_message.from_user.id + await get_info_answer_by_id(message, bot, user_id) + else: + await get_info_answer_by_id(message, bot, message.from_user.id) + except Exception as e: + await message.reply("В вашем запросе что-то пошло не так," + " попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение") + # print(e) + await log(e) + +async def get_chat_info(message: Message, bot: Bot): + answer = ( + f"*Название чата:* {message.chat.title}\n" + f"*ID чата:* `{message.chat.id}`\n \n" + f"*Суммарное количество сообщений в чате:* {get_chat_all_stat(message.chat.id)}\n" + f"*Количество пользователей в чате:* {await bot.get_chat_member_count(message.chat.id)}\n" + f"*Количество администраторов в чате:* {len(await bot.get_chat_administrators(message.chat.id))}" + ) + await message.reply(answer, parse_mode="MarkdownV2") \ No newline at end of file diff --git a/src/modules/standard/info/routers.py b/src/modules/standard/info/routers.py new file mode 100644 index 0000000..957930f --- /dev/null +++ b/src/modules/standard/info/routers.py @@ -0,0 +1,8 @@ +from aiogram import Router, F + +from src.modules.standard.info.handlers import get_user_info, get_chat_info + +router = Router() + +router.message.register(get_user_info, F.text.startswith("/info") == True) +router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True) \ No newline at end of file diff --git a/src/modules/standard/message_processing/__init__.py b/src/modules/standard/message_processing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/standard/message_processing/message_api.py b/src/modules/standard/message_processing/message_api.py new file mode 100644 index 0000000..028a2cf --- /dev/null +++ b/src/modules/standard/message_processing/message_api.py @@ -0,0 +1,99 @@ +from aiogram import Router, F, Bot, types + +from src.modules.external.yandexgpt.handlers import answer_to_message +from src.modules.standard.database.db_api import * +from src.modules.standard.config.config import get_yandexgpt_start_words, get_yandexgpt_in_words, get_aproved_chat_id +from src.core.logger import log + + +async def chat_check(message: types.Message): + # Проверка наличия id чата в базе данных чатов + # Если чата нет в базе данных, то проверяем его в наличии в конфиге и если он там есть то добавляем его в БД + # Если чат есть в базе данных, то pass + if get_chat(message.chat.id) is None: + if message.chat.id in get_aproved_chat_id(): + # print(f"Chat in approve list: {message.chat.id} {message.chat.title}") + await log(f"Chat in approve list: {message.chat.id} {message.chat.title}") + add_chat(message.chat.id, message.chat.title) + # print(f"Chat added: {message.chat.id} {message.chat.title}") + await log(f"Chat added: {message.chat.id} {message.chat.title}") + else: + # print(f"Chat not in approve list: {message.chat.id} {message.chat.title}") + await log(f"Chat not in approve list: {message.chat.id} {message.chat.title}") + pass + else: + # Проверяем обновление названия чата + chat = get_chat(message.chat.id) + if chat.chat_name != message.chat.title: + chat.chat_name = message.chat.title + chat.save() + # print(f"Chat updated: {message.chat.id} {message.chat.title}") + await log(f"Chat updated: {message.chat.id} {message.chat.title}") + else: + # print(f"Chat already exists: {message.chat.id} {message.chat.title}") + await log(f"Chat already exists: {message.chat.id} {message.chat.title}") + pass + +async def user_check(message: types.Message): + # Проверка наличия id пользователя в базе данных пользователей + # Если пользователя нет в базе данных, то добавляем его + # Если пользователь есть в базе данных, то pass + current_user_name = "" + if message.from_user.last_name is None: + current_user_name = message.from_user.first_name + else: + current_user_name = message.from_user.first_name + " " + message.from_user.last_name + + if get_user(message.from_user.id) is None: + add_user(message.from_user.id, + message.from_user.first_name, message.from_user.last_name, + message.from_user.username) + # print(f"User added: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name}") + await log(f"User added: {message.from_user.id} {current_user_name}") + else: + # print(f"User already exists: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name} {message.from_user.username}") + await log(f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}") + # Проверяем обновление имени пользователя + if get_user_name(message.from_user.id) != current_user_name: + change_user_name(message.from_user.id, current_user_name) + # print(f"User updated: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name}") + await log(f"User name updated: {message.from_user.id} {current_user_name}") + # Проверяем обновление username пользователя + if get_user_tag(message.from_user.id) != message.from_user.username: + change_user_tag(message.from_user.id, message.from_user.username) + # print(f"User updated: {message.from_user.id} {message.from_user.username}") + await log(f"User tag updated: {message.from_user.id} {message.from_user.username}") + pass + +async def add_stats(message: types.Message): + # Добавляем пользователю и чату статистику + update_chat_all_stat(message.chat.id) + update_user_all_stat(message.from_user.id) + + +async def message_processing(message: types.Message, bot: Bot): + await chat_check(message) + await user_check(message) + await add_stats(message) + + add_message(message) + # Если сообщение в начале содержит слово из списка или внутри сообщения содержится слово из списка или сообщение отвечает на сообщение бота + + if ((message.text.split(" ")[0] in get_yandexgpt_start_words()) + or (any(word in message.text for word in get_yandexgpt_in_words()))): + # print("message_processing") + await log("message_processing") + await answer_to_message(message, bot) + + elif message.reply_to_message is not None: + if message.reply_to_message.from_user.is_bot: + # print("message_processing") + await log("message_processing") + await answer_to_message(message, bot) + + + + +router = Router() +# Если сообщение содержит текст то вызывается функция message_processing +router.message.register(message_processing, F.text) \ No newline at end of file diff --git a/src/modules/standard/moderation/moderation.py b/src/modules/standard/moderation/moderation.py index 1cba35e..da78c74 100644 --- a/src/modules/standard/moderation/moderation.py +++ b/src/modules/standard/moderation/moderation.py @@ -45,69 +45,31 @@ class Moderation: async def ban_user(self, chat_id, user_id, bot: aiogram.Bot): await bot.ban_chat_member(chat_id, user_id) - async def mute_user(self, message, chat_id, user_id, bot: aiogram.Bot, time=0): - # Проверка что отправитель является администратором чата - # Проверяем мин - try: - if check_admin_permission(message.from_user.id): - # Проверка отвечает ли сообщение на другое сообщение - if message.reply_to_message is not None: - time = message.text.split(' ')[1] - # получаем id отправителя сообщение на которое отвечает message - target_id = message.reply_to_message.from_user.id - target_name = \ - cursor.execute("SELECT user_name FROM user_list WHERE user_id = ?", (target_id,)).fetchone()[0] - # Проверка, что человек пользователь - if cursor.execute("SELECT user_role FROM user_list WHERE user_id = ?", (target_id,)).fetchone()[ - 0] == 0: - # ограничения прав пользователя по отправке сообщений на time секунд - time_sec = await time_to_seconds(message.text.split(' ')[1]) - if time_sec <= 30 or time_sec >= 31536000: - await message.reply("Время мута должно быть больше 30 секунд и меньше 365 дней") - return - date_string = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - date_time = datetime.datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S") - unix_time = int(mktime(date_time.timetuple())) - await bot.restrict_chat_member(message.chat.id, target_id, until_date=unix_time + time_sec, - permissions=types.ChatPermissions(can_send_messages=False)) - await message.reply( - f"Пользователь {target_name} замьючен на {await short_time_to_time(time)}") - else: - await message.reply( - f"Пользователь [{target_name}](tg://user?id={target_id}) является {await get_role(target_id)} и не может быть замьючен", - parse_mode='Markdown') - return - else: - target_tag = message.text.split(' ')[1] - target_tag = target_tag[1:] - target_id = int( - cursor.execute("SELECT user_id FROM user_list WHERE user_name = ?", (target_tag,)).fetchone()[ - 0]) - target_name = \ - cursor.execute("SELECT user_name FROM user_list WHERE user_id = ?", (target_id,)).fetchone()[0] - # ограничения прав пользователя по отправке сообщений на time секунд - time_mute = message.text.split(' ')[2] - - if cursor.execute("SELECT user_role FROM user_list WHERE user_id = ?", (target_id,)).fetchone()[ - 0] == 0: - # ограничения прав пользователя по отправке сообщений на time секунд - time_sec = await time_to_seconds(message.text.split(' ')[2]) - if time_sec <= 30 or time_sec >= 31536000: - await message.reply("Время мута должно быть больше 30 секунд и меньше 365 дней") - return - date_string = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - date_time = datetime.datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S") - unix_time = int(mktime(date_time.timetuple())) - await bot.restrict_chat_member(message.chat.id, target_id, until_date=unix_time + time_sec, - permissions=types.ChatPermissions(can_send_messages=False)) - await message.reply( - f"Пользователь {target_name} замьючен на {await short_time_to_time(time_mute)}.") - else: - await message.reply( - f"Пользователь [{target_name}](tg://user?id={target_id}) является {await get_role(target_id)} и не может быть замьючен", - parse_mode="Markdown") - return - except: - await message.reply("Ошибка данных. Возможно пользователь ещё ничего не написал.") +async def mute_user(chat_id, user_id, time, bot: aiogram.Bot): + mutePermissions = { + "can_send_messages": False, + "can_send_audios": False, + "can_send_documents": False, + "can_send_photos": False, + "can_send_videos": False, + "can_send_video_notes": False, + "can_send_voice_notes": False, + "can_send_polls": False, + "can_send_other_messages": False, + "can_add_web_page_previews": False, + "can_change_info": False, + "can_invite_users": False, + "can_pin_messages": False, + "can_manage_topics": False + } + end_time = time + int(time.time()) + await bot.restrict_chat_member(chat_id, user_id, until_date=end_time, **mutePermissions) + + +async def unmute_user(chat_id, user_id, bot: aiogram.Bot): + await bot.restrict_chat_member(chat_id, user_id, use_independent_chat_permissions=True) + +async def ban_user(chat_id, user_id, bot: aiogram.Bot): + await bot.ban_chat_member(chat_id, user_id) diff --git a/src/modules/standard/welcome/handlers.py b/src/modules/standard/welcome/handlers.py new file mode 100644 index 0000000..397a107 --- /dev/null +++ b/src/modules/standard/welcome/handlers.py @@ -0,0 +1,74 @@ +from aiogram import Bot +from aiogram.types import Message +from src.modules.standard.config.config import get_telegram_check_bot +from src.modules.standard.roles.roles import Roles +from src.modules.standard.database.db_api import * +from src.modules.standard.moderation.moderation import mute_user, unmute_user, ban_user +from aiogram.utils.keyboard import InlineKeyboardBuilder +from aiogram.types import inline_keyboard_button as types +import random, asyncio +from threading import Thread + +async def create_math_task(): + first_number = random.randint(1, 100) + second_number = random.randint(1, 100) + answer = first_number + second_number + fake_answers = [] + for i in range(3): + diff = random.randint(1, 10) + diff_sign = random.choice(["+", "-"]) + fake_answers.append(answer + diff if diff_sign == "+" else answer - diff) + fake_answers.append(answer) + random.shuffle(fake_answers) + return [answer, first_number, second_number, fake_answers] + +async def ban_user_timer(chat_id: int, user_id: int, time: int, bot: Bot): + await asyncio.sleep(time) + if get_user(user_id) is not None: + pass + else: + await ban_user() + + + + +async def check_new_user(message: Message, bot: Bot): + print("check_new_user") + if get_telegram_check_bot(): + # Проверяем наличие пользователя в базе данных + + if get_user(message.from_user.id) is None: + # Выдаём пользователю ограничение на отправку сообщений на 3 минуты + ban_task = Thread(target=ban_user_timer, args=(message.chat.id, message.from_user.id, 180, bot)) + ban_task.start() + # Создаём задачу с отложенным выполнением на 3 минуты + + math_task = await create_math_task() + text = f"{math_task[1]} + {math_task[2]}" + builder = InlineKeyboardBuilder() + for answer in math_task[3]: + if answer == math_task[0]: + builder.add(types.InlineKeyboardButton( + text=answer, + callback_data=f"check_math_task_true") + ) + else: + builder.add(types.InlineKeyboardButton( + text=answer, + callback_data=f"check_math_task_false") + ) + await message.reply( + f"Приветствую, {message.from_user.first_name}!\n" + f"Для продолжения работы с ботом, пожалуйста, решите математический пример в течении 3х минут:\n" + f"*{text}*", + reply_markup=builder.as_markup() + ) + + +async def math_task_true(message: Message, bot: Bot): + await message.reply(f"Верно! Добро пожаловать в чат {message.from_user.first_name}") + await unmute_user(message.chat.id, message.from_user.id, bot) + add_user(message.from_user.id, + message.from_user.first_name + ' ' + message.from_user.last_name, + message.from_user.username) + pass diff --git a/src/modules/standard/welcome/routers.py b/src/modules/standard/welcome/routers.py new file mode 100644 index 0000000..4ddfa6f --- /dev/null +++ b/src/modules/standard/welcome/routers.py @@ -0,0 +1,10 @@ +from aiogram import Router, F + +from src.modules.standard.welcome.handlers import check_new_user + +router = Router() + +# Если в чат пришел новый пользователь +router.message.register(check_new_user, F.new_chat_members.exists()) +# Ловин колбеки от кнопок с callback_data=f"check_math_task_true" +router.callback_query.register(check_new_user, F.callback_data == "check_math_task_true") \ No newline at end of file