Реорганизация проекта.

Обработчики разделены на несколько модулей.
This commit is contained in:
Kirill Kirilenko 2025-08-28 03:21:48 +03:00
parent c73f9cb522
commit 0894e3b775
12 changed files with 527 additions and 493 deletions

490
bot.py
View file

@ -1,488 +1,14 @@
import asyncio
import calendar
import datetime
import time
from vkbottle.bot import Bot, Message
from vkbottle_types.objects import MessagesGetConversationMembers
from vkbottle.tools.formatting import bold, italic
import config
from config import config_load
import database
import utils
import handlers
import tasks
from labeler import labeler
config_load()
bot = Bot(config.Config['api_token'])
from vkbottle.bot import Bot
def posix_time():
return calendar.timegm(time.gmtime())
def vk_user_is_admin(user_id: int, chat_members: MessagesGetConversationMembers):
for member in chat_members.items:
if member.member_id != user_id:
continue
return member.is_admin
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):
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 = 'Извините, но эту команду нужно вызывать в ответном сообщении.'
MESSAGE_DEFAULT_RULES = 'Правила не установлены. Просто ведите себя хорошо.'
MESSAGE_DEFAULT_GREETING_JOIN = 'Добро пожаловать, {name}!'
MESSAGE_DEFAULT_GREETING_REJOIN = 'С возвращением, {name}!'
MESSAGE_DEFAULT_BIRTHDAY = 'Сегодня {name} празднует День Рождения!\nПоздравляю!'
@bot.on.chat_message(text="!помощь")
async def rules_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
response = bold('Команды для всех') + '\n'
response += '!правила - вывести правила\n'
response += '!сегодня - статистика сообщений за сегодня\n'
response += '!месяц - статистика сообщений за месяц\n'
response += '!молчуны - список молчунов\n'
response += '!предупреждения - список участников с предупреждениями\n'
response += '!поздравление - запретить/разрешить поздравлять с днем рождения\n'
response += '\n'
response += bold('Команды для администраторов') + '\n'
response += '!старт - начать работу в чате\n'
response += '!правила* - изменить правила\n'
response += '!приветствие* - изменить приветствие новичков\n'
response += '!возвращение* - изменить приветствие при возвращении\n'
response += '!деньрождения* - изменить уведомление о дне рождения\n'
response += '!предупреждение* - выдать предупреждение участнику\n'
response += '\n'
response += italic('Команды с пометкой * нужно вызывать в ответном сообщении.')
await message.answer(response)
@bot.on.chat_message(text="!старт")
async def start_handler(message: Message):
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(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(chat_id, member.member_id)
await message.answer('Готова к работе!')
@bot.on.chat_message(text="!правила")
async def rules_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
if message.reply_message is None:
if chat['rules'] is not None:
await message.answer(bold('Правила чата') + '\n' + chat['rules'])
else:
await message.answer(MESSAGE_DEFAULT_RULES)
else:
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(MESSAGE_PERMISSION_DENIED)
return
database.DB.chat_update(chat_id, rules=message.reply_message.text)
await message.answer('Правила чата изменены.')
@bot.on.chat_message(text="!приветствие")
async def set_greeting_join_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
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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, greeting_join=message.reply_message.text)
await message.answer('Приветствие изменено.')
@bot.on.chat_message(text="!возвращение")
async def set_greeting_rejoin_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
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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, greeting_rejoin=message.reply_message.text)
await message.answer('Приветствие при возвращении изменено.')
@bot.on.chat_message(text="!деньрождения")
async def set_birthday_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
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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, birthday_message=message.reply_message.text)
await message.answer('Уведомление о дне рождения изменено.')
@bot.on.chat_message(text="!предупреждение")
async def warning_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
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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
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 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):
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
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:
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):
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
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:
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):
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
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:
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
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:
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(text="!поздравление")
async def no_birthday_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
user_id = message.from_id
user = create_user_if_not_exists(chat_id, user_id)
happy_birthday = 1 if user['happy_birthday'] == 0 else 0
database.DB.user_toggle_happy_birthday(chat_id, user_id, happy_birthday)
if happy_birthday == 1:
await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.')
else:
await message.answer('Хорошо, я не буду поздравлять тебя с днем рождения.')
@bot.on.chat_message(action=['chat_invite_user', 'chat_invite_user_by_link'])
async def user_join_handler(message: Message):
chat_id = message.peer_id
chat = create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
return
if message.action.type == 'chat_invite_user_by_link':
user_id = message.from_id
first_join = True
else:
user_id = message.action.member_id
first_join = (user_id != message.from_id)
user_info = (await bot.api.users.get(user_ids=[user_id]))[0]
if user_id < 0:
return
if first_join:
response = chat['greeting_join'] or MESSAGE_DEFAULT_GREETING_JOIN
else:
response = chat['greeting_rejoin'] or MESSAGE_DEFAULT_GREETING_REJOIN
response = response.format(name=f'@id{user_id} ({user_info.first_name})')
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
# Не учитывать служебные сообщения
if message.action is not None:
return
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())
database.DB.user_increment_messages(chat_id, user_id)
async def wait_until(target_time: datetime.datetime):
now = datetime.datetime.now(target_time.tzinfo)
if now >= target_time:
return
delay_seconds = (target_time - now).total_seconds()
await asyncio.sleep(delay_seconds)
def reset_counters(reset_month: bool):
print('Resetting daily counters...')
database.DB.reset_messages_today()
if reset_month:
print('Resetting monthly counters...')
database.DB.reset_messages_month()
async def check_birthdays():
chats = database.DB.get_chats()
for chat in chats:
if chat['active'] == 0:
continue
chat_id = chat['id']
members = await bot.api.messages.get_conversation_members(peer_id=chat_id, extended=False, fields=['bdate'])
today = datetime.datetime.today()
for member in members.profiles:
if member.id < 0 or member.bdate is None:
continue
user = database.DB.get_user(chat_id, member.id)
if user['happy_birthday'] == 0:
continue
parts = member.bdate.split('.')
if len(parts) < 2:
continue
day = int(parts[0])
month = int(parts[1])
if day == today.day and month == today.month:
message = chat['birthday_message'] or MESSAGE_DEFAULT_BIRTHDAY
message = message.format(name=f'@id{member.id} ({member.first_name} {member.last_name})')
await bot.api.messages.send(random_id=0, peer_id=chat_id, message=message)
async def daily_maintenance_task():
tz = datetime.timezone(datetime.timedelta(hours=3), name="MSK")
target_time = datetime.time(6, 0, 0, tzinfo=tz)
now = datetime.datetime.now(tz)
if now.hour > target_time.hour or now.hour == target_time.hour and now.minute > target_time.minute:
target_date = now.date() + datetime.timedelta(days=1)
else:
target_date = now.date()
target_datetime = datetime.datetime.combine(target_date, target_time)
while True:
await wait_until(target_datetime)
reset_counters(target_datetime.day == 1)
await check_birthdays()
target_datetime = target_datetime + datetime.timedelta(days=1)
async def startup_task():
print("Bot started.")
bot.loop_wrapper.on_startup.append(startup_task())
bot.loop_wrapper.add_task(daily_maintenance_task())
assert handlers
bot = Bot(config.Config['api_token'], labeler=labeler)
bot.loop_wrapper.on_startup.append(tasks.startup_task(bot.api))
bot.loop_wrapper.add_task(tasks.daily_maintenance_task(bot.api))
bot.run_forever()

