Поддержка множества аккаунтов ботов.

This commit is contained in:
Kirill Kirilenko 2026-02-01 19:21:44 +03:00
parent 6088606810
commit b4a60b59e8
19 changed files with 427 additions and 310 deletions

View file

@ -40,7 +40,7 @@ class AiAgent:
self.model_temp = model_temp self.model_temp = model_temp
self.client = OpenRouter(api_key=api_token, retry_config=retry_config) self.client = OpenRouter(api_key=api_token, retry_config=retry_config)
async def get_group_chat_reply(self, chat_id: int, async def get_group_chat_reply(self, bot_id: int, chat_id: int,
message: Message, forwarded_messages: List[Message]) -> Tuple[str, bool]: message: Message, forwarded_messages: List[Message]) -> Tuple[str, bool]:
message_text = f"[{message.user_name}]: {message.text}" message_text = f"[{message.user_name}]: {message.text}"
for fwd_message in forwarded_messages: for fwd_message in forwarded_messages:
@ -48,7 +48,7 @@ class AiAgent:
message_text += fwd_message.text + '\n' message_text += fwd_message.text + '\n'
message_text += '<Конец цитаты>' message_text += '<Конец цитаты>'
context = self._get_chat_context(is_group_chat=True, chat_id=chat_id) context = self._get_chat_context(is_group_chat=True, bot_id=bot_id, chat_id=chat_id)
context.append({"role": "user", "content": message_text}) context.append({"role": "user", "content": message_text})
try: try:
@ -64,9 +64,9 @@ class AiAgent:
ai_response = response.choices[0].message.content ai_response = response.choices[0].message.content
# Add message and AI response to context # Add message and AI response to context
self.db.context_add_message(chat_id=chat_id, role="user", content=message_text, self.db.context_add_message(bot_id, chat_id, role="user", content=message_text,
message_id=message.message_id, max_messages=GROUP_CHAT_MAX_MESSAGES) message_id=message.message_id, max_messages=GROUP_CHAT_MAX_MESSAGES)
self.db.context_add_message(chat_id=chat_id, role="assistant", content=ai_response, self.db.context_add_message(bot_id, chat_id, role="assistant", content=ai_response,
message_id=None, max_messages=GROUP_CHAT_MAX_MESSAGES) message_id=None, max_messages=GROUP_CHAT_MAX_MESSAGES)
return ai_response, True return ai_response, True
@ -78,8 +78,8 @@ class AiAgent:
print(f"Ошибка выполнения запроса к ИИ: {e}") print(f"Ошибка выполнения запроса к ИИ: {e}")
return f"Извините, при обработке запроса произошла ошибка.", False return f"Извините, при обработке запроса произошла ошибка.", False
async def get_private_chat_reply(self, chat_id: int, message: str, message_id: int) -> Tuple[str, bool]: async def get_private_chat_reply(self, bot_id: int, chat_id: int, message: str, message_id: int) -> Tuple[str, bool]:
context = self._get_chat_context(is_group_chat=False, chat_id=chat_id) context = self._get_chat_context(is_group_chat=False, bot_id=bot_id, chat_id=chat_id)
context.append({"role": "user", "content": message}) context.append({"role": "user", "content": message})
try: try:
@ -88,16 +88,16 @@ class AiAgent:
model=self.model, model=self.model,
messages=context, messages=context,
max_tokens=500, max_tokens=500,
temperature=0.5 temperature=self.model_temp
) )
# Extract AI response # Extract AI response
ai_response = response.choices[0].message.content ai_response = response.choices[0].message.content
# Add message and AI response to context # Add message and AI response to context
self.db.context_add_message(chat_id=chat_id, role="user", content=message, self.db.context_add_message(bot_id, chat_id, role="user", content=message,
message_id=message_id, max_messages=PRIVATE_CHAT_MAX_MESSAGES) message_id=message_id, max_messages=PRIVATE_CHAT_MAX_MESSAGES)
self.db.context_add_message(chat_id=chat_id, role="assistant", content=ai_response, self.db.context_add_message(bot_id, chat_id, role="assistant", content=ai_response,
message_id=None, max_messages=PRIVATE_CHAT_MAX_MESSAGES) message_id=None, max_messages=PRIVATE_CHAT_MAX_MESSAGES)
return ai_response, True return ai_response, True
@ -109,23 +109,27 @@ class AiAgent:
print(f"Ошибка выполнения запроса к ИИ: {e}") print(f"Ошибка выполнения запроса к ИИ: {e}")
return f"Извините, при обработке запроса произошла ошибка.", False return f"Извините, при обработке запроса произошла ошибка.", False
def get_last_assistant_message_id(self, chat_id: int): def get_last_assistant_message_id(self, bot_id: int, chat_id: int):
return self.db.context_get_last_assistant_message_id(chat_id) return self.db.context_get_last_assistant_message_id(bot_id, chat_id)
def set_last_response_id(self, chat_id: int, message_id: int): def set_last_response_id(self, bot_id: int, chat_id: int, message_id: int):
self.db.context_set_last_message_id(chat_id, message_id) self.db.context_set_last_message_id(bot_id, chat_id, message_id)
def clear_chat_context(self, chat_id: int): def clear_chat_context(self, bot_id: int, chat_id: int):
self.db.context_clear(chat_id) self.db.context_clear(bot_id, chat_id)
def _get_chat_context(self, is_group_chat: bool, chat_id: int) -> list[dict]: def _get_chat_context(self, is_group_chat: bool, bot_id: int, chat_id: int) -> list[dict]:
prompt = GROUP_CHAT_SYSTEM_PROMPT if is_group_chat else PRIVATE_CHAT_SYSTEM_PROMPT prompt = GROUP_CHAT_SYSTEM_PROMPT if is_group_chat else PRIVATE_CHAT_SYSTEM_PROMPT
chat = self.db.create_chat_if_not_exists(chat_id) bot = self.db.get_bot(bot_id)
if bot['ai_prompt'] is not None:
prompt += '\n\n' + bot['ai_prompt']
chat = self.db.create_chat_if_not_exists(bot_id, chat_id)
if chat['ai_prompt'] is not None: if chat['ai_prompt'] is not None:
prompt += '\n\n' + chat['ai_prompt'] prompt += '\n\n' + chat['ai_prompt']
messages = self.db.context_get_messages(chat_id) messages = self.db.context_get_messages(bot_id, chat_id)
return [{"role": "system", "content": prompt}] + messages return [{"role": "system", "content": prompt}] + messages

View file

