From 42b85d4417c0a8f2fa3aa388033ccf9010bd8ca1 Mon Sep 17 00:00:00 2001 From: Kirill Kirilenko Date: Sat, 23 Aug 2025 20:40:50 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20cha?= =?UTF-8?q?ts=20=D0=B8=20=D1=84=D0=BB=D0=B0=D0=B3=20active.=20=D0=9E=D0=BF?= =?UTF-8?q?=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2=20=D0=BA=20VK=20API.?= =?UTF-8?q?=20=D0=9E=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=83=D1=81=D1=82=D1=8B=D1=85=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=BE=D0=B2.=20=D0=A1=D0=BA=D0=BB=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BC=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9.=20=D0=A1=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=20"=D0=BF=D1=80=D0=B5=D0=B4=D1=83=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5"=20=D1=81=20=D1=87=D0=B8?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=BC=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 173 ++++++++++++++++++++++++++++++++++++++-------------- database.py | 24 +++++++- utils.py | 9 +++ 3 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 utils.py diff --git a/bot.py b/bot.py index 925ee98..a64b19b 100644 --- a/bot.py +++ b/bot.py @@ -2,13 +2,16 @@ import asyncio import calendar import datetime import time +from typing import Optional from vkbottle.bot import Bot, Message from vkbottle_types.objects import MessagesGetConversationMembers +from vkbottle.tools.formatting import bold import config from config import config_load import database +import utils config_load() @@ -19,13 +22,6 @@ def posix_time(): return calendar.timegm(time.gmtime()) -async def vk_get_user_info(user_id): - info = await bot.api.users.get(user_ids=[user_id]) - if len(info) > 0: - return info[0] - return None - - def vk_user_is_admin(user_id: int, chat_members: MessagesGetConversationMembers): for member in chat_members.items: if member.member_id != user_id: @@ -34,125 +30,212 @@ def vk_user_is_admin(user_id: int, chat_members: MessagesGetConversationMembers) return False +def create_chat_if_not_exists(chat_id: str): + chat = database.DB.get_chat(chat_id) + if chat is None: + database.DB.add_chat(chat_id) + chat = database.DB.get_chat(chat_id) + return chat + + def create_user_if_not_exists(chat_id: str, user_id: str): - res = database.DB.get_user(chat_id, user_id) - if res is None: + user = database.DB.get_user(chat_id, user_id) + if user is None: database.DB.add_user(chat_id, user_id) + user = database.DB.get_user(chat_id, user_id) + return user + + +MESSAGE_CHAT_NOT_ACTIVE = 'Извините, но я пока не работаю в этом чате.' +MESSAGE_PERMISSION_DENIED = 'Извините, но о таком меня может попросить только администратор чата.' +MESSAGE_NEED_REPLY = 'Извините, но эту команду нужно вызывать в ответном сообщении.' @bot.on.chat_message(text="!старт") async def start_handler(message: Message): - chat_members = await bot.api.messages.get_conversation_members(peer_id=message.peer_id, extended=False) + chat_id = message.peer_id + create_chat_if_not_exists(chat_id) + + chat_members = await bot.api.messages.get_conversation_members(peer_id=chat_id, extended=False) if not vk_user_is_admin(message.from_id, chat_members): - await message.answer('О таком меня может попросить только администратор беседы.') + await message.answer(MESSAGE_PERMISSION_DENIED) return + database.DB.chat_update(chat_id, active=1) + for member in chat_members.items: # Пропустить ботов if member.member_id < 0: continue - create_user_if_not_exists(message.peer_id, member.member_id) + create_user_if_not_exists(chat_id, member.member_id) await message.answer('Готова к работе!') @bot.on.chat_message(text="!предупреждение") async def warning_handler(message: Message): - chat_members = await bot.api.messages.get_conversation_members(peer_id=message.peer_id, extended=False) + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + await message.answer(MESSAGE_CHAT_NOT_ACTIVE) + return + + chat_members = await bot.api.messages.get_conversation_members(peer_id=chat_id, extended=False) if not vk_user_is_admin(message.from_id, chat_members): - await message.answer('О таком меня может попросить только администратор беседы.') + await message.answer(MESSAGE_PERMISSION_DENIED) return if message.reply_message is None: - await message.answer('Эту команду нужно вызывать в ответном сообщении.') + await message.answer(MESSAGE_NEED_REPLY) return - chat_id = message.peer_id user_id = message.reply_message.from_id create_user_if_not_exists(chat_id, user_id) database.DB.user_increment_warnings(chat_id, user_id) user = database.DB.get_user(chat_id, user_id) - user_info = await vk_get_user_info(user_id) - if user_info is not None: - await message.answer('У участника {} {} {} предупреждений.'.format( - user_info.first_name, - user_info.last_name, - user['warnings']) + user_info = await bot.api.users.get(user_ids=[user_id], name_case='gen') + if len(user_info) == 1: + await message.answer('У {} {} {} {}.'.format( + user_info[0].first_name, + user_info[0].last_name, + user['warnings'], + utils.make_word_agree_with_number(user['warnings'], 'предупреждение')) ) @bot.on.chat_message(text="!предупреждения") async def warnings_handler(message: Message): - top_users = database.DB.get_top_warnings(message.peer_id) + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + await message.answer(MESSAGE_CHAT_NOT_ACTIVE) + return - response = '* Участники с предупреждениями:\n' + top_users = database.DB.get_top_warnings(chat_id) + if len(top_users) == 0: + await message.answer('Пока все спокойно. Продолжайте в том же духе.') + return + + top_user_ids = [user['user_id'] for user in top_users] + users_info = await bot.api.users.get(user_ids=top_user_ids) + + response = bold('Участники с предупреждениями') + '\n' i = 1 for user in top_users: - info = await vk_get_user_info(user['user_id']) - response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['warnings']) - i = i + 1 + for info in users_info: + if info.id == user['user_id']: + response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['warnings']) + i = i + 1 + break await message.answer(response) @bot.on.chat_message(text="!сегодня") async def stats_today_handler(message: Message): - top_users = database.DB.get_top_messages_today(message.peer_id) + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + await message.answer(MESSAGE_CHAT_NOT_ACTIVE) + return - response = '* Статистика за сегодня:\n' + top_users = database.DB.get_top_messages_today(chat_id) + if len(top_users) == 0: + await message.answer('Сегодня еще никто не писал. Вы можете стать первым!') + return + + top_user_ids = [user['user_id'] for user in top_users] + users_info = await bot.api.users.get(user_ids=top_user_ids) + + response = bold('Статистика за сегодня') + '\n' i = 1 for user in top_users: - info = await vk_get_user_info(user['user_id']) - response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['messages_today']) - i = i + 1 + for info in users_info: + if info.id == user['user_id']: + response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['messages_today']) + i = i + 1 + break await message.answer(response) @bot.on.chat_message(text="!месяц") async def stats_month_handler(message: Message): - top_users = database.DB.get_top_messages_month(message.peer_id) + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + await message.answer(MESSAGE_CHAT_NOT_ACTIVE) + return - response = '* Статистика за месяц:\n' + top_users = database.DB.get_top_messages_month(chat_id) + if len(top_users) == 0: + await message.answer('В этом месяце еще никто не писал. Вы можете стать первым!') + return + + top_user_ids = [user['user_id'] for user in top_users] + users_info = await bot.api.users.get(user_ids=top_user_ids) + + response = bold('Статистика за месяц') + '\n' i = 1 for user in top_users: - info = await vk_get_user_info(user['user_id']) - response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['messages_month']) - i = i + 1 + for info in users_info: + if info.id == user['user_id']: + response += '{}. {} {} - {}\n'.format(i, info.first_name, info.last_name, user['messages_month']) + i = i + 1 + break await message.answer(response) @bot.on.chat_message(text="!молчуны") async def silent_handler(message: Message): + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + await message.answer(MESSAGE_CHAT_NOT_ACTIVE) + return + now = posix_time() threshold = now - 14 * 24 * 3600 top_users = database.DB.get_top_silent(message.peer_id, threshold) + if len(top_users) == 0: + await message.answer('Молчунов нет. Все молодцы!') + return - response = '* Молчуны чата (не писали больше 2 недель):\n' + top_user_ids = [user['user_id'] for user in top_users] + users_info = await bot.api.users.get(user_ids=top_user_ids) + + response = bold('Молчуны чата') + ' (не писали больше 2 недель)\n' i = 1 for user in top_users: - info = await vk_get_user_info(user['user_id']) - if user['last_message'] == 0: - response += '{}. {} {} - никогда\n'.format(i, info.first_name, info.last_name) - else: - days_silent = round((now - user['last_message']) / 3600 / 24) - response += '{}. {} {} - {} дней\n'.format(i, info.first_name, info.last_name, days_silent) + for info in users_info: + if info.id == user['user_id']: + if user['last_message'] == 0: + response += '{}. {} {} - никогда\n'.format(i, info.first_name, info.last_name) + else: + days_silent = round((now - user['last_message']) / 3600 / 24) + response += '{}. {} {} - {} дней\n'.format(i, info.first_name, info.last_name, days_silent) + i = i + 1 + break await message.answer(response) @bot.on.chat_message() async def any_message_handler(message: Message): + chat_id = message.peer_id + chat = create_chat_if_not_exists(chat_id) + if chat['active'] == 0: + return + # Игнорировать ботов if message.from_id < 0: return - chat_id = message.peer_id user_id = message.from_id create_user_if_not_exists(chat_id, user_id) database.DB.user_set_last_message(chat_id, user_id, posix_time()) diff --git a/database.py b/database.py index 5b457c4..b3943cc 100644 --- a/database.py +++ b/database.py @@ -8,6 +8,13 @@ class Database: self.conn.row_factory = sqlite3.Row self.cursor = self.conn.cursor() + self.cursor.execute(""" + CREATE TABLE IF NOT EXISTS chats ( + "id" INTEGER, + "active" INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY("chat_id")) + """) + self.cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( "chat_id" INTEGER, @@ -18,6 +25,20 @@ class Database: "warnings" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("chat_id","user_id")) """) + + self.conn.commit() + + def get_chat(self, chat_id: int): + self.cursor.execute("SELECT * FROM chats WHERE id = ?", (chat_id,)) + return self.cursor.fetchone() + + def add_chat(self, chat_id: int): + self.cursor.execute("INSERT INTO chats (id) VALUES (?)", (chat_id,)) + self.conn.commit() + + def chat_update(self, chat_id: int, **kwargs): + self.cursor.execute("UPDATE chats SET " + ", ".join(f + " = :" + f for f in kwargs) + + f" WHERE id = {chat_id}", kwargs) self.conn.commit() def get_user(self, chat_id: int, user_id: int): @@ -48,9 +69,6 @@ class Database: self.conn.commit() def user_update(self, chat_id: int, user_id: int, **kwargs): - # query = ("UPDATE users SET " + ", ".join(f + " = :" + f for f in kwargs) + - # " WHERE chat_id = ? AND user_id = ?") - # print(query) self.cursor.execute("UPDATE users SET " + ", ".join(f + " = :" + f for f in kwargs) + f" WHERE chat_id = {chat_id} AND user_id = {user_id}", kwargs) self.conn.commit() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..73eded4 --- /dev/null +++ b/utils.py @@ -0,0 +1,9 @@ +import pymorphy3 + + +_morph = pymorphy3.MorphAnalyzer() + + +def make_word_agree_with_number(n: int, word: str) -> str: + w = _morph.parse(word)[0] + return w.make_agree_with_number(n).word