View file

@ -9,15 +9,9 @@ Config = {}
def config_load():
global Config
try:
file = open('config.json', 'r')
with open('config.json', 'r') as file:
Config = json.load(file)
file.close()
except IOError:
Config = {'api_token': ''}
def config_save():
global Config
with open('config.json', 'w') as file:
json.dump(Config, file, indent=4)
config_load()
print('Конфигурация загружена.')

View file

@ -127,3 +127,19 @@ class Database:
DB = Database()
def create_chat_if_not_exists(chat_id: int):
chat = DB.get_chat(chat_id)
if chat is None:
DB.add_chat(chat_id)
chat = DB.get_chat(chat_id)
return chat
def create_user_if_not_exists(chat_id: int, user_id: int):
user = DB.get_user(chat_id, user_id)
if user is None:
DB.add_user(chat_id, user_id)
user = DB.get_user(chat_id, user_id)
return user

7
handlers/__init__.py Normal file
View file

@ -0,0 +1,7 @@
import handlers.user
import handlers.admin
import handlers.action
import handlers.default
__all__ = []

34
handlers/action.py Normal file
View file

@ -0,0 +1,34 @@
from vkbottle.bot import Message
import database
from messages import *
from labeler import labeler
@labeler.chat_message(action=['chat_invite_user', 'chat_invite_user_by_link'])
async def user_join_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
return
if message.action.type == 'chat_invite_user_by_link':
user_id = message.from_id
first_join = True
else:
user_id = message.action.member_id
first_join = (user_id != message.from_id)
user_info = (await message.ctx_api.users.get(user_ids=[user_id]))[0]
if user_id < 0:
return
if first_join:
response = chat['greeting_join'] or MESSAGE_DEFAULT_GREETING_JOIN
else:
response = chat['greeting_rejoin'] or MESSAGE_DEFAULT_GREETING_REJOIN
response = response.format(name=f'@id{user_id} ({user_info.first_name})')
await message.answer(response)