@ -12,166 +12,179 @@ class BasicDatabase:
self.conn.setencoding(encoding='utf-8') self.conn.setencoding(encoding='utf-8')
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
def get_chats(self): def get_bots(self):
self.cursor.execute("SELECT * FROM chats") self.cursor.execute("SELECT * FROM bots")
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def get_chat(self, chat_id: int): def get_bot(self, bot_id: int):
self.cursor.execute("SELECT * FROM chats WHERE id = ?", chat_id) self.cursor.execute("SELECT * FROM bots WHERE id = ?", bot_id)
return self._to_dict(self.cursor.fetchone()) return self._to_dict(self.cursor.fetchone())
def add_chat(self, chat_id: int): def get_chats(self, bot_id: int):
self.cursor.execute("INSERT INTO chats (id) VALUES (?)", chat_id) self.cursor.execute("SELECT * FROM chats WHERE bot_id = ?", bot_id)
return self._to_dict(self.cursor.fetchall())
def chat_update(self, chat_id: int, **kwargs): def get_chat(self, bot_id: int, chat_id: int):
self.cursor.execute("SELECT * FROM chats WHERE bot_id = ? AND chat_id = ?", bot_id, chat_id)
return self._to_dict(self.cursor.fetchone())
def add_chat(self, bot_id: int, chat_id: int):
self.cursor.execute("INSERT INTO chats (bot_id, chat_id) VALUES (?, ?)", bot_id, chat_id)
def chat_update(self, bot_id: int, chat_id: int, **kwargs):
self.cursor.execute("UPDATE chats SET " + ", ".join(f + " = ?" for f in kwargs) + self.cursor.execute("UPDATE chats SET " + ", ".join(f + " = ?" for f in kwargs) +
" WHERE id = ?", list(kwargs.values()) + [chat_id]) " WHERE bot_id = ? AND chat_id = ?", list(kwargs.values()) + [bot_id, chat_id])
def chat_delete(self, chat_id: int): def chat_delete(self, bot_id: int, chat_id: int):
self.cursor.execute("DELETE FROM chats WHERE id = ?", chat_id) self.cursor.execute("DELETE FROM chats WHERE bot_id = ? AND chat_id = ?", bot_id, chat_id)
def get_user(self, chat_id: int, user_id: int): def get_user(self, bot_id: int, chat_id: int, user_id: int):
self.cursor.execute("SELECT * FROM users WHERE chat_id = ? AND user_id = ?", chat_id, user_id) self.cursor.execute("SELECT * FROM users WHERE bot_id = ? AND chat_id = ? AND user_id = ?",
bot_id, chat_id, user_id)
return self._to_dict(self.cursor.fetchone()) return self._to_dict(self.cursor.fetchone())
def get_users(self, chat_id: int): def get_users(self, bot_id: int, chat_id: int):
self.cursor.execute("SELECT * FROM users WHERE chat_id = ?", chat_id) self.cursor.execute("SELECT * FROM users WHERE bot_id = ? AND chat_id = ?", bot_id, chat_id)
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def add_user(self, chat_id: int, user_id: int): def add_user(self, bot_id: int, chat_id: int, user_id: int):
self.cursor.execute("INSERT INTO users (chat_id, user_id) VALUES (?, ?)", chat_id, user_id) self.cursor.execute("INSERT INTO users (bot_id, chat_id, user_id) VALUES (?, ?, ?)",
bot_id, chat_id, user_id)
def user_set_last_message(self, chat_id: int, user_id: int, last_message: int): def user_set_last_message(self, bot_id: int, chat_id: int, user_id: int, last_message: int):
self.user_update(chat_id, user_id, last_message=last_message) self.user_update(bot_id, chat_id, user_id, last_message=last_message)
def user_increment_messages(self, chat_id: int, user_id: int): def user_increment_messages(self, bot_id: int, chat_id: int, user_id: int):
self.user_increment(chat_id, user_id, ['messages_today', 'messages_month']) self.user_increment(bot_id, chat_id, user_id, ['messages_today', 'messages_month'])
def user_increment_warnings(self, chat_id: int, user_id: int): def user_increment_warnings(self, bot_id: int, chat_id: int, user_id: int):
self.user_increment(chat_id, user_id, ['warnings']) self.user_increment(bot_id, chat_id, user_id, ['warnings'])
def user_increment(self, chat_id: int, user_id: int, fields: List[str]): def user_increment(self, bot_id: int, chat_id: int, user_id: int, fields: List[str]):
self.cursor.execute("UPDATE users SET " + ", ".join(f + " = " + f + " + 1" for f in fields) + self.cursor.execute("UPDATE users SET " + ", ".join(f + " = " + f + " + 1" for f in fields) +
" WHERE chat_id = ? AND user_id = ?", chat_id, user_id) " WHERE bot_id = ? AND chat_id = ? AND user_id = ?", bot_id, chat_id, user_id)
def user_update(self, chat_id: int, user_id: int, **kwargs): def user_update(self, bot_id, chat_id: int, user_id: int, **kwargs):
self.cursor.execute("UPDATE users SET " + ", ".join(f + " = ?" for f in kwargs) + self.cursor.execute("UPDATE users SET " + ", ".join(f + " = ?" for f in kwargs) +
" WHERE chat_id = ? AND user_id = ?", list(kwargs.values()) + [chat_id, user_id]) " WHERE bot_id = ? AND chat_id = ? AND user_id = ?",
list(kwargs.values()) + [bot_id, chat_id, user_id])
def delete_user(self, chat_id: int, user_id: int): def delete_user(self, bot_id: int, chat_id: int, user_id: int):
self.cursor.execute("DELETE FROM users WHERE chat_id = ? AND user_id = ?", chat_id, user_id) self.cursor.execute("DELETE FROM users WHERE bot_id = ? AND chat_id = ? AND user_id = ?",
bot_id, chat_id, user_id)
def get_top_messages_today(self, chat_id: int): def get_top_messages_today(self, bot_id: int, chat_id: int):
self.cursor.execute(""" self.cursor.execute("""
SELECT user_id, messages_today AS value FROM users SELECT user_id, messages_today AS value FROM users
WHERE chat_id = ? AND messages_today > 0 WHERE bot_id = ? AND chat_id = ? AND messages_today > 0
ORDER BY messages_today DESC ORDER BY messages_today DESC
""", chat_id) """, bot_id, chat_id)
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def get_top_messages_month(self, chat_id: int): def get_top_messages_month(self, bot_id: int, chat_id: int):
self.cursor.execute(""" self.cursor.execute("""
SELECT user_id, messages_month AS value FROM users SELECT user_id, messages_month AS value FROM users
WHERE chat_id = ? AND messages_month > 0 WHERE bot_id = ? AND chat_id = ? AND messages_month > 0
ORDER BY messages_month DESC ORDER BY messages_month DESC
""", chat_id) """, bot_id, chat_id)
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def get_top_silent(self, chat_id: int, threshold_days: int): def get_top_silent(self, bot_id: int, chat_id: int, threshold_days: int):
current_time = int(datetime.now().timestamp()) current_time = int(datetime.now().timestamp())
threshold = current_time - threshold_days * 86400 threshold = current_time - threshold_days * 86400
self.cursor.execute(""" self.cursor.execute("""
SELECT user_id, (? - last_message) DIV 86400 as value SELECT user_id, (? - last_message) DIV 86400 as value
FROM users FROM users
WHERE chat_id = ? AND last_message <= ? WHERE bot_id = ? AND chat_id = ? AND last_message <= ?
ORDER BY last_message ASC ORDER BY last_message ASC
""", current_time, chat_id, threshold) """, current_time, bot_id, chat_id, threshold)
result = self._to_dict(self.cursor.fetchall()) result = self._to_dict(self.cursor.fetchall())
for row in result: for row in result:
if row['value'] > 3650: if row['value'] > 3650:
row['value'] = 'никогда' row['value'] = 'никогда'
return result return result
def get_top_warnings(self, chat_id: int): def get_top_warnings(self, bot_id: int, chat_id: int):
self.cursor.execute(""" self.cursor.execute("""
SELECT user_id, warnings AS value FROM users SELECT user_id, warnings AS value FROM users
WHERE chat_id = ? AND warnings > 0 WHERE bot_id = ? AND chat_id = ? AND warnings > 0
ORDER BY warnings DESC ORDER BY warnings DESC
""", chat_id) """, bot_id, chat_id)
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def reset_messages_today(self): def reset_messages_today(self, bot_id: int):
self.cursor.execute("UPDATE users SET messages_today = 0") self.cursor.execute("UPDATE users SET messages_today = 0 WHERE bot_id = ?", bot_id)
def reset_messages_month(self): def reset_messages_month(self, bot_id: int):
self.cursor.execute("UPDATE users SET messages_month = 0") self.cursor.execute("UPDATE users SET messages_month = 0 WHERE bot_id = ?", bot_id)
def context_get_messages(self, chat_id: int) -> list[dict]: def context_get_messages(self, bot_id: int, chat_id: int) -> list[dict]:
self.cursor.execute(""" self.cursor.execute("""
SELECT role, content FROM contexts SELECT role, content FROM contexts
WHERE chat_id = ? AND message_id IS NOT NULL WHERE bot_id = ? AND chat_id = ? AND message_id IS NOT NULL
ORDER BY message_id ORDER BY message_id
""", chat_id) """, bot_id, chat_id)
return self._to_dict(self.cursor.fetchall()) return self._to_dict(self.cursor.fetchall())
def context_get_count(self, chat_id: int) -> int: def context_get_count(self, bot_id: int, chat_id: int) -> int:
self.cursor.execute("SELECT COUNT(*) FROM contexts WHERE chat_id = ?", chat_id) self.cursor.execute("SELECT COUNT(*) FROM contexts WHERE bot_id = ? AND chat_id = ?", bot_id, chat_id)
return self.cursor.fetchval() return self.cursor.fetchval()
def context_get_last_assistant_message_id(self, chat_id: int) -> Optional[int]: def context_get_last_assistant_message_id(self, bot_id: int, chat_id: int) -> Optional[int]:
return self.cursor.execute(""" return self.cursor.execute("""
SELECT message_id FROM contexts SELECT message_id FROM contexts
WHERE chat_id = ? AND role = 'assistant' AND message_id IS NOT NULL WHERE bot_id = ? AND chat_id = ? AND role = 'assistant' AND message_id IS NOT NULL
ORDER BY message_id DESC ORDER BY message_id DESC
LIMIT 1 LIMIT 1
""", chat_id).fetchval() """, bot_id, chat_id).fetchval()
def context_add_message(self, chat_id: int, role: str, content: str, message_id: Optional[int], max_messages: int): def context_add_message(self, bot_id: int, chat_id: int, role: str, content: str, message_id: Optional[int], max_messages: int):
self._context_trim(chat_id, max_messages) self._context_trim(bot_id, chat_id, max_messages)
if message_id is not None: if message_id is not None:
self.cursor.execute("INSERT INTO contexts (chat_id, message_id, role, content) VALUES (?, ?, ?, ?)", self.cursor.execute(
chat_id, message_id, role, content) "INSERT INTO contexts (bot_id, chat_id, message_id, role, content) VALUES (?, ?, ?, ?, ?)",
bot_id, chat_id, message_id, role, content)
else: else:
self.cursor.execute("INSERT INTO contexts (chat_id, role, content) VALUES (?, ?, ?)", self.cursor.execute("INSERT INTO contexts (bot_id, chat_id, role, content) VALUES (?, ?, ?, ?)",
chat_id, role, content) bot_id, chat_id, role, content)
def context_set_last_message_id(self, chat_id: int, message_id: int): def context_set_last_message_id(self, bot_id: int, chat_id: int, message_id: int):
self.cursor.execute("UPDATE contexts SET message_id = ? WHERE chat_id = ? AND message_id IS NULL", self.cursor.execute("UPDATE contexts SET message_id = ? WHERE bot_id = ? AND chat_id = ? AND message_id IS NULL",
message_id, chat_id) message_id, bot_id, chat_id)
def _context_trim(self, chat_id: int, max_messages: int): def _context_trim(self, bot_id: int, chat_id: int, max_messages: int):
current_count = self.context_get_count(chat_id) current_count = self.context_get_count(bot_id, chat_id)
while current_count >= max_messages: while current_count >= max_messages:
oldest_message_id = self.cursor.execute(""" oldest_message_id = self.cursor.execute("""
SELECT message_id FROM contexts SELECT message_id FROM contexts
WHERE chat_id = ? AND message_id IS NOT NULL WHERE bot_id = ? AND chat_id = ? AND message_id IS NOT NULL
ORDER BY message_id ASC ORDER BY message_id ASC
LIMIT 1 LIMIT 1
""", chat_id).fetchval() """, bot_id, chat_id).fetchval()
if oldest_message_id: if oldest_message_id:
self.cursor.execute("DELETE FROM contexts WHERE chat_id = ? AND message_id = ?", self.cursor.execute("DELETE FROM contexts WHERE bot_id = ? AND chat_id = ? AND message_id = ?",
chat_id, oldest_message_id) bot_id, chat_id, oldest_message_id)
current_count -= 1 current_count -= 1
else: else:
break break
def context_clear(self, chat_id: int): def context_clear(self, bot_id: int, chat_id: int):
self.cursor.execute("DELETE FROM contexts WHERE chat_id = ?", chat_id) self.cursor.execute("DELETE FROM contexts WHERE bot_id = ? AND chat_id = ?", bot_id, chat_id)
def create_chat_if_not_exists(self, chat_id: int): def create_chat_if_not_exists(self, bot_id: int, chat_id: int):
chat = self.get_chat(chat_id) chat = self.get_chat(bot_id, chat_id)
if chat is None: if chat is None:
self.add_chat(chat_id) self.add_chat(bot_id, chat_id)
chat = self.get_chat(chat_id) chat = self.get_chat(bot_id, chat_id)
return chat return chat
def create_user_if_not_exists(self, chat_id: int, user_id: int): def create_user_if_not_exists(self, bot_id: int, chat_id: int, user_id: int):
user = self.get_user(chat_id, user_id) user = self.get_user(bot_id, chat_id, user_id)
if user is None: if user is None:
self.add_user(chat_id, user_id) self.add_user(bot_id, chat_id, user_id)
user = self.get_user(chat_id, user_id) user = self.get_user(bot_id, chat_id, user_id)
return user return user
def _to_dict(self, args: Union[Row, List[Row], None]): def _to_dict(self, args: Union[Row, List[Row], None]):

