Добавлена таблица chats и флаг active.

Оптимизация запросов к VK API.
Отдельные сообщения вместо вывода пустых списков.
Склонение имен пользователей.
Согласование слова "предупреждение" с числом перед ним.
This commit is contained in:
Kirill Kirilenko 2025-08-23 20:40:50 +03:00
parent eaf316ac84
commit 42b85d4417
3 changed files with 158 additions and 48 deletions

173
bot.py
View file

@ -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())

View file

@ -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()

9
utils.py Normal file
View file

@ -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