159
handlers/admin.py Normal file
View file

@ -0,0 +1,159 @@
from vkbottle import bold
from vkbottle.bot import Message
from vkbottle_types.codegen.objects import MessagesGetConversationMembers
import database
from messages import *
import utils
from labeler import labeler
def vk_user_is_admin(user_id: int, chat_members: MessagesGetConversationMembers):
for member in chat_members.items:
if member.member_id != user_id:
continue
return member.is_admin
return False
@labeler.chat_message(text="!старт")
async def start_handler(message: Message):
chat_id = message.peer_id
database.create_chat_if_not_exists(chat_id)
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
database.DB.chat_update(chat_id, active=1)
for member in chat_members.items:
# Пропустить ботов
if member.member_id < 0:
continue
database.create_user_if_not_exists(chat_id, member.member_id)
await message.answer('Готова к работе!')
@labeler.chat_message(text="!правила")
async def rules_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
if message.reply_message is None:
if chat['rules'] is not None:
await message.answer(bold('Правила чата') + '\n' + chat['rules'])
else:
await message.answer(MESSAGE_DEFAULT_RULES)
else:
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
database.DB.chat_update(chat_id, rules=message.reply_message.text)
await message.answer('Правила чата изменены.')
@labeler.chat_message(text="!приветствие")
async def set_greeting_join_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, greeting_join=message.reply_message.text)
await message.answer('Приветствие изменено.')
@labeler.chat_message(text="!возвращение")
async def set_greeting_rejoin_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, greeting_rejoin=message.reply_message.text)
await message.answer('Приветствие при возвращении изменено.')
@labeler.chat_message(text="!деньрождения")
async def set_birthday_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
database.DB.chat_update(chat_id, birthday_message=message.reply_message.text)
await message.answer('Уведомление о дне рождения изменено.')
@labeler.chat_message(text="!предупреждение")
async def warning_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_members = await message.ctx_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(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
user_id = message.reply_message.from_id
database.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 message.ctx_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'], 'предупреждение'))
)

27
handlers/default.py Normal file
View file

@ -0,0 +1,27 @@
from vkbottle.bot import Message
import database
import utils
from labeler import labeler
# Обычные сообщения (не команды и не действия)
@labeler.chat_message()
async def any_message_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
return
# Игнорировать ботов
if message.from_id < 0:
return
# Не учитывать служебные сообщения
if message.action is not None:
return
user_id = message.from_id
database.create_user_if_not_exists(chat_id, user_id)
database.DB.user_set_last_message(chat_id, user_id, utils.posix_time())
database.DB.user_increment_messages(chat_id, user_id)

174
handlers/user.py Normal file
View file

@ -0,0 +1,174 @@
from vkbottle import bold, italic
from vkbottle.bot import Message
import database
from messages import *
import utils
from labeler import labeler
@labeler.chat_message(text="!помощь")
async def rules_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
response = bold('Команды для всех') + '\n'
response += '!правила - вывести правила\n'
response += '!сегодня - статистика сообщений за сегодня\n'
response += '!месяц - статистика сообщений за месяц\n'
response += '!молчуны - список молчунов\n'
response += '!предупреждения - список участников с предупреждениями\n'
response += '!поздравление - запретить/разрешить поздравлять с днем рождения\n'
response += '\n'
response += bold('Команды для администраторов') + '\n'
response += '!старт - начать работу в чате\n'
response += '!правила* - изменить правила\n'
response += '!приветствие* - изменить приветствие новичков\n'
response += '!возвращение* - изменить приветствие при возвращении\n'
response += '!деньрождения* - изменить уведомление о дне рождения\n'
response += '!предупреждение* - выдать предупреждение участнику\n'
response += '\n'
response += italic('Команды с пометкой * нужно вызывать в ответном сообщении.')
await message.answer(response)
@labeler.chat_message(text="!сегодня")
async def stats_today_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(message.MESSAGE_CHAT_NOT_ACTIVE)
return
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 message.ctx_api.users.get(user_ids=top_user_ids)
response = bold('Статистика за сегодня') + '\n'
i = 1
for user in top_users:
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)
@labeler.chat_message(text="!месяц")
async def stats_month_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
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 message.ctx_api.users.get(user_ids=top_user_ids)
response = bold('Статистика за месяц') + '\n'
i = 1
for user in top_users:
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)
@labeler.chat_message(text="!молчуны")
async def silent_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
now = utils.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
top_user_ids = [user['user_id'] for user in top_users]
users_info = await message.ctx_api.users.get(user_ids=top_user_ids)
response = bold('Молчуны чата') + ' (не писали больше 2 недель)\n'
i = 1
for user in top_users:
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)
@labeler.chat_message(text="!предупреждения")
async def warnings_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
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 message.ctx_api.users.get(user_ids=top_user_ids)
response = bold('Участники с предупреждениями') + '\n'
i = 1
for user in top_users:
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)
@labeler.chat_message(text="!поздравление")
async def no_birthday_handler(message: Message):
chat_id = message.peer_id
chat = database.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
user_id = message.from_id
user = database.create_user_if_not_exists(chat_id, user_id)
happy_birthday = 1 if user['happy_birthday'] == 0 else 0
database.DB.user_toggle_happy_birthday(chat_id, user_id, happy_birthday)
if happy_birthday == 1:
await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.')
else:
await message.answer('Хорошо, я не буду поздравлять тебя с днем рождения.')