View file

@ -16,18 +16,24 @@ async def main() -> None:
config = json.load(file) config = json.load(file)
print('Конфигурация загружена.') print('Конфигурация загружена.')
bot = Bot(token=config['api_token'])
database.create_database(config['db_connection_string']) database.create_database(config['db_connection_string'])
create_ai_agent(config['openrouter_token'], create_ai_agent(config['openrouter_token'],
config['openrouter_model'], config['openrouter_model'],
config['openrouter_model_temp'], config['openrouter_model_temp'],
database.DB) database.DB)
bots: list[Bot] = []
for item in database.DB.get_bots():
bot = Bot(token=item['api_token'])
asyncio.create_task(tasks.startup_task(bot))
asyncio.create_task(tasks.daily_maintenance_task(bot))
bots.append(bot)
dp = Dispatcher() dp = Dispatcher()
dp.include_router(handlers.router) dp.include_router(handlers.router)
dp.startup.register(tasks.startup_task) await dp.start_polling(*bots)
asyncio.create_task(tasks.daily_maintenance_task(bot))
await dp.start_polling(bot)
if __name__ == '__main__': if __name__ == '__main__':
asyncio.run(main()) asyncio.run(main())

View file

@ -1,4 +1,4 @@
from aiogram import Router, F from aiogram import Router, F, Bot
from aiogram.enums import ContentType, ParseMode from aiogram.enums import ContentType, ParseMode
from aiogram.types import Message from aiogram.types import Message
@ -9,9 +9,9 @@ router = Router()
@router.message(F.content_type == ContentType.NEW_CHAT_MEMBERS) @router.message(F.content_type == ContentType.NEW_CHAT_MEMBERS)
async def user_join_handler(message: Message): async def user_join_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
@ -25,9 +25,9 @@ async def user_join_handler(message: Message):
@router.message(F.content_type == ContentType.LEFT_CHAT_MEMBER) @router.message(F.content_type == ContentType.LEFT_CHAT_MEMBER)
async def user_join_handler(message: Message): async def user_join_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
@ -35,11 +35,11 @@ async def user_join_handler(message: Message):
if member.is_bot: if member.is_bot:
return return
database.DB.delete_user(chat_id, member.id) database.DB.delete_user(bot.id, chat_id, member.id)
@router.message(F.content_type == ContentType.MIGRATE_TO_CHAT_ID) @router.message(F.content_type == ContentType.MIGRATE_TO_CHAT_ID)
async def migration_handler(message: Message): async def migration_handler(message: Message, bot: Bot):
old_id, new_id = message.chat.id, message.migrate_to_chat_id old_id, new_id = message.chat.id, message.migrate_to_chat_id
database.DB.chat_delete(new_id) database.DB.chat_delete(bot.id, new_id)
database.DB.chat_update(old_id, id=new_id) database.DB.chat_update(bot.id, old_id, id=new_id)

View file

@ -21,20 +21,20 @@ async def tg_user_is_admin(chat_id: int, user_id: int, bot: Bot):
@router.message(F.text == "!старт") @router.message(F.text == "!старт")
async def start_handler(message: Message, bot: Bot): async def start_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot.id, chat_id)
if not await tg_user_is_admin(chat_id, message.from_user.id, bot): if not await tg_user_is_admin(chat_id, message.from_user.id, bot):
await message.answer(MESSAGE_PERMISSION_DENIED) await message.answer(MESSAGE_PERMISSION_DENIED)
return return
database.DB.chat_update(chat_id, active=1) database.DB.chat_update(bot.id, chat_id, active=1)
await message.answer('Готова к работе!') await message.answer('Готова к работе!')
@router.message(F.text == "!правила") @router.message(F.text == "!правила")
async def rules_handler(message: Message, bot: Bot): async def rules_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -50,14 +50,14 @@ async def rules_handler(message: Message, bot: Bot):
await message.answer(MESSAGE_PERMISSION_DENIED) await message.answer(MESSAGE_PERMISSION_DENIED)
return return
database.DB.chat_update(chat_id, rules=message.reply_to_message.text) database.DB.chat_update(bot.id, chat_id, rules=message.reply_to_message.text)
await message.answer('Правила чата изменены.') await message.answer('Правила чата изменены.')
@router.message(F.text == "!приветствие") @router.message(F.text == "!приветствие")
async def set_greeting_handler(message: Message, bot: Bot): async def set_greeting_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -70,14 +70,14 @@ async def set_greeting_handler(message: Message, bot: Bot):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, greeting_join=message.reply_to_message.text) database.DB.chat_update(bot.id, chat_id, greeting_join=message.reply_to_message.text)
await message.answer('Приветствие изменено.') await message.answer('Приветствие изменено.')
@router.message(F.text == "!личность") @router.message(F.text == "!личность")
async def set_ai_prompt_handler(message: Message, bot: Bot): async def set_ai_prompt_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -90,15 +90,15 @@ async def set_ai_prompt_handler(message: Message, bot: Bot):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, ai_prompt=message.reply_to_message.text) database.DB.chat_update(bot.id, chat_id, ai_prompt=message.reply_to_message.text)
ai_agent.agent.clear_chat_context(chat_id) ai_agent.agent.clear_chat_context(bot.id, chat_id)
await message.answer('Личность ИИ изменена.') await message.answer('Личность ИИ изменена.')
@router.message(F.text == "!предупреждение") @router.message(F.text == "!предупреждение")
async def warning_handler(message: Message, bot: Bot): async def warning_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -112,10 +112,10 @@ async def warning_handler(message: Message, bot: Bot):
return return
user_id = message.reply_to_message.from_user.id user_id = message.reply_to_message.from_user.id
database.DB.create_user_if_not_exists(chat_id, user_id) database.DB.create_user_if_not_exists(bot.id, chat_id, user_id)
database.DB.user_increment_warnings(chat_id, user_id) database.DB.user_increment_warnings(bot.id, chat_id, user_id)
user = database.DB.get_user(chat_id, user_id) user = database.DB.get_user(bot.id, chat_id, user_id)
user_info = message.reply_to_message.from_user user_info = message.reply_to_message.from_user
# TODO: родительный падеж имени и фамилии, если возможно # TODO: родительный падеж имени и фамилии, если возможно

View file

