diff --git a/bot.py b/bot.py index 1fbde4a..cbdcf23 100644 --- a/bot.py +++ b/bot.py @@ -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() diff --git a/config.py b/config.py index d44836e..123fa48 100644 --- a/config.py +++ b/config.py @@ -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('Конфигурация загружена.') diff --git a/database.py b/database.py index e6dca7b..da6dbfe 100644 --- a/database.py +++ b/database.py @@ -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 diff --git a/handlers/__init__.py b/handlers/__init__.py new file mode 100644 index 0000000..fab1bbb --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1,7 @@ +import handlers.user +import handlers.admin +import handlers.action +import handlers.default + + +__all__ = [] diff --git a/handlers/action.py b/handlers/action.py new file mode 100644 index 0000000..ba4b142 --- /dev/null +++ b/handlers/action.py @@ -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) diff --git a/handlers/admin.py b/handlers/admin.py new file mode 100644 index 0000000..2c5a5a7 --- /dev/null +++ b/handlers/admin.py @@ -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'], 'предупреждение')) + ) diff --git a/handlers/default.py b/handlers/default.py new file mode 100644 index 0000000..584978f --- /dev/null +++ b/handlers/default.py @@ -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) diff --git a/handlers/user.py b/handlers/user.py new file mode 100644 index 0000000..c7c5a1a --- /dev/null +++ b/handlers/user.py @@ -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_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('Хорошо, я не буду поздравлять тебя с днем рождения.') diff --git a/labeler.py b/labeler.py new file mode 100644 index 0000000..c1b145a --- /dev/null +++ b/labeler.py @@ -0,0 +1,4 @@ +from vkbottle.bot import BotLabeler + + +labeler = BotLabeler() diff --git a/messages.py b/messages.py new file mode 100644 index 0000000..e57da65 --- /dev/null +++ b/messages.py @@ -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Поздравляю!' diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..17f3e7b --- /dev/null +++ b/tasks.py @@ -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}) запущен.") diff --git a/utils.py b/utils.py index 73eded4..463ef5b 100644 --- a/utils.py +++ b/utils.py @@ -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())