488 lines
18 KiB
Python
488 lines
18 KiB
Python
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
|
||
|
||
|
||
config_load()
|
||
bot = Bot(config.Config['api_token'])
|
||
|
||
|
||
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())
|
||
bot.run_forever()
|