@ -1,9 +1,7 @@
from functools import partial from functools import partial
from typing import Optional
from aiogram import Router, F from aiogram import Router, F, Bot
from aiogram.types import Message from aiogram.types import Message
from aiogram.types.user import User
from aiogram.enums.content_type import ContentType from aiogram.enums.content_type import ContentType
import ai_agent import ai_agent
@ -15,8 +13,6 @@ from tg.utils import get_user_name_for_ai
router = Router() router = Router()
bot_user: Optional[User] = None
ACCEPTED_CONTENT_TYPES: list[ContentType] = [ ACCEPTED_CONTENT_TYPES: list[ContentType] = [
ContentType.TEXT, ContentType.TEXT,
ContentType.ANIMATION, ContentType.ANIMATION,
@ -38,9 +34,9 @@ ACCEPTED_CONTENT_TYPES: list[ContentType] = [
@router.message(F.content_type.in_(ACCEPTED_CONTENT_TYPES)) @router.message(F.content_type.in_(ACCEPTED_CONTENT_TYPES))
async def any_message_handler(message: Message): async def any_message_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
@ -49,13 +45,11 @@ async def any_message_handler(message: Message):
return return
user_id = message.from_user.id user_id = message.from_user.id
database.DB.create_user_if_not_exists(chat_id, user_id) database.DB.create_user_if_not_exists(bot.id, chat_id, user_id)
database.DB.user_set_last_message(chat_id, user_id, utils.posix_time()) database.DB.user_set_last_message(bot.id, chat_id, user_id, utils.posix_time())
database.DB.user_increment_messages(chat_id, user_id) database.DB.user_increment_messages(bot.id, chat_id, user_id)
global bot_user bot_user = await bot.me()
if bot_user is None:
bot_user = await message.bot.get_me()
ai_message = ai_agent.Message() ai_message = ai_agent.Message()
ai_fwd_messages: list[ai_agent.Message] = [] ai_fwd_messages: list[ai_agent.Message] = []
@ -82,7 +76,7 @@ async def any_message_handler(message: Message):
await message.reply(MESSAGE_NOT_TEXT) await message.reply(MESSAGE_NOT_TEXT)
return return
last_id = ai_agent.agent.get_last_assistant_message_id(chat_id) last_id = ai_agent.agent.get_last_assistant_message_id(bot.id, chat_id)
if message.reply_to_message.message_id != last_id: if message.reply_to_message.message_id != last_id:
# Оригинального сообщения нет в контексте, или оно не последнее # Оригинального сообщения нет в контексте, или оно не последнее
if message.content_type == ContentType.TEXT: if message.content_type == ContentType.TEXT:
@ -99,10 +93,10 @@ async def any_message_handler(message: Message):
ai_message.message_id = message.message_id ai_message.message_id = message.message_id
answer, success = await utils.run_with_progress( answer, success = await utils.run_with_progress(
partial(ai_agent.agent.get_group_chat_reply, chat_id, ai_message, ai_fwd_messages), partial(ai_agent.agent.get_group_chat_reply, bot.id, chat_id, ai_message, ai_fwd_messages),
partial(message.bot.send_chat_action, chat_id, 'typing'), partial(message.bot.send_chat_action, chat_id, 'typing'),
interval=4) interval=4)
answer_id = (await message.reply(answer)).message_id answer_id = (await message.reply(answer)).message_id
if success: if success:
ai_agent.agent.set_last_response_id(chat_id, answer_id) ai_agent.agent.set_last_response_id(bot.id, chat_id, answer_id)

View file

@ -1,6 +1,6 @@
from functools import partial from functools import partial
from aiogram import Router, F from aiogram import Router, F, Bot
from aiogram.enums import ChatType, ContentType from aiogram.enums import ChatType, ContentType
from aiogram.filters import Command, CommandObject, CommandStart from aiogram.filters import Command, CommandObject, CommandStart
from aiogram.types import Message from aiogram.types import Message
@ -16,33 +16,33 @@ router = Router()
@router.message(CommandStart(), F.chat.type == ChatType.PRIVATE) @router.message(CommandStart(), F.chat.type == ChatType.PRIVATE)
async def start_handler(message: Message): async def start_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot.id, chat_id)
database.DB.chat_update(chat_id, active=1) database.DB.chat_update(bot.id, chat_id, active=1)
await message.answer("Привет!") await message.answer("Привет!")
@router.message(Command("личность", prefix="!"), F.chat.type == ChatType.PRIVATE) @router.message(Command("личность", prefix="!"), F.chat.type == ChatType.PRIVATE)
async def set_prompt_handler(message: Message, command: CommandObject): async def set_prompt_handler(message: Message, command: CommandObject, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot.id, chat_id)
database.DB.chat_update(chat_id, ai_prompt=command.args) database.DB.chat_update(bot.id, chat_id, ai_prompt=command.args)
await message.answer("Личность ИИ изменена.") await message.answer("Личность ИИ изменена.")
@router.message(Command("сброс", prefix="!"), F.chat.type == ChatType.PRIVATE) @router.message(Command("сброс", prefix="!"), F.chat.type == ChatType.PRIVATE)
async def reset_context_handler(message: Message): async def reset_context_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot.id, chat_id)
ai_agent.agent.clear_chat_context(chat_id) ai_agent.agent.clear_chat_context(bot.id, chat_id)
await message.answer("Контекст очищен.") await message.answer("Контекст очищен.")
@router.message(F.content_type.in_(ACCEPTED_CONTENT_TYPES), F.chat.type == ChatType.PRIVATE) @router.message(F.content_type.in_(ACCEPTED_CONTENT_TYPES), F.chat.type == ChatType.PRIVATE)
async def any_message_handler(message: Message): async def any_message_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
if message.content_type != ContentType.TEXT: if message.content_type != ContentType.TEXT:
@ -50,10 +50,10 @@ async def any_message_handler(message: Message):
return return
answer, success = await utils.run_with_progress( answer, success = await utils.run_with_progress(
partial(ai_agent.agent.get_private_chat_reply, chat_id, message.text, message.message_id), partial(ai_agent.agent.get_private_chat_reply, bot.id, chat_id, message.text, message.message_id),
partial(message.bot.send_chat_action, chat_id, 'typing'), partial(message.bot.send_chat_action, chat_id, 'typing'),
interval=4) interval=4)
answer_id = (await message.answer(answer)).message_id answer_id = (await message.answer(answer)).message_id
if success: if success:
ai_agent.agent.set_last_response_id(chat_id, answer_id) ai_agent.agent.set_last_response_id(bot.id, chat_id, answer_id)

View file

@ -36,9 +36,9 @@ def calculate_total_messages(top_users: List[Any]) -> int:
@router.message(F.text == '!помощь') @router.message(F.text == '!помощь')
async def help_handler(message: Message): async def help_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -67,15 +67,15 @@ async def help_handler(message: Message):
@router.message(F.text == "") @router.message(F.text == "")
async def about_handler(message: Message): async def about_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
target_user = message.from_user target_user = message.from_user
user = database.DB.create_user_if_not_exists(chat_id, target_user.id) user = database.DB.create_user_if_not_exists(bot.id, chat_id, target_user.id)
if message.reply_to_message is None: if message.reply_to_message is None:
if user['about'] is not None: if user['about'] is not None:
response = Italic(utils.full_name(target_user.first_name, target_user.last_name)) + '\n' + user['about'] response = Italic(utils.full_name(target_user.first_name, target_user.last_name)) + '\n' + user['about']
@ -84,16 +84,16 @@ async def about_handler(message: Message):
await message.answer('Вы не установили визитку.') await message.answer('Вы не установили визитку.')
else: else:
if message.reply_to_message.content_type == ContentType.TEXT: if message.reply_to_message.content_type == ContentType.TEXT:
database.DB.user_update(chat_id, target_user.id, about=message.reply_to_message.text) database.DB.user_update(bot.id, chat_id, target_user.id, about=message.reply_to_message.text)
await message.answer('Визитка изменена.') await message.answer('Визитка изменена.')
else: else:
await message.answer('Извините, но я понимаю только текст.') await message.answer('Извините, но я понимаю только текст.')
@router.message(F.text == "!кто") @router.message(F.text == "!кто")
async def whois_handler(message: Message): async def whois_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -104,7 +104,7 @@ async def whois_handler(message: Message):
target_user = message.reply_to_message.from_user target_user = message.reply_to_message.from_user
full_name = utils.full_name(target_user.first_name, target_user.last_name) full_name = utils.full_name(target_user.first_name, target_user.last_name)
user = database.DB.get_user(chat_id, target_user.id) user = database.DB.get_user(bot.id, chat_id, target_user.id)
if user is not None and user['about'] is not None: if user is not None and user['about'] is not None:
response = Italic(full_name) + '\n' + user['about'] response = Italic(full_name) + '\n' + user['about']
await message.answer(**response.as_kwargs()) await message.answer(**response.as_kwargs())
@ -115,12 +115,12 @@ async def whois_handler(message: Message):
@router.message(F.text == "!сегодня") @router.message(F.text == "!сегодня")
async def stats_today_handler(message: Message, bot: Bot): async def stats_today_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_messages_today(chat_id) top_users = database.DB.get_top_messages_today(bot.id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Сегодня еще никто не писал.') await message.answer('Сегодня еще никто не писал.')
return return
@ -134,12 +134,12 @@ async def stats_today_handler(message: Message, bot: Bot):
@router.message(F.text == "!месяц") @router.message(F.text == "!месяц")
async def stats_month_handler(message: Message, bot: Bot): async def stats_month_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_messages_month(chat_id) top_users = database.DB.get_top_messages_month(bot.id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('В этом месяце еще никто не писал.') await message.answer('В этом месяце еще никто не писал.')
return return
@ -153,12 +153,12 @@ async def stats_month_handler(message: Message, bot: Bot):
@router.message(F.text == "!молчуны") @router.message(F.text == "!молчуны")
async def silent_handler(message: Message, bot: Bot): async def silent_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_silent(chat_id, 14) top_users = database.DB.get_top_silent(bot.id, chat_id, 14)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Молчунов нет. Все молодцы!') await message.answer('Молчунов нет. Все молодцы!')
return return
@ -171,12 +171,12 @@ async def silent_handler(message: Message, bot: Bot):
@router.message(F.text == "!предупреждения") @router.message(F.text == "!предупреждения")
async def warnings_handler(message: Message, bot: Bot): async def warnings_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_warnings(chat_id) top_users = database.DB.get_top_warnings(bot.id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Пока все спокойно. Продолжайте в том же духе.') await message.answer('Пока все спокойно. Продолжайте в том же духе.')
return return
@ -189,7 +189,7 @@ async def warnings_handler(message: Message, bot: Bot):
@router.message(F.text == "!проверка") @router.message(F.text == "!проверка")
async def check_rules_violation_handler(message: Message, bot: Bot): async def check_rules_violation_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot.id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -207,14 +207,12 @@ async def check_rules_violation_handler(message: Message, bot: Bot):
prompt += chat_rules + '\n\n' prompt += chat_rules + '\n\n'
prompt += 'Проверь, не нарушают ли правила следующие сообщения (если нарушают, то укажи пункты правил):' prompt += 'Проверь, не нарушают ли правила следующие сообщения (если нарушают, то укажи пункты правил):'
chat_prompt = chat['ai_prompt']
ai_message = ai_agent.Message(user_name=await get_user_name_for_ai(message.from_user), text=prompt) ai_message = ai_agent.Message(user_name=await get_user_name_for_ai(message.from_user), text=prompt)
ai_fwd_messages = [ai_agent.Message(user_name=await get_user_name_for_ai(message.reply_to_message.from_user), ai_fwd_messages = [ai_agent.Message(user_name=await get_user_name_for_ai(message.reply_to_message.from_user),
text=message.reply_to_message.text)] text=message.reply_to_message.text)]
await message.answer( await message.answer(
await utils.run_with_progress( await utils.run_with_progress(
partial(ai_agent.agent.get_group_chat_reply, chat_id, chat_prompt, ai_message, ai_fwd_messages), partial(ai_agent.agent.get_group_chat_reply, bot.id, chat_id, ai_message, ai_fwd_messages),
partial(bot.send_chat_action, chat_id, 'typing'), partial(bot.send_chat_action, chat_id, 'typing'),
interval=4)) interval=4))

View file

@ -2,8 +2,9 @@ import datetime
import traceback import traceback
from asyncio import sleep from asyncio import sleep
from aiogram import Bot from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest, TelegramMigrateToChat from aiogram.exceptions import TelegramBadRequest
from aiogram.types import ChatMemberBanned, ChatMemberLeft from aiogram.types import ChatMemberBanned, ChatMemberLeft
from aiogram.utils.formatting import Bold from aiogram.utils.formatting import Bold
@ -25,51 +26,50 @@ async def is_user_in_chat(bot: Bot, chat_id: int, user_id: int) -> bool:
async def cleanup_chats(bot: Bot): async def cleanup_chats(bot: Bot):
me = await bot.get_me() for chat in database.DB.get_chats(bot.id):
chat_id = chat['chat_id']
for chat in database.DB.get_chats():
chat_id = chat['id']
if chat_id > 0: if chat_id > 0:
# TODO # TODO
continue continue
if not await is_user_in_chat(bot, chat_id, me.id): if not await is_user_in_chat(bot, chat_id, bot.id):
database.DB.chat_delete(chat_id) database.DB.chat_delete(bot.id, chat_id)
print(f"Чат (bot_id={bot.id}, chat_id={chat_id}) удален.")
async def cleanup_users(bot: Bot): async def cleanup_users(bot: Bot):
for chat in database.DB.get_chats(): for chat in database.DB.get_chats(bot.id):
if chat['active'] == 0: if chat['active'] == 0:
continue continue
chat_id = chat['id'] chat_id = chat['chat_id']
for user in database.DB.get_top_silent(bot_id=bot.id, chat_id=chat_id, threshold_days=14):
for user in database.DB.get_top_silent(chat_id=chat_id, threshold_days=14):
user_id = user['user_id'] user_id = user['user_id']
if not await is_user_in_chat(bot, chat_id, user_id): if not await is_user_in_chat(bot, chat_id, user_id):
database.DB.delete_user(chat_id, user_id) database.DB.delete_user(bot.id, chat_id, user_id)
print(f'Из чата (id={chat_id}) удален пользователь (id={user_id})') print(f'Из чата (bot_id={bot.id}, chat_id={chat_id}) удален пользователь (id={user_id})')
async def reset_counters(reset_month: bool, bot: Bot): async def reset_counters(reset_month: bool, bot: Bot):
database.DB.reset_messages_today() database.DB.reset_messages_today(bot.id)
print('Дневные счетчики сброшены.')
if reset_month: if not reset_month:
for chat in database.DB.get_chats(): return
for chat in database.DB.get_chats(bot.id):
if chat['active'] == 0: if chat['active'] == 0:
continue continue
top_users = database.DB.get_top_messages_month(chat['id']) chat_id = chat['chat_id']
top_users = database.DB.get_top_messages_month(bot.id, chat['chat_id'])
if len(top_users) == 0: if len(top_users) == 0:
continue continue
message = Bold('Итоговая статистика за прошедший месяц') + '\n' message = Bold('Итоговая статистика за прошедший месяц') + '\n'
message += await format_rating(chat['id'], top_users, bot) message += await format_rating(chat_id, top_users, bot)
await bot.send_message(chat_id=chat['id'], **message.as_kwargs()) await bot.send_message(chat_id=chat_id, **message.as_kwargs())
database.DB.reset_messages_month() database.DB.reset_messages_month(bot.id)
print('Месячные счетчики сброшены.')
async def wait_until(target_time: datetime.datetime): async def wait_until(target_time: datetime.datetime):

View file

@ -6,17 +6,31 @@ class TgDatabase(database.BasicDatabase):
super().__init__(connection_string) super().__init__(connection_string)
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS chats ( CREATE TABLE IF NOT EXISTS bots (
id BIGINT NOT NULL, id BIGINT NOT NULL,
active TINYINT NOT NULL DEFAULT 0, owner_id BIGINT NOT NULL,
rules VARCHAR(4000), api_token VARCHAR(64) NOT NULL,
greeting_join VARCHAR(2000), ai_prompt VARCHAR(4000) DEFAULT NULL,
ai_prompt VARCHAR(4000), group_chats_allowed TINYINT NOT NULL DEFAULT 1,
private_chats_allowed TINYINT NOT NULL DEFAULT 1,
PRIMARY KEY (id)) PRIMARY KEY (id))
""") """)
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS chats (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL,
active TINYINT NOT NULL DEFAULT 0,
rules VARCHAR(4000),
greeting_join VARCHAR(2000),
ai_prompt VARCHAR(4000),
PRIMARY KEY (bot_id, chat_id),
CONSTRAINT fk_chats_bots FOREIGN KEY (bot_id) REFERENCES bots (id) ON DELETE CASCADE ON UPDATE CASCADE)
""")
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL, chat_id BIGINT NOT NULL,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
last_message BIGINT NOT NULL DEFAULT 0, last_message BIGINT NOT NULL DEFAULT 0,
@ -24,18 +38,19 @@ class TgDatabase(database.BasicDatabase):
messages_month SMALLINT NOT NULL DEFAULT 0, messages_month SMALLINT NOT NULL DEFAULT 0,
warnings TINYINT NOT NULL DEFAULT 0, warnings TINYINT NOT NULL DEFAULT 0,
about VARCHAR(1000), about VARCHAR(1000),
PRIMARY KEY (chat_id, user_id), PRIMARY KEY (bot_id, chat_id, user_id),
CONSTRAINT fk_users_chats FOREIGN KEY (chat_id) REFERENCES chats (id) ON UPDATE CASCADE ON DELETE CASCADE) CONSTRAINT fk_users_chats FOREIGN KEY (bot_id, chat_id) REFERENCES chats (bot_id, chat_id) ON UPDATE CASCADE ON DELETE CASCADE)
""") """)
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS contexts ( CREATE TABLE IF NOT EXISTS contexts (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL, chat_id BIGINT NOT NULL,
message_id BIGINT, message_id BIGINT,
role VARCHAR(16) NOT NULL, role VARCHAR(16) NOT NULL,
content VARCHAR(2000) NOT NULL, content VARCHAR(2000) NOT NULL,
UNIQUE KEY contexts_unique (chat_id, message_id), UNIQUE KEY contexts_unique (bot_id, chat_id, message_id),
CONSTRAINT fk_contexts_chats FOREIGN KEY (chat_id) REFERENCES chats (id) ON UPDATE CASCADE ON DELETE CASCADE) CONSTRAINT fk_contexts_chats FOREIGN KEY (bot_id, chat_id) REFERENCES chats (bot_id, chat_id) ON UPDATE CASCADE ON DELETE CASCADE)
""") """)
self.conn.commit() self.conn.commit()

View file

@ -1,6 +1,6 @@
import json import json
from vkbottle.bot import Bot as VkBot from vkbottle.bot import Bot, run_multibot
from ai_agent import create_ai_agent from ai_agent import create_ai_agent
@ -8,6 +8,7 @@ import vk.vk_database as database
from . import handlers from . import handlers
from . import tasks from . import tasks
from .utils import MyAPI
if __name__ == '__main__': if __name__ == '__main__':
@ -15,13 +16,20 @@ if __name__ == '__main__':
config = json.load(file) config = json.load(file)
print('Конфигурация загружена.') print('Конфигурация загружена.')
bot = VkBot(config['api_token'], labeler=handlers.labeler)
database.create_database(config['db_connection_string']) database.create_database(config['db_connection_string'])
create_ai_agent(config['openrouter_token'], create_ai_agent(config['openrouter_token'],
config['openrouter_model'], config['openrouter_model'],
config['openrouter_model_temp'], config['openrouter_model_temp'],
database.DB) database.DB)
bot.loop_wrapper.on_startup.append(tasks.startup_task(bot.api)) bot = Bot(labeler=handlers.labeler)
bot.loop_wrapper.add_task(tasks.daily_maintenance_task(bot.api))
bot.run_forever() apis: list[MyAPI] = []
for item in database.DB.get_bots():
api = MyAPI(item['id'], item['api_token'])
bot.loop_wrapper.on_startup.append(tasks.startup_task(api))
bot.loop_wrapper.add_task(tasks.daily_maintenance_task(api))
apis.append(api)
run_multibot(bot, apis)

View file

@ -3,14 +3,16 @@ from vkbottle.framework.labeler import BotLabeler
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.utils import *
labeler = BotLabeler() labeler = BotLabeler()
@labeler.chat_message(action=['chat_invite_user', 'chat_invite_user_by_link']) @labeler.chat_message(action=['chat_invite_user', 'chat_invite_user_by_link'])
async def user_join_handler(message: Message): async def user_join_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
@ -37,11 +39,12 @@ async def user_join_handler(message: Message):
@labeler.chat_message(action=['chat_kick_user']) @labeler.chat_message(action=['chat_kick_user'])
async def user_leave_handler(message: Message): async def user_leave_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
user_id = message.action.member_id user_id = message.action.member_id
if user_id != message.from_id: if user_id != message.from_id:
database.DB.delete_user(chat_id, user_id) database.DB.delete_user(bot_id, chat_id, user_id)

View file

@ -8,6 +8,7 @@ import utils
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.utils import *
labeler = BotLabeler() labeler = BotLabeler()
@ -22,8 +23,9 @@ def vk_user_is_admin(user_id: int, chat_members: MessagesGetConversationMembers)
@labeler.chat_message(text="!старт") @labeler.chat_message(text="!старт")
async def start_handler(message: Message): async def start_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot_id, chat_id)
chat_members = await message.ctx_api.messages.get_conversation_members(peer_id=chat_id, extended=False) chat_members = await message.ctx_api.messages.get_conversation_members(peer_id=chat_id, extended=False)
@ -31,21 +33,22 @@ async def start_handler(message: Message):
await message.answer(MESSAGE_PERMISSION_DENIED) await message.answer(MESSAGE_PERMISSION_DENIED)
return return
database.DB.chat_update(chat_id, active=1) database.DB.chat_update(bot_id, chat_id, active=1)
for member in chat_members.items: for member in chat_members.items:
# Пропустить ботов # Пропустить ботов
if member.member_id < 0: if member.member_id < 0:
continue continue
database.DB.create_user_if_not_exists(chat_id, member.member_id) database.DB.create_user_if_not_exists(bot_id, chat_id, member.member_id)
await message.answer('Готова к работе!') await message.answer('Готова к работе!')
@labeler.chat_message(text="!правила") @labeler.chat_message(text="!правила")
async def rules_handler(message: Message): async def rules_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -61,14 +64,15 @@ async def rules_handler(message: Message):
await message.answer(MESSAGE_PERMISSION_DENIED) await message.answer(MESSAGE_PERMISSION_DENIED)
return return
database.DB.chat_update(chat_id, rules=message.reply_message.text) database.DB.chat_update(bot_id, chat_id, rules=message.reply_message.text)
await message.answer('Правила чата изменены.') await message.answer('Правила чата изменены.')
@labeler.chat_message(text="!приветствие") @labeler.chat_message(text="!приветствие")
async def set_greeting_join_handler(message: Message): async def set_greeting_join_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -82,14 +86,15 @@ async def set_greeting_join_handler(message: Message):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, greeting_join=message.reply_message.text) database.DB.chat_update(bot_id, chat_id, greeting_join=message.reply_message.text)
await message.answer('Приветствие изменено.') await message.answer('Приветствие изменено.')
@labeler.chat_message(text="!возвращение") @labeler.chat_message(text="!возвращение")
async def set_greeting_rejoin_handler(message: Message): async def set_greeting_rejoin_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -103,14 +108,15 @@ async def set_greeting_rejoin_handler(message: Message):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, greeting_rejoin=message.reply_message.text) database.DB.chat_update(bot_id, chat_id, greeting_rejoin=message.reply_message.text)
await message.answer('Приветствие при возвращении изменено.') await message.answer('Приветствие при возвращении изменено.')
@labeler.chat_message(text="!деньрождения") @labeler.chat_message(text="!деньрождения")
async def set_birthday_handler(message: Message): async def set_birthday_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -124,14 +130,15 @@ async def set_birthday_handler(message: Message):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, birthday_message=message.reply_message.text) database.DB.chat_update(bot_id, chat_id, birthday_message=message.reply_message.text)
await message.answer('Уведомление о дне рождения изменено.') await message.answer('Уведомление о дне рождения изменено.')
@labeler.chat_message(text="!личность") @labeler.chat_message(text="!личность")
async def set_ai_prompt_handler(message: Message): async def set_ai_prompt_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -145,15 +152,16 @@ async def set_ai_prompt_handler(message: Message):
await message.answer(MESSAGE_NEED_REPLY) await message.answer(MESSAGE_NEED_REPLY)
return return
database.DB.chat_update(chat_id, ai_prompt=message.reply_message.text) database.DB.chat_update(bot_id, chat_id, ai_prompt=message.reply_message.text)
ai_agent.agent.clear_chat_context(chat_id) ai_agent.agent.clear_chat_context(bot_id, chat_id)
await message.answer('Личность ИИ изменена.') await message.answer('Личность ИИ изменена.')
@labeler.chat_message(text="!предупреждение") @labeler.chat_message(text="!предупреждение")
async def warning_handler(message: Message): async def warning_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -169,10 +177,10 @@ async def warning_handler(message: Message):
return return
user_id = message.reply_message.from_id user_id = message.reply_message.from_id
database.DB.create_user_if_not_exists(chat_id, user_id) database.DB.create_user_if_not_exists(bot_id, chat_id, user_id)
database.DB.user_increment_warnings(chat_id, user_id) database.DB.user_increment_warnings(bot_id, chat_id, user_id)
user = database.DB.get_user(chat_id, user_id) user = database.DB.get_user(bot_id, chat_id, user_id)
user_info = await message.ctx_api.users.get(user_ids=[user_id], name_case='gen') user_info = await message.ctx_api.users.get(user_ids=[user_id], name_case='gen')
if len(user_info) == 1: if len(user_info) == 1:
@ -186,8 +194,9 @@ async def warning_handler(message: Message):
@labeler.chat_message(text="!исключить") @labeler.chat_message(text="!исключить")
async def ban_handler(message: Message): async def ban_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -205,10 +214,10 @@ async def ban_handler(message: Message):
user_id = message.reply_message.from_id user_id = message.reply_message.from_id
try: try:
await message.ctx_api.messages.remove_chat_user(chat_id - 2000000000, member_id=user_id) await message.ctx_api.messages.remove_chat_user(bot_id, chat_id - 2000000000, member_id=user_id)
if user_id > 0: if user_id > 0:
database.DB.delete_user(chat_id, user_id) database.DB.delete_user(bot_id, chat_id, user_id)
user_info = await message.ctx_api.users.get(user_ids=[user_id]) user_info = await message.ctx_api.users.get(user_ids=[user_id])
await message.answer('Участник {} {} исключен.'.format(user_info[0].first_name, user_info[0].last_name)) await message.answer('Участник {} {} исключен.'.format(user_info[0].first_name, user_info[0].last_name))

View file

@ -11,7 +11,7 @@ import utils
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.utils import get_user_name_for_ai from vk.utils import *
labeler = BotLabeler() labeler = BotLabeler()
@ -21,8 +21,9 @@ bot_user: Optional[GroupsGroup] = None
# Обычные сообщения (не команды и не действия) # Обычные сообщения (не команды и не действия)
@labeler.chat_message() @labeler.chat_message()
async def any_message_handler(message: Message): async def any_message_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
return return
@ -35,9 +36,9 @@ async def any_message_handler(message: Message):
return return
user_id = message.from_id user_id = message.from_id
database.DB.create_user_if_not_exists(chat_id, user_id) database.DB.create_user_if_not_exists(bot_id, chat_id, user_id)
database.DB.user_set_last_message(chat_id, user_id, utils.posix_time()) database.DB.user_set_last_message(bot_id, chat_id, user_id, utils.posix_time())
database.DB.user_increment_messages(chat_id, user_id) database.DB.user_increment_messages(bot_id, chat_id, user_id)
global bot_user global bot_user
if bot_user is None: if bot_user is None:
@ -65,7 +66,7 @@ async def any_message_handler(message: Message):
await message.reply(MESSAGE_NOT_TEXT) await message.reply(MESSAGE_NOT_TEXT)
return return
last_id = ai_agent.agent.get_last_assistant_message_id(chat_id) last_id = ai_agent.agent.get_last_assistant_message_id(bot_id, chat_id)
if message.reply_message.message_id != last_id: if message.reply_message.message_id != last_id:
# Оригинального сообщения нет в контексте, или оно не последнее # Оригинального сообщения нет в контексте, или оно не последнее
if len(message.reply_message.text) > 0: if len(message.reply_message.text) > 0:
@ -83,7 +84,8 @@ async def any_message_handler(message: Message):
if message.reply_message: if message.reply_message:
if len(message.reply_message.text) > 0: if len(message.reply_message.text) > 0:
ai_fwd_messages.append( ai_fwd_messages.append(
ai_agent.Message(user_name=await get_user_name_for_ai(message.ctx_api, message.reply_message.from_id), ai_agent.Message(
user_name=await get_user_name_for_ai(message.ctx_api, message.reply_message.from_id),
text=message.reply_message.text)) text=message.reply_message.text))
else: else:
await message.reply(MESSAGE_NOT_TEXT) await message.reply(MESSAGE_NOT_TEXT)
@ -102,10 +104,10 @@ async def any_message_handler(message: Message):
ai_message.message_id = message.conversation_message_id ai_message.message_id = message.conversation_message_id
answer, success = await utils.run_with_progress( answer, success = await utils.run_with_progress(
partial(ai_agent.agent.get_group_chat_reply, chat_id, ai_message, ai_fwd_messages), partial(ai_agent.agent.get_group_chat_reply, bot_id, chat_id, ai_message, ai_fwd_messages),
partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'), partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'),
interval=4) interval=4)
answer_id = (await message.reply(answer)).conversation_message_id answer_id = (await message.reply(answer)).conversation_message_id
if success: if success:
ai_agent.agent.set_last_response_id(chat_id, answer_id) ai_agent.agent.set_last_response_id(bot_id, chat_id, answer_id)

View file

@ -9,38 +9,43 @@ import utils
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.utils import *
labeler = BotLabeler() labeler = BotLabeler()
@labeler.private_message(text="!старт") @labeler.private_message(text="!старт")
async def start_handler(message: Message): async def start_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot_id, chat_id)
database.DB.chat_update(chat_id, active=1) database.DB.chat_update(bot_id, chat_id, active=1)
await message.answer("Привет!") await message.answer("Привет!")
@labeler.private_message(RegexRule(r"^!личность ((?:.|\n)+)")) @labeler.private_message(RegexRule(r"^!личность ((?:.|\n)+)"))
async def set_prompt_handler(message: Message, match): async def set_prompt_handler(message: Message, match):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot_id, chat_id)
database.DB.chat_update(chat_id, ai_prompt=match[0]) database.DB.chat_update(bot_id, chat_id, ai_prompt=match[0])
await message.answer("Личность ИИ изменена.") await message.answer("Личность ИИ изменена.")
@labeler.private_message(text="!сброс") @labeler.private_message(text="!сброс")
async def reset_context_handler(message: Message): async def reset_context_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
database.DB.create_chat_if_not_exists(chat_id) database.DB.create_chat_if_not_exists(bot_id, chat_id)
ai_agent.agent.clear_chat_context(chat_id) ai_agent.agent.clear_chat_context(bot_id, chat_id)
await message.answer("Контекст очищен.") await message.answer("Контекст очищен.")
@labeler.private_message() @labeler.private_message()
async def any_message_handler(message: Message): async def any_message_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
if len(message.text) == 0: if len(message.text) == 0:
@ -48,10 +53,10 @@ async def any_message_handler(message: Message):
return return
answer, success = await utils.run_with_progress( answer, success = await utils.run_with_progress(
partial(ai_agent.agent.get_private_chat_reply, chat_id, message.text, message.message_id), partial(ai_agent.agent.get_private_chat_reply, bot_id, chat_id, message.text, message.message_id),
partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'), partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'),
interval=4) interval=4)
answer_id = (await message.answer(answer)).message_id answer_id = (await message.answer(answer)).message_id
if success: if success:
ai_agent.agent.set_last_response_id(chat_id, answer_id) ai_agent.agent.set_last_response_id(bot_id, chat_id, answer_id)

View file

@ -1,7 +1,7 @@
from functools import partial from functools import partial
from typing import List, Any from typing import List, Any
from vkbottle import bold, italic, API from vkbottle import bold, italic
from vkbottle.bot import Message from vkbottle.bot import Message
from vkbottle.framework.labeler import BotLabeler from vkbottle.framework.labeler import BotLabeler
@ -10,7 +10,7 @@ import utils
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.utils import get_user_name_for_ai from vk.utils import *
labeler = BotLabeler() labeler = BotLabeler()
@ -42,8 +42,9 @@ def calculate_total_messages(top_users: List[Any]) -> int:
# noinspection SpellCheckingInspection # noinspection SpellCheckingInspection
@labeler.chat_message(text="!помощь") @labeler.chat_message(text="!помощь")
async def rules_handler(message: Message): async def rules_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -77,14 +78,15 @@ async def rules_handler(message: Message):
@labeler.chat_message(text="") @labeler.chat_message(text="")
async def about_handler(message: Message): async def about_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
user_id = message.from_id user_id = message.from_id
user = database.DB.create_user_if_not_exists(chat_id, user_id) user = database.DB.create_user_if_not_exists(bot_id, chat_id, user_id)
if message.reply_message is None: if message.reply_message is None:
info = await message.ctx_api.users.get(user_ids=[user_id]) info = await message.ctx_api.users.get(user_ids=[user_id])
if len(info) == 1: if len(info) == 1:
@ -96,7 +98,7 @@ async def about_handler(message: Message):
await message.answer('Не могу получить информацию об участнике.') await message.answer('Не могу получить информацию об участнике.')
else: else:
if len(message.reply_message.text) > 0: if len(message.reply_message.text) > 0:
database.DB.user_update(chat_id, user_id, about=message.reply_message.text) database.DB.user_update(bot_id, chat_id, user_id, about=message.reply_message.text)
await message.answer('Визитка изменена.') await message.answer('Визитка изменена.')
else: else:
await message.answer('Извините, но я понимаю только текст.') await message.answer('Извините, но я понимаю только текст.')
@ -104,8 +106,9 @@ async def about_handler(message: Message):
@labeler.chat_message(text="!кто") @labeler.chat_message(text="!кто")
async def whois_handler(message: Message): async def whois_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -117,7 +120,7 @@ async def whois_handler(message: Message):
target_user_id = message.reply_message.from_id target_user_id = message.reply_message.from_id
info = await message.ctx_api.users.get(user_ids=[target_user_id]) info = await message.ctx_api.users.get(user_ids=[target_user_id])
if len(info) == 1: if len(info) == 1:
user = database.DB.get_user(chat_id, target_user_id) user = database.DB.get_user(bot_id, chat_id, target_user_id)
if user is not None and user['about'] is not None: if user is not None and user['about'] is not None:
await message.answer(italic(f'{info[0].first_name} {info[0].last_name}') + '\n' + user['about']) await message.answer(italic(f'{info[0].first_name} {info[0].last_name}') + '\n' + user['about'])
else: else:
@ -128,13 +131,14 @@ async def whois_handler(message: Message):
@labeler.chat_message(text="!сегодня") @labeler.chat_message(text="!сегодня")
async def stats_today_handler(message: Message): async def stats_today_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_messages_today(chat_id) top_users = database.DB.get_top_messages_today(bot_id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Сегодня еще никто не писал.') await message.answer('Сегодня еще никто не писал.')
return return
@ -147,13 +151,14 @@ async def stats_today_handler(message: Message):
@labeler.chat_message(text="!месяц") @labeler.chat_message(text="!месяц")
async def stats_month_handler(message: Message): async def stats_month_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_messages_month(chat_id) top_users = database.DB.get_top_messages_month(bot_id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('В этом месяце еще никто не писал.') await message.answer('В этом месяце еще никто не писал.')
return return
@ -166,13 +171,14 @@ async def stats_month_handler(message: Message):
@labeler.chat_message(text="!молчуны") @labeler.chat_message(text="!молчуны")
async def silent_handler(message: Message): async def silent_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_silent(chat_id, 14) top_users = database.DB.get_top_silent(bot_id, chat_id, 14)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Молчунов нет. Все молодцы!') await message.answer('Молчунов нет. Все молодцы!')
return return
@ -184,13 +190,14 @@ async def silent_handler(message: Message):
@labeler.chat_message(text="!предупреждения") @labeler.chat_message(text="!предупреждения")
async def warnings_handler(message: Message): async def warnings_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
top_users = database.DB.get_top_warnings(chat_id) top_users = database.DB.get_top_warnings(bot_id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
await message.answer('Пока все спокойно. Продолжайте в том же духе.') await message.answer('Пока все спокойно. Продолжайте в том же духе.')
return return
@ -202,16 +209,17 @@ async def warnings_handler(message: Message):
@labeler.chat_message(text="!поздравление") @labeler.chat_message(text="!поздравление")
async def no_birthday_handler(message: Message): async def no_birthday_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
user_id = message.from_id user_id = message.from_id
user = database.DB.create_user_if_not_exists(chat_id, user_id) user = database.DB.create_user_if_not_exists(bot_id, chat_id, user_id)
happy_birthday = 1 if user['happy_birthday'] == 0 else 0 happy_birthday = 1 if user['happy_birthday'] == 0 else 0
database.DB.user_toggle_happy_birthday(chat_id, user_id, happy_birthday) database.DB.user_toggle_happy_birthday(bot_id, chat_id, user_id, happy_birthday)
if happy_birthday == 1: if happy_birthday == 1:
await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.') await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.')
@ -221,8 +229,9 @@ async def no_birthday_handler(message: Message):
@labeler.chat_message(text="!проверка") @labeler.chat_message(text="!проверка")
async def check_rules_violation_handler(message: Message): async def check_rules_violation_handler(message: Message):
bot_id = get_bot_id(message.ctx_api)
chat_id = message.peer_id chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(bot_id, chat_id)
if chat['active'] == 0: if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE) await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return return
@ -236,8 +245,6 @@ async def check_rules_violation_handler(message: Message):
prompt += chat_rules + '\n\n' prompt += chat_rules + '\n\n'
prompt += 'Проверь, не нарушают ли правила следующие сообщения (если нарушают, то укажи пункты правил):' prompt += 'Проверь, не нарушают ли правила следующие сообщения (если нарушают, то укажи пункты правил):'
chat_prompt = chat['ai_prompt']
ai_message = ai_agent.Message(user_name=await get_user_name_for_ai(message.ctx_api, message.from_id), text=prompt) ai_message = ai_agent.Message(user_name=await get_user_name_for_ai(message.ctx_api, message.from_id), text=prompt)
ai_fwd_messages: list[ai_agent.Message] = [] ai_fwd_messages: list[ai_agent.Message] = []
if message.reply_message is not None and len(message.reply_message.text) > 0: if message.reply_message is not None and len(message.reply_message.text) > 0:
@ -257,6 +264,6 @@ async def check_rules_violation_handler(message: Message):
await message.answer( await message.answer(
await utils.run_with_progress( await utils.run_with_progress(
partial(ai_agent.agent.get_group_chat_reply, chat_id, chat_prompt, ai_message, ai_fwd_messages), partial(ai_agent.agent.get_group_chat_reply, bot_id, chat_id, ai_message, ai_fwd_messages),
partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'), partial(message.ctx_api.messages.set_activity, peer_id=chat_id, type='typing'),
interval=4)) interval=4))

View file

@ -2,22 +2,32 @@ import datetime
import traceback import traceback
from asyncio import sleep from asyncio import sleep
from vkbottle import API, bold from vkbottle import API, bold
from vkbottle_types.codegen.objects import UsersFields
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from vk.handlers.user import format_rating from vk.handlers.user import format_rating
from vk.utils import *
async def cleanup_chats(api: API):
# TODO
pass
async def cleanup_users(api: API): async def cleanup_users(api: API):
for chat in database.DB.get_chats(): bot_id = get_bot_id(api)
for chat in database.DB.get_chats(bot_id):
if chat['active'] == 0: if chat['active'] == 0:
continue continue
chat_id = chat['id'] chat_id = chat['chat_id']
members = await api.messages.get_conversation_members(peer_id=chat_id, extended=False) members = await api.messages.get_conversation_members(peer_id=chat_id, extended=False)
for user in database.DB.get_top_silent(chat_id=chat_id, threshold_days=14): for user in database.DB.get_top_silent(bot_id, chat_id, threshold_days=14):
user_id = user['user_id'] user_id = user['user_id']
found = False found = False
for item in members.items: for item in members.items:
@ -26,48 +36,52 @@ async def cleanup_users(api: API):
break break
if not found: if not found:
database.DB.delete_user(chat_id, user_id) database.DB.delete_user(bot_id, chat_id, user_id)
print(f'Из чата (id={chat_id}) удален пользователь (id={user_id})') print(f'Из чата (bot_id={bot_id}, chat_id={chat_id}) удален пользователь (id={user_id})')
async def reset_counters(reset_month: bool, api: API): async def reset_counters(reset_month: bool, api: API):
database.DB.reset_messages_today() bot_id = get_bot_id(api)
print('Дневные счетчики сброшены.') database.DB.reset_messages_today(bot_id)
if reset_month: if not reset_month:
for chat in database.DB.get_chats(): return
for chat in database.DB.get_chats(bot_id):
if chat['active'] == 0: if chat['active'] == 0:
continue continue
top_users = database.DB.get_top_messages_month(chat['id']) chat_id = chat['chat_id']
top_users = database.DB.get_top_messages_month(bot_id, chat_id)
if len(top_users) == 0: if len(top_users) == 0:
continue continue
message = bold('Итоговая статистика за прошедший месяц') + '\n' message = bold('Итоговая статистика за прошедший месяц') + '\n'
message += await format_rating(top_users, api) message += await format_rating(top_users, api)
# noinspection PyArgumentList # noinspection PyArgumentList
await api.messages.send(random_id=0, peer_id=chat['id'], await api.messages.send(random_id=0, peer_id=chat_id,
message=str(message), format_data=message.as_raw_data()) message=str(message), format_data=message.as_raw_data())
database.DB.reset_messages_month() database.DB.reset_messages_month(bot_id)
print('Месячные счетчики сброшены.')
async def check_birthdays(api: API): async def check_birthdays(api: API):
chats = database.DB.get_chats() bot_id = get_bot_id(api)
chats = database.DB.get_chats(bot_id)
for chat in chats: for chat in chats:
if chat['active'] == 0: if chat['active'] == 0:
continue continue
chat_id = chat['id'] chat_id = chat['chat_id']
members = await api.messages.get_conversation_members(peer_id=chat_id, extended=False, fields=['bdate']) members = await api.messages.get_conversation_members(peer_id=chat_id, extended=False,
fields=[UsersFields.BDATE])
today = datetime.datetime.today() today = datetime.datetime.today()
for item in members.items: for item in members.items:
user_id = item.member_id user_id = item.member_id
if user_id < 0: if user_id < 0:
continue continue
user = database.DB.create_user_if_not_exists(chat_id, user_id) user = database.DB.create_user_if_not_exists(bot_id, chat_id, user_id)
for profile in members.profiles: for profile in members.profiles:
if profile.id == user_id: if profile.id == user_id:
@ -111,9 +125,22 @@ async def daily_maintenance_task(api: API):
while True: while True:
await wait_until(target_datetime) await wait_until(target_datetime)
try:
await cleanup_chats(api)
except Exception:
print(traceback.format_exc())
try: try:
await cleanup_users(api) await cleanup_users(api)
except Exception:
print(traceback.format_exc())
try:
await reset_counters(target_datetime.day == 1, api) await reset_counters(target_datetime.day == 1, api)
except Exception:
print(traceback.format_exc())
try:
await check_birthdays(api) await check_birthdays(api)
except Exception: except Exception:
print(traceback.format_exc()) print(traceback.format_exc())

View file

@ -1,6 +1,17 @@
from vkbottle import API from vkbottle import API
class MyAPI(API):
def __init__(self, bot_id: int, api_token: str):
super().__init__(api_token)
self.bot_id = bot_id
def get_bot_id(api: API) -> int:
my_api: MyAPI = api
return my_api.bot_id
async def get_user_name_for_ai(api: API, user_id: int): async def get_user_name_for_ai(api: API, user_id: int):
user = await api.users.get(user_ids=[user_id]) user = await api.users.get(user_ids=[user_id])
if len(user) == 1: if len(user) == 1:

View file

@ -6,19 +6,33 @@ class VkDatabase(database.BasicDatabase):
super().__init__(connection_string) super().__init__(connection_string)
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS chats ( CREATE TABLE IF NOT EXISTS bots (
id BIGINT NOT NULL, id BIGINT NOT NULL,
owner_id BIGINT NOT NULL,
api_token VARCHAR(256) NOT NULL,
ai_prompt VARCHAR(4000) DEFAULT NULL,
group_chats_allowed TINYINT NOT NULL DEFAULT 1,
private_chats_allowed TINYINT NOT NULL DEFAULT 1,
PRIMARY KEY (id))
""")
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS chats (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL,
active TINYINT NOT NULL DEFAULT 0, active TINYINT NOT NULL DEFAULT 0,
rules VARCHAR(4000), rules VARCHAR(4000),
greeting_join VARCHAR(2000), greeting_join VARCHAR(2000),
greeting_rejoin VARCHAR(2000), greeting_rejoin VARCHAR(2000),
birthday_message VARCHAR(2000), birthday_message VARCHAR(2000),
ai_prompt VARCHAR(4000), ai_prompt VARCHAR(4000),
PRIMARY KEY (id)) PRIMARY KEY (bot_id, chat_id),
CONSTRAINT fk_chats_bots FOREIGN KEY (bot_id) REFERENCES bots (id) ON DELETE CASCADE ON UPDATE CASCADE)
""") """)
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL, chat_id BIGINT NOT NULL,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
last_message BIGINT NOT NULL DEFAULT 0, last_message BIGINT NOT NULL DEFAULT 0,
@ -27,24 +41,25 @@ class VkDatabase(database.BasicDatabase):
warnings TINYINT NOT NULL DEFAULT 0, warnings TINYINT NOT NULL DEFAULT 0,
happy_birthday TINYINT NOT NULL DEFAULT 1, happy_birthday TINYINT NOT NULL DEFAULT 1,
about VARCHAR(1000), about VARCHAR(1000),
PRIMARY KEY (chat_id, user_id), PRIMARY KEY (bot_id, chat_id, user_id),
CONSTRAINT fk_users_chats FOREIGN KEY (chat_id) REFERENCES chats (id) ON UPDATE CASCADE ON DELETE CASCADE) CONSTRAINT fk_users_chats FOREIGN KEY (bot_id, chat_id) REFERENCES chats (bot_id, chat_id) ON UPDATE CASCADE ON DELETE CASCADE)
""") """)
self.cursor.execute(""" self.cursor.execute("""
CREATE TABLE IF NOT EXISTS contexts ( CREATE TABLE IF NOT EXISTS contexts (
bot_id BIGINT NOT NULL,
chat_id BIGINT NOT NULL, chat_id BIGINT NOT NULL,
message_id BIGINT, message_id BIGINT,
role VARCHAR(16) NOT NULL, role VARCHAR(16) NOT NULL,
content VARCHAR(2000) NOT NULL, content VARCHAR(2000) NOT NULL,
UNIQUE KEY contexts_unique (chat_id, message_id), UNIQUE KEY contexts_unique (bot_id, chat_id, message_id),
CONSTRAINT fk_contexts_chats FOREIGN KEY (chat_id) REFERENCES chats (id) ON UPDATE CASCADE ON DELETE CASCADE) CONSTRAINT fk_contexts_chats FOREIGN KEY (bot_id, chat_id) REFERENCES chats (bot_id, chat_id) ON UPDATE CASCADE ON DELETE CASCADE)
""") """)
self.conn.commit() self.conn.commit()
def user_toggle_happy_birthday(self, chat_id: int, user_id: int, happy_birthday: int): def user_toggle_happy_birthday(self, bot_id: int, chat_id: int, user_id: int, happy_birthday: int):
self.user_update(chat_id, user_id, happy_birthday=happy_birthday) self.user_update(bot_id, chat_id, user_id, happy_birthday=happy_birthday)
DB: VkDatabase DB: VkDatabase