4
labeler.py Normal file
View file

@ -0,0 +1,4 @@
from vkbottle.bot import BotLabeler
labeler = BotLabeler()

7
messages.py Normal file
View file

@ -0,0 +1,7 @@
MESSAGE_CHAT_NOT_ACTIVE = 'Извините, но я пока не работаю в этом чате.'
MESSAGE_PERMISSION_DENIED = 'Извините, но о таком меня может попросить только администратор чата.'
MESSAGE_NEED_REPLY = 'Извините, но эту команду нужно вызывать в ответном сообщении.'
MESSAGE_DEFAULT_RULES = 'Правила не установлены. Просто ведите себя хорошо.'
MESSAGE_DEFAULT_GREETING_JOIN = 'Добро пожаловать, {name}!'
MESSAGE_DEFAULT_GREETING_REJOIN = 'С возвращением, {name}!'
MESSAGE_DEFAULT_BIRTHDAY = 'Сегодня {name} празднует День Рождения!\nПоздравляю!'

80
tasks.py Normal file
View file

@ -0,0 +1,80 @@
import datetime
from asyncio import sleep
from vkbottle import API
import database
from messages import *
def reset_counters(reset_month: bool):
database.DB.reset_messages_today()
print('Дневные счетчики сброшены.')
if reset_month:
database.DB.reset_messages_month()
print('Месячные счетчики сброшены.')
async def check_birthdays(api: API):
chats = database.DB.get_chats()
for chat in chats:
if chat['active'] == 0:
continue
chat_id = chat['id']
members = await api.messages.get_conversation_members(peer_id=chat_id, extended=False, fields=['bdate'])
today = datetime.datetime.today()
for member in members.profiles:
if member.id < 0 or member.bdate is None:
continue
user = database.DB.get_user(chat_id, member.id)
if user['happy_birthday'] == 0:
continue
parts = member.bdate.split('.')
if len(parts) < 2:
continue
day = int(parts[0])
month = int(parts[1])
if day == today.day and month == today.month:
message = chat['birthday_message'] or MESSAGE_DEFAULT_BIRTHDAY
message = message.format(name=f'@id{member.id} ({member.first_name} {member.last_name})')
await api.messages.send(random_id=0, peer_id=chat_id, message=message)
async def wait_until(target_time: datetime.datetime):
now = datetime.datetime.now(target_time.tzinfo)
if now >= target_time:
return
delay_seconds = (target_time - now).total_seconds()
await sleep(delay_seconds)
async def daily_maintenance_task(api: API):
tz = datetime.timezone(datetime.timedelta(hours=3), name="MSK")
target_time = datetime.time(6, 0, 0, tzinfo=tz)
now = datetime.datetime.now(tz)
if now.hour > target_time.hour or now.hour == target_time.hour and now.minute > target_time.minute:
target_date = now.date() + datetime.timedelta(days=1)
else:
target_date = now.date()
target_datetime = datetime.datetime.combine(target_date, target_time)
while True:
await wait_until(target_datetime)
reset_counters(target_datetime.day == 1)
await check_birthdays(api)
target_datetime = target_datetime + datetime.timedelta(days=1)
async def startup_task(api: API):
me = (await api.groups.get_by_id()).groups[0]
print(f"Бот '{me.name}' (id={me.id}) запущен.")

View file

@ -1,9 +1,15 @@
import pymorphy3
from calendar import timegm
from pymorphy3 import MorphAnalyzer
from time import gmtime
_morph = pymorphy3.MorphAnalyzer()
_morph = 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
def posix_time():
return timegm(gmtime())