From 786360d1cf2d0c044bc2eef827b5d9974ae4a75f Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:17:39 +0300 Subject: [PATCH 01/17] feat: telegram bot manager server --- app/db.py | 6 +- app/tg.py | 46 ++++++++++++ debug/create_agent.py | 27 ++++---- requirements.txt | 1 + tg/__init__.py | 0 tg/bot/__init__.py | 0 tg/bot/kind/__init__.py | 0 tg/bot/kind/general/__init__.py | 0 tg/bot/kind/general/router.py | 119 ++++++++++++++++++++++++++++++++ tg/bot/kind/god/__init__.py | 0 tg/bot/kind/god/router.py | 31 +++++++++ tg/bot/kind/god/startup.py | 11 +++ tg/bot/pool.py | 72 +++++++++++++++++++ tg/bot/types/kind.py | 5 ++ tg/bot/types/router_obj.py | 12 ++++ tg/schedule/__init__.py | 0 tg/schedule/agent.py | 30 ++++++++ 17 files changed, 347 insertions(+), 13 deletions(-) create mode 100644 app/tg.py create mode 100644 tg/__init__.py create mode 100644 tg/bot/__init__.py create mode 100644 tg/bot/kind/__init__.py create mode 100644 tg/bot/kind/general/__init__.py create mode 100644 tg/bot/kind/general/router.py create mode 100644 tg/bot/kind/god/__init__.py create mode 100644 tg/bot/kind/god/router.py create mode 100644 tg/bot/kind/god/startup.py create mode 100644 tg/bot/pool.py create mode 100644 tg/bot/types/kind.py create mode 100644 tg/bot/types/router_obj.py create mode 100644 tg/schedule/__init__.py create mode 100644 tg/schedule/agent.py diff --git a/app/db.py b/app/db.py index 09e4398c..5abbefaa 100644 --- a/app/db.py +++ b/app/db.py @@ -38,7 +38,7 @@ def init_db( global engine if engine is None: engine = create_engine(conn_str) - # safe_migrate(engine) + safe_migrate(engine) # Initialize psycopg connection global conn @@ -83,6 +83,10 @@ class Agent(SQLModel, table=True): twitter_enabled: bool = Field(default=False) twitter_config: Optional[dict] = Field(sa_column=Column(JSONB, nullable=True)) twitter_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) + # if telegram_enabled, telegram_config will be checked + telegram_enabled: bool = Field(default=False) + telegram_config: Optional[dict] = Field(sa_column=Column(JSONB, nullable=True)) + telegram_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) # crestal skills crestal_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) # skills not require config diff --git a/app/tg.py b/app/tg.py new file mode 100644 index 00000000..1c01c60b --- /dev/null +++ b/app/tg.py @@ -0,0 +1,46 @@ +import asyncio +import logging +from os import getenv +import sys +from app.db import get_db, Agent, AgentQuota, init_db +from app.config import config +import signal +from tg.bot.pool import BotPool +from tg.schedule.agent import AgentScheduler + +BASE_URL = getenv("TG_BASE_URL") +WEB_SERVER_HOST = getenv("TG_SERVER_HOST", "127.0.0.1") +WEB_SERVER_PORT = getenv("TG_SERVER_PORT", "8081") + +logger = logging.getLogger(__name__) + + +def run_telegram_server() -> None: + # Initialize database connection + init_db(**config.db) + + # Signal handler for graceful shutdown + def signal_handler(signum, frame): + logger.info("Received termination signal. Shutting down gracefully...") + scheduler.shutdown() + sys.exit(0) + + # Register signal handlers + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + bot_pool = BotPool(BASE_URL) + + bot_pool.init_god_bot() + bot_pool.init_all_dispatchers() + + scheduler = AgentScheduler(bot_pool) + + loop = asyncio.new_event_loop() + loop.create_task(scheduler.start()) + + bot_pool.start(loop, WEB_SERVER_HOST, int(WEB_SERVER_PORT)) + + +if __name__ == "__main__": + run_telegram_server() diff --git a/debug/create_agent.py b/debug/create_agent.py index e717bc5d..0ac99534 100644 --- a/debug/create_agent.py +++ b/debug/create_agent.py @@ -6,7 +6,7 @@ if config.env == "local": # Set up logging configuration logging.basicConfig() - logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG) + logging.getLogger("sqlalchemy.engine").setLevel(logging.DEBUG) init_db(**config.db) db = next(get_db()) @@ -15,17 +15,20 @@ name="IntentKit", model="gpt-4o-mini", # This repetition could be omitted if default is intended prompt="", # Confirm if an empty prompt is acceptable - thought_enabled= False, # This field must be provided - thought_content = "", # Optional, provide if needed - thought_minutes = None, # Optional, provide if needed - cdp_enabled = True, - cdp_skills = [], # Confirm if loading all skills when empty is the desired behavior - cdp_wallet_data = "", # Assuming wallet_data was meant to be cdp_wallet_data - cdp_network_id = "base-sepolia", - twitter_enabled = False, - twitter_config = {}, # Ensure this dict structure aligns with expected config format - twitter_skills = [], # Confirm if no specific Twitter skills are to be enabled - common_skills = [], # Confirm if no common skills are to be added initially + thought_enabled=False, # This field must be provided + thought_content="", # Optional, provide if needed + thought_minutes=None, # Optional, provide if needed + cdp_enabled=True, + cdp_skills=[], # Confirm if loading all skills when empty is the desired behavior + cdp_wallet_data="", # Assuming wallet_data was meant to be cdp_wallet_data + cdp_network_id="base-sepolia", + twitter_enabled=False, + twitter_config={}, # Ensure this dict structure aligns with expected config format + twitter_skills=[], # Confirm if no specific Twitter skills are to be enabled + telegram_enabled=False, + telegram_config={}, # Ensure this dict structure aligns with expected config format + telegram_skills=[], # Confirm if no specific Telegram skills are to be enabled + common_skills=[], # Confirm if no common skills are to be added initially ) agent.create_or_update(db) diff --git a/requirements.txt b/requirements.txt index 4a694a9a..b0971c6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -116,3 +116,4 @@ uvicorn==0.32.1 web3==7.6.0 websockets==13.1 yarl==1.18.3 +aiogram==3.16.0 \ No newline at end of file diff --git a/tg/__init__.py b/tg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/bot/__init__.py b/tg/bot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/bot/kind/__init__.py b/tg/bot/kind/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/bot/kind/general/__init__.py b/tg/bot/kind/general/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/bot/kind/general/router.py b/tg/bot/kind/general/router.py new file mode 100644 index 00000000..11e12a23 --- /dev/null +++ b/tg/bot/kind/general/router.py @@ -0,0 +1,119 @@ +import logging +from typing import Any, Dict + +from aiogram import F, Router, html +from aiogram.filters import Command, CommandStart +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from aiogram.types import ( + KeyboardButton, + Message, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, +) + +general_router = Router() + + +class GeneralForm(StatesGroup): + name = State() + like_bots = State() + language = State() + + +@general_router.message(CommandStart()) +async def command_start(message: Message, state: FSMContext) -> None: + await state.set_state(GeneralForm.name) + await message.answer( + "Hi there! What's your name?", + reply_markup=ReplyKeyboardRemove(), + ) + + +@general_router.message(Command("cancel")) +@general_router.message(F.text.casefold() == "cancel") +async def cancel_handler(message: Message, state: FSMContext) -> None: + """ + Allow user to cancel any action + """ + current_state = await state.get_state() + if current_state is None: + return + + logging.info("Cancelling state %r", current_state) + await state.clear() + await message.answer( + "Cancelled.", + reply_markup=ReplyKeyboardRemove(), + ) + + +@general_router.message(GeneralForm.name) +async def process_name(message: Message, state: FSMContext) -> None: + await state.update_data(name=message.text) + await state.set_state(GeneralForm.like_bots) + await message.answer( + f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", + reply_markup=ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton(text="Yes"), + KeyboardButton(text="No"), + ] + ], + resize_keyboard=True, + ), + ) + + +@general_router.message(GeneralForm.like_bots, F.text.casefold() == "no") +async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None: + data = await state.get_data() + await state.clear() + await message.answer( + "Not bad not terrible.\nSee you soon.", + reply_markup=ReplyKeyboardRemove(), + ) + await show_summary(message=message, data=data, positive=False) + + +@general_router.message(GeneralForm.like_bots, F.text.casefold() == "yes") +async def process_like_write_bots(message: Message, state: FSMContext) -> None: + await state.set_state(GeneralForm.language) + + await message.reply( + "Cool! I'm too!\nWhat programming language did you use for it?", + reply_markup=ReplyKeyboardRemove(), + ) + + +@general_router.message(GeneralForm.like_bots) +async def process_unknown_write_bots(message: Message) -> None: + await message.reply("I don't understand you :(") + + +@general_router.message(GeneralForm.language) +async def process_language(message: Message, state: FSMContext) -> None: + data = await state.update_data(language=message.text) + await state.clear() + + if message.text.casefold() == "python": + await message.reply( + "Python, you say? That's the language that makes my circuits light up! 😉" + ) + + await show_summary(message=message, data=data) + + +async def show_summary( + message: Message, data: Dict[str, Any], positive: bool = True +) -> None: + name = data["name"] + language = data.get("language", "") + text = f"I'll keep in mind that, {html.quote(name)}, " + text += ( + f"you like to write bots with {html.quote(language)}." + if positive + else "you don't like to write bots, so sad..." + ) + await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) diff --git a/tg/bot/kind/god/__init__.py b/tg/bot/kind/god/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/bot/kind/god/router.py b/tg/bot/kind/god/router.py new file mode 100644 index 00000000..bb2aa974 --- /dev/null +++ b/tg/bot/kind/god/router.py @@ -0,0 +1,31 @@ +from typing import Any, Dict, Union + +from aiogram import Bot, F, Router +from aiogram.exceptions import TelegramUnauthorizedError +from aiogram.filters import Command, CommandObject +from aiogram.types import Message +from aiogram.utils.token import TokenValidationError, validate_token + +god_router = Router() + + +def is_bot_token(value: str) -> Union[bool, Dict[str, Any]]: + try: + validate_token(value) + except TokenValidationError: + return False + return True + + +@god_router.message(Command("add", magic=F.args.func(is_bot_token))) +async def command_add_bot(message: Message, command: CommandObject, bot: Bot) -> Any: + new_bot = Bot(token=command.args, session=bot.session) + try: + bot_user = await new_bot.get_me() + except TelegramUnauthorizedError: + return message.answer("Invalid token") + # await new_bot.delete_webhook(drop_pending_updates=True) + # await new_bot.set_webhook(OTHER_BOTS_URL.format(bot_token=command.args)) + return await message.answer( + f"Your Bot is @{bot_user.username} but, it should be registered in Intent Kit first!" + ) diff --git a/tg/bot/kind/god/startup.py b/tg/bot/kind/god/startup.py new file mode 100644 index 00000000..6e0769dd --- /dev/null +++ b/tg/bot/kind/god/startup.py @@ -0,0 +1,11 @@ +from os import getenv + +from aiogram import Bot, Dispatcher + +BASE_URL = getenv("TG_BASE_URL") +GOD_BOT_PATH = "/webhook/god" +GOD_BOT_TOKEN = getenv("TG_TOKEN_GOD_BOT") + + +async def on_startup(dispatcher: Dispatcher, bot: Bot): + await bot.set_webhook(f"{BASE_URL}{GOD_BOT_PATH}") diff --git a/tg/bot/pool.py b/tg/bot/pool.py new file mode 100644 index 00000000..c0869ed1 --- /dev/null +++ b/tg/bot/pool.py @@ -0,0 +1,72 @@ +from aiohttp import web +from aiogram import Bot, Dispatcher +from aiogram.enums import ParseMode +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.client.bot import DefaultBotProperties +from aiogram.webhook.aiohttp_server import ( + SimpleRequestHandler, + TokenBasedRequestHandler, + setup_application, +) + +from bot.types.router_obj import RouterObj +from bot.types.kind import Kind +from bot.kind.general.router import general_router +from bot.kind.god.router import god_router +from bot.kind.god.startup import on_startup, GOD_BOT_TOKEN, GOD_BOT_PATH + +BOTS_PATH = "/webhook/bot/{kind}/{bot_token}" + + +class BotPool: + def __init__(self, base_url): + self.app = web.Application() + self.base_url = f"{base_url}{BOTS_PATH}" + self.routers = { + Kind.General: RouterObj(general_router), + } + + self.bots = {} + + def init_god_bot(self): + self.god_bot = Bot( + token=GOD_BOT_TOKEN, + default=DefaultBotProperties(parse_mode=ParseMode.HTML), + ) + storage = MemoryStorage() + # In order to use RedisStorage you need to use Key Builder with bot ID: + # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) + dp = Dispatcher(storage=storage) + dp.include_router(god_router) + dp.startup.register(on_startup) + SimpleRequestHandler(dispatcher=dp, bot=self.god_bot).register( + self.app, path=GOD_BOT_PATH + ) + setup_application(self.app, dp, bot=self.god_bot) + + def init_all_dispatchers(self): + for kind, b in self.routers.items(): + storage = MemoryStorage() + # In order to use RedisStorage you need to use Key Builder with bot ID: + # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) + b.set_dispatcher(Dispatcher(storage=storage)) + b.get_dispatcher().include_router(b.get_router()) + TokenBasedRequestHandler( + dispatcher=b.get_dispatcher(), + default=DefaultBotProperties(parse_mode=ParseMode.HTML), + ).register( + self.app, path=BOTS_PATH.format(kind=kind, bot_token="{bot_token}") + ) + setup_application(self.app, b.get_dispatcher()) + + async def init_new_bot(self, kind, token): + bot = Bot( + token=token, + default=DefaultBotProperties(parse_mode=ParseMode.HTML), + ) + await bot.delete_webhook(drop_pending_updates=True) + await bot.set_webhook(self.base_url.format(kind=kind, bot_token=token)) + self.bots[token] = bot + + def start(self, asyncio_loop, host, port): + web.run_app(self.app, loop=asyncio_loop, host=host, port=port) diff --git a/tg/bot/types/kind.py b/tg/bot/types/kind.py new file mode 100644 index 00000000..b751d371 --- /dev/null +++ b/tg/bot/types/kind.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class Kind(Enum): + General = 1 diff --git a/tg/bot/types/router_obj.py b/tg/bot/types/router_obj.py new file mode 100644 index 00000000..fc46dd33 --- /dev/null +++ b/tg/bot/types/router_obj.py @@ -0,0 +1,12 @@ +class RouterObj: + def __init__(self, router): + self.router = router + + def get_router(self): + return self.router + + def set_dispatcher(self, dp): + self.dispatcher = dp + + def get_dispatcher(self): + return self.dispatcher diff --git a/tg/schedule/__init__.py b/tg/schedule/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tg/schedule/agent.py b/tg/schedule/agent.py new file mode 100644 index 00000000..dddda601 --- /dev/null +++ b/tg/schedule/agent.py @@ -0,0 +1,30 @@ +import asyncio +from sqlmodel import select, Session + +from app.db import get_db, Agent + + +class AgentScheduler: + def __init__(self, bot_pool): + self.bot_pool = bot_pool + + def check_new_bots(self): + db: Session = next(get_db()) + # Get all telegram agents + agents = db.exec( + select(Agent).where( + Agent.telegram_enabled == True, + ) + ).all() + + new_bots = [] + for agent in agents: + if agent.telegram_config["token"] not in self.bot_pool.bots: + new_bots.append(agent.telegram_config) + + async def start(self, interval): + while True: + print("check for new bots...") + await asyncio.sleep(interval) + for new_bot in self.check_new_bots(): + await self.bot_pool.init_new_bot(new_bot["kind"], new_bot["token"]) From f85c976822da7b9f522034d2579049316151cb4c Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:25:19 +0300 Subject: [PATCH 02/17] feat: integrate new bots task --- app/tg.py | 5 +++-- example.env | 4 ++++ tg/bot/pool.py | 43 +++++++++++++++++++++++-------------------- tg/schedule/agent.py | 6 ++++-- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/app/tg.py b/app/tg.py index 1c01c60b..5898deb6 100644 --- a/app/tg.py +++ b/app/tg.py @@ -2,7 +2,7 @@ import logging from os import getenv import sys -from app.db import get_db, Agent, AgentQuota, init_db +from app.db import init_db from app.config import config import signal from tg.bot.pool import BotPool @@ -11,6 +11,7 @@ BASE_URL = getenv("TG_BASE_URL") WEB_SERVER_HOST = getenv("TG_SERVER_HOST", "127.0.0.1") WEB_SERVER_PORT = getenv("TG_SERVER_PORT", "8081") +TG_NEW_AGENT_POLL_INTERVAL = getenv("TG_NEW_AGENT_POLL_INTERVAL", "60") logger = logging.getLogger(__name__) @@ -37,7 +38,7 @@ def signal_handler(signum, frame): scheduler = AgentScheduler(bot_pool) loop = asyncio.new_event_loop() - loop.create_task(scheduler.start()) + loop.create_task(scheduler.start(int(TG_NEW_AGENT_POLL_INTERVAL))) bot_pool.start(loop, WEB_SERVER_HOST, int(WEB_SERVER_PORT)) diff --git a/example.env b/example.env index 24976701..6d659a70 100644 --- a/example.env +++ b/example.env @@ -9,5 +9,9 @@ DB_USERNAME= DB_PASSWORD= DB_NAME= +TG_TOKEN_GOD_BOT= +TG_BASE_URL= +TG_NEW_AGENT_POLL_INTERVAL= + CDP_API_KEY_NAME= CDP_API_KEY_PRIVATE_KEY= diff --git a/tg/bot/pool.py b/tg/bot/pool.py index c0869ed1..b96a77a8 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -9,11 +9,11 @@ setup_application, ) -from bot.types.router_obj import RouterObj -from bot.types.kind import Kind -from bot.kind.general.router import general_router -from bot.kind.god.router import god_router -from bot.kind.god.startup import on_startup, GOD_BOT_TOKEN, GOD_BOT_PATH +from tg.bot.types.router_obj import RouterObj +from tg.bot.types.kind import Kind +from tg.bot.kind.general.router import general_router +from tg.bot.kind.god.router import god_router +from tg.bot.kind.god.startup import on_startup, GOD_BOT_TOKEN, GOD_BOT_PATH BOTS_PATH = "/webhook/bot/{kind}/{bot_token}" @@ -29,20 +29,21 @@ def __init__(self, base_url): self.bots = {} def init_god_bot(self): - self.god_bot = Bot( - token=GOD_BOT_TOKEN, - default=DefaultBotProperties(parse_mode=ParseMode.HTML), - ) - storage = MemoryStorage() - # In order to use RedisStorage you need to use Key Builder with bot ID: - # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) - dp = Dispatcher(storage=storage) - dp.include_router(god_router) - dp.startup.register(on_startup) - SimpleRequestHandler(dispatcher=dp, bot=self.god_bot).register( - self.app, path=GOD_BOT_PATH - ) - setup_application(self.app, dp, bot=self.god_bot) + if GOD_BOT_TOKEN is not None: + self.god_bot = Bot( + token=GOD_BOT_TOKEN, + default=DefaultBotProperties(parse_mode=ParseMode.HTML), + ) + storage = MemoryStorage() + # In order to use RedisStorage you need to use Key Builder with bot ID: + # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) + dp = Dispatcher(storage=storage) + dp.include_router(god_router) + dp.startup.register(on_startup) + SimpleRequestHandler(dispatcher=dp, bot=self.god_bot).register( + self.app, path=GOD_BOT_PATH + ) + setup_application(self.app, dp, bot=self.god_bot) def init_all_dispatchers(self): for kind, b in self.routers.items(): @@ -51,11 +52,13 @@ def init_all_dispatchers(self): # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) b.set_dispatcher(Dispatcher(storage=storage)) b.get_dispatcher().include_router(b.get_router()) + print(BOTS_PATH.format(kind=kind.value, bot_token="{bot_token}")) TokenBasedRequestHandler( dispatcher=b.get_dispatcher(), default=DefaultBotProperties(parse_mode=ParseMode.HTML), ).register( - self.app, path=BOTS_PATH.format(kind=kind, bot_token="{bot_token}") + self.app, + path=BOTS_PATH.format(kind=kind.value, bot_token="{bot_token}"), ) setup_application(self.app, b.get_dispatcher()) diff --git a/tg/schedule/agent.py b/tg/schedule/agent.py index dddda601..92be5a53 100644 --- a/tg/schedule/agent.py +++ b/tg/schedule/agent.py @@ -21,10 +21,12 @@ def check_new_bots(self): for agent in agents: if agent.telegram_config["token"] not in self.bot_pool.bots: new_bots.append(agent.telegram_config) + return new_bots async def start(self, interval): while True: print("check for new bots...") await asyncio.sleep(interval) - for new_bot in self.check_new_bots(): - await self.bot_pool.init_new_bot(new_bot["kind"], new_bot["token"]) + if self.check_new_bots() != None: + for new_bot in self.check_new_bots(): + await self.bot_pool.init_new_bot(new_bot["kind"], new_bot["token"]) From 9644b82d9a83500d659332d85955ef598ea2b654 Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:56:21 +0300 Subject: [PATCH 03/17] lint: sort tg imports --- app/tg.py | 7 ++++--- tg/bot/pool.py | 10 +++++----- tg/schedule/agent.py | 5 +++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/tg.py b/app/tg.py index 5898deb6..6b1b8352 100644 --- a/app/tg.py +++ b/app/tg.py @@ -1,10 +1,11 @@ import asyncio import logging -from os import getenv +import signal import sys -from app.db import init_db +from os import getenv + from app.config import config -import signal +from app.db import init_db from tg.bot.pool import BotPool from tg.schedule.agent import AgentScheduler diff --git a/tg/bot/pool.py b/tg/bot/pool.py index b96a77a8..dc430e14 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -1,19 +1,19 @@ -from aiohttp import web from aiogram import Bot, Dispatcher +from aiogram.client.bot import DefaultBotProperties from aiogram.enums import ParseMode from aiogram.fsm.storage.memory import MemoryStorage -from aiogram.client.bot import DefaultBotProperties from aiogram.webhook.aiohttp_server import ( SimpleRequestHandler, TokenBasedRequestHandler, setup_application, ) +from aiohttp import web -from tg.bot.types.router_obj import RouterObj -from tg.bot.types.kind import Kind from tg.bot.kind.general.router import general_router from tg.bot.kind.god.router import god_router -from tg.bot.kind.god.startup import on_startup, GOD_BOT_TOKEN, GOD_BOT_PATH +from tg.bot.kind.god.startup import GOD_BOT_PATH, GOD_BOT_TOKEN, on_startup +from tg.bot.types.kind import Kind +from tg.bot.types.router_obj import RouterObj BOTS_PATH = "/webhook/bot/{kind}/{bot_token}" diff --git a/tg/schedule/agent.py b/tg/schedule/agent.py index 92be5a53..3b5fd10d 100644 --- a/tg/schedule/agent.py +++ b/tg/schedule/agent.py @@ -1,7 +1,8 @@ import asyncio -from sqlmodel import select, Session -from app.db import get_db, Agent +from sqlmodel import Session, select + +from app.db import Agent, get_db class AgentScheduler: From b89a456f378e0089a49b244e022eb0cd17ee7122 Mon Sep 17 00:00:00 2001 From: Muninn Date: Wed, 25 Dec 2024 00:51:53 +0800 Subject: [PATCH 04/17] fix: move tg config to config --- app/config.py | 9 +++++++-- app/tg.py | 10 +++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/config.py b/app/config.py index c734ae2b..853f2583 100644 --- a/app/config.py +++ b/app/config.py @@ -64,13 +64,18 @@ def __init__(self): self.openai_api_key = self.load("OPENAI_API_KEY") self.slack_token = self.load("SLACK_TOKEN") self.slack_channel = self.load("SLACK_CHANNEL") + self.tg_base_url = self.load("TG_BASE_URL") + self.tg_server_host = self.load("TG_SERVER_HOST", "127.0.0.1") + self.tg_server_port = self.load("TG_SERVER_PORT", "8081") + self.tg_new_agent_poll_interval = self.load("TG_NEW_AGENT_POLL_INTERVAL", "60") + # Now we know the env, set up logging setup_logging(self.env, self.debug) logger.info("config loaded") - def load(self, key): + def load(self, key, default=None): """Load a secret from the secrets map or env""" - return self.secrets.get(key, os.getenv(key)) + return self.secrets.get(key, os.getenv(key, default)) config = Config() diff --git a/app/tg.py b/app/tg.py index 6b1b8352..2848981e 100644 --- a/app/tg.py +++ b/app/tg.py @@ -9,10 +9,6 @@ from tg.bot.pool import BotPool from tg.schedule.agent import AgentScheduler -BASE_URL = getenv("TG_BASE_URL") -WEB_SERVER_HOST = getenv("TG_SERVER_HOST", "127.0.0.1") -WEB_SERVER_PORT = getenv("TG_SERVER_PORT", "8081") -TG_NEW_AGENT_POLL_INTERVAL = getenv("TG_NEW_AGENT_POLL_INTERVAL", "60") logger = logging.getLogger(__name__) @@ -31,7 +27,7 @@ def signal_handler(signum, frame): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - bot_pool = BotPool(BASE_URL) + bot_pool = BotPool(config.tg_base_url) bot_pool.init_god_bot() bot_pool.init_all_dispatchers() @@ -39,9 +35,9 @@ def signal_handler(signum, frame): scheduler = AgentScheduler(bot_pool) loop = asyncio.new_event_loop() - loop.create_task(scheduler.start(int(TG_NEW_AGENT_POLL_INTERVAL))) + loop.create_task(scheduler.start(int(config.tg_new_agent_poll_interval))) - bot_pool.start(loop, WEB_SERVER_HOST, int(WEB_SERVER_PORT)) + bot_pool.start(loop, config.tg_server_host, int(config.tg_server_port)) if __name__ == "__main__": From 372b1e9b2fc5d89cf7316e0a6397c60b39b1ac42 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 11:42:57 +0800 Subject: [PATCH 05/17] refactor: tg import rel --- app/entrypoints/tg.py | 37 ++++++++++++++++++++++++---- poetry.lock | 57 ++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index 2848981e..b52f49b3 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -2,17 +2,44 @@ import logging import signal import sys -from os import getenv -from app.config import config -from app.db import init_db -from tg.bot.pool import BotPool -from tg.schedule.agent import AgentScheduler +from sqlmodel import Session, select +from app.config.config import config +from app.models.db import init_db, get_engine, Agent +from tg.bot.pool import BotPool logger = logging.getLogger(__name__) +class AgentScheduler: + def __init__(self, bot_pool): + self.bot_pool = bot_pool + + def check_new_bots(self): + with Session(get_engine()) as db: + # Get all telegram agents + agents = db.exec( + select(Agent).where( + Agent.telegram_enabled == True, + ) + ).all() + + new_bots = [] + for agent in agents: + if agent.telegram_config["token"] not in self.bot_pool.bots: + new_bots.append(agent.telegram_config) + return new_bots + + async def start(self, interval): + while True: + print("check for new bots...") + await asyncio.sleep(interval) + if self.check_new_bots() != None: + for new_bot in self.check_new_bots(): + await self.bot_pool.init_new_bot(new_bot["kind"], new_bot["token"]) + + def run_telegram_server() -> None: # Initialize database connection init_db(**config.db) diff --git a/poetry.lock b/poetry.lock index 0dcc7037..55b5696f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,46 @@ # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + +[[package]] +name = "aiogram" +version = "3.16.0" +description = "Modern and fully asynchronous framework for Telegram Bot API" +optional = false +python-versions = ">=3.9" +files = [ + {file = "aiogram-3.16.0-py3-none-any.whl", hash = "sha256:c92a52aff032217bce5df4089a8ff8f0b86ce0533126c22f3fe55e6b1b230a66"}, + {file = "aiogram-3.16.0.tar.gz", hash = "sha256:b7fd7a6c6434d831472c1d6d971e23348966a9aeb71a1c0e575a01990390e0f1"}, +] + +[package.dependencies] +aiofiles = ">=23.2.1,<24.2" +aiohttp = ">=3.9.0,<3.12" +certifi = ">=2023.7.22" +magic-filter = ">=1.0.12,<1.1" +pydantic = ">=2.4.1,<2.11" +typing-extensions = ">=4.7.0,<=5.0" + +[package.extras] +cli = ["aiogram-cli (>=1.1.0,<2.0.0)"] +dev = ["black (>=24.4.2,<24.5.0)", "isort (>=5.13.2,<5.14.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.10.0,<1.11.0)", "packaging (>=24.1,<25.0)", "pre-commit (>=3.5,<4.0)", "ruff (>=0.5.1,<0.6.0)", "toml (>=0.10.2,<0.11.0)"] +docs = ["furo (>=2024.8.6,<2024.9.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.18.0,<2.19.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=8.0.2,<8.1.0)", "sphinx-autobuild (>=2024.9.3,<2024.10.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.2.0,<2.3.0)", "sphinx-substitution-extensions (>=2024.8.6,<2024.9.0)", "sphinxcontrib-towncrier (>=0.4.0a0,<0.5.0)", "towncrier (>=24.8.0,<24.9.0)"] +fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)", "uvloop (>=0.21.0)"] +i18n = ["babel (>=2.13.0,<2.14.0)"] +mongo = ["motor (>=3.3.2,<3.7.0)"] +proxy = ["aiohttp-socks (>=0.8.3,<0.9.0)"] +redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"] +test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"] + [[package]] name = "aiohappyeyeballs" version = "2.4.4" @@ -2071,6 +2112,20 @@ requests-toolbelt = ">=1.0.0,<2.0.0" compression = ["zstandard (>=0.23.0,<0.24.0)"] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] +[[package]] +name = "magic-filter" +version = "1.0.12" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6"}, + {file = "magic_filter-1.0.12.tar.gz", hash = "sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9"}, +] + +[package.extras] +dev = ["black (>=22.8.0,<22.9.0)", "flake8 (>=5.0.4,<5.1.0)", "isort (>=5.11.5,<5.12.0)", "mypy (>=1.4.1,<1.5.0)", "pre-commit (>=2.20.0,<2.21.0)", "pytest (>=7.1.3,<7.2.0)", "pytest-cov (>=3.0.0,<3.1.0)", "pytest-html (>=3.1.1,<3.2.0)", "types-setuptools (>=65.3.0,<65.4.0)"] + [[package]] name = "mako" version = "1.3.8" @@ -4141,4 +4196,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "82cec61b6456b86e296d09732cb52af419a5956773a894038366835ef67779be" +content-hash = "676ae75d4bc1907ec54d04c762b46a7079916a8f660a1d167fe66bd5a96f501f" diff --git a/pyproject.toml b/pyproject.toml index a6d80ef9..2c4fdb2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ slack-sdk = "^3.34.0" requests = "^2.32.3" aws-secretsmanager-caching = "^1.1.3" botocore = "^1.35.81" +aiogram = "^3.16.0" [tool.poetry.group.dev] optional = true From 27e247cf7aaa1cab4e4f91158f322e58a2a42c70 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 16:38:30 +0800 Subject: [PATCH 06/17] fix: change tg webhook url --- tg/bot/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tg/bot/pool.py b/tg/bot/pool.py index dc430e14..1a131ed7 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -15,7 +15,7 @@ from tg.bot.types.kind import Kind from tg.bot.types.router_obj import RouterObj -BOTS_PATH = "/webhook/bot/{kind}/{bot_token}" +BOTS_PATH = "/webhook/tgbot/{kind}/{bot_token}" class BotPool: From 8d5ec71933635c5473d3098f4f901070e19adb13 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 16:42:31 +0800 Subject: [PATCH 07/17] fix: change default tg listen host --- app/config/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/config.py b/app/config/config.py index 0f0b5b96..a2709263 100644 --- a/app/config/config.py +++ b/app/config/config.py @@ -66,7 +66,7 @@ def __init__(self): self.slack_token = self.load("SLACK_TOKEN") self.slack_channel = self.load("SLACK_CHANNEL") self.tg_base_url = self.load("TG_BASE_URL") - self.tg_server_host = self.load("TG_SERVER_HOST", "127.0.0.1") + self.tg_server_host = self.load("TG_SERVER_HOST", "0.0.0.0") self.tg_server_port = self.load("TG_SERVER_PORT", "8081") self.tg_new_agent_poll_interval = self.load("TG_NEW_AGENT_POLL_INTERVAL", "60") From 2af49612fb13363a9cd0dec2491479db89ec9b11 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 17:33:43 +0800 Subject: [PATCH 08/17] chore: change docker image tag rule --- .github/workflows/build.yml | 21 +++++++++++++++++++-- .github/workflows/testnet-dev.yml | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7512f7c4..4a788e13 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,10 +3,13 @@ name: Build Docker Image on: push: branches: [ main ] + tags: + - "v*.*.*" paths-ignore: - '.github/**' - 'docs/**' - '*.md' + pull_request: jobs: @@ -23,6 +26,19 @@ jobs: uses: sergeysova/jq-action@v2 with: cmd: echo -n "${{github.event.head_commit.message}}" | jq -Rsa . | sed -e 's/^"//' -e 's/"$//' + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + # list of Docker images to use as base name for tags + images: | + crestal/intentkit + # generate Docker tags based on the following events/attributes + tags: | + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -37,9 +53,10 @@ jobs: uses: docker/build-push-action@v5 with: build-args: | - RELEASE=${{ github.run_number }} + RELEASE=${{ github.sha }} push: true - tags: crestal/intentkit:latest,crestal/intentkit:${{ github.sha }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - diff --git a/.github/workflows/testnet-dev.yml b/.github/workflows/testnet-dev.yml index f2dc21f8..73bee653 100644 --- a/.github/workflows/testnet-dev.yml +++ b/.github/workflows/testnet-dev.yml @@ -40,7 +40,7 @@ jobs: uses: docker/build-push-action@v5 with: build-args: | - RELEASE=${{ github.run_number }} + RELEASE=${{ github.sha }} push: true tags: ${{ secrets.AWS_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/intentkit:${{ github.sha }},${{ secrets.AWS_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/intentkit:testnet-dev cache-from: type=gha From 618ef580135781ab7646d0eadf78b74759de4790 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 17:37:29 +0800 Subject: [PATCH 09/17] fix: change tg server default host --- app/config/config.py | 2 +- app/entrypoints/tg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/config.py b/app/config/config.py index a2709263..0f0b5b96 100644 --- a/app/config/config.py +++ b/app/config/config.py @@ -66,7 +66,7 @@ def __init__(self): self.slack_token = self.load("SLACK_TOKEN") self.slack_channel = self.load("SLACK_CHANNEL") self.tg_base_url = self.load("TG_BASE_URL") - self.tg_server_host = self.load("TG_SERVER_HOST", "0.0.0.0") + self.tg_server_host = self.load("TG_SERVER_HOST", "127.0.0.1") self.tg_server_port = self.load("TG_SERVER_PORT", "8081") self.tg_new_agent_poll_interval = self.load("TG_NEW_AGENT_POLL_INTERVAL", "60") diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index b52f49b3..2e59fa2f 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -6,7 +6,7 @@ from sqlmodel import Session, select from app.config.config import config -from app.models.db import init_db, get_engine, Agent +from app.models.db import Agent, get_engine, init_db from tg.bot.pool import BotPool logger = logging.getLogger(__name__) From 5b7c284d7229f504a7879017e7d70f04f844e692 Mon Sep 17 00:00:00 2001 From: Muninn Date: Thu, 26 Dec 2024 18:48:29 +0800 Subject: [PATCH 10/17] feat: add health check endpoint to tg server --- tg/bot/pool.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tg/bot/pool.py b/tg/bot/pool.py index 1a131ed7..2580fc18 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -18,9 +18,15 @@ BOTS_PATH = "/webhook/tgbot/{kind}/{bot_token}" +async def health_handler(request): + """Health check endpoint handler.""" + return web.json_response({"status": "healthy"}) + + class BotPool: def __init__(self, base_url): self.app = web.Application() + self.app.router.add_get("/health", health_handler) self.base_url = f"{base_url}{BOTS_PATH}" self.routers = { Kind.General: RouterObj(general_router), From c1b540d2628ed4ef0678bcdb51024bdea4a2df6c Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:06:26 +0300 Subject: [PATCH 11/17] feat: add logs to tg --- app/entrypoints/tg.py | 6 +++++- tg/bot/pool.py | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index 2e59fa2f..a40d602f 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -29,11 +29,14 @@ def check_new_bots(self): for agent in agents: if agent.telegram_config["token"] not in self.bot_pool.bots: new_bots.append(agent.telegram_config) + logger.info("New agent with id {id} found...".format(id=agent.id)) + return new_bots async def start(self, interval): + logger.info("New agent addition tracking started...") while True: - print("check for new bots...") + logger.info("check for new bots...") await asyncio.sleep(interval) if self.check_new_bots() != None: for new_bot in self.check_new_bots(): @@ -54,6 +57,7 @@ def signal_handler(signum, frame): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) + logger.info("Initialize bot pool...") bot_pool = BotPool(config.tg_base_url) bot_pool.init_god_bot() diff --git a/tg/bot/pool.py b/tg/bot/pool.py index 2580fc18..fe09cd3f 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -1,3 +1,5 @@ +import logging + from aiogram import Bot, Dispatcher from aiogram.client.bot import DefaultBotProperties from aiogram.enums import ParseMode @@ -15,6 +17,8 @@ from tg.bot.types.kind import Kind from tg.bot.types.router_obj import RouterObj +logger = logging.getLogger(__name__) + BOTS_PATH = "/webhook/tgbot/{kind}/{bot_token}" @@ -36,6 +40,7 @@ def __init__(self, base_url): def init_god_bot(self): if GOD_BOT_TOKEN is not None: + logger.info("Initialize god bot...") self.god_bot = Bot( token=GOD_BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML), @@ -52,13 +57,13 @@ def init_god_bot(self): setup_application(self.app, dp, bot=self.god_bot) def init_all_dispatchers(self): + logger.info("Initialize all dispatchers...") for kind, b in self.routers.items(): storage = MemoryStorage() # In order to use RedisStorage you need to use Key Builder with bot ID: # storage = RedisStorage.from_url(TG_REDIS_DSN, key_builder=DefaultKeyBuilder(with_bot_id=True)) b.set_dispatcher(Dispatcher(storage=storage)) b.get_dispatcher().include_router(b.get_router()) - print(BOTS_PATH.format(kind=kind.value, bot_token="{bot_token}")) TokenBasedRequestHandler( dispatcher=b.get_dispatcher(), default=DefaultBotProperties(parse_mode=ParseMode.HTML), @@ -67,6 +72,7 @@ def init_all_dispatchers(self): path=BOTS_PATH.format(kind=kind.value, bot_token="{bot_token}"), ) setup_application(self.app, b.get_dispatcher()) + logger.info("{kind} router initialized...".format(kind=kind)) async def init_new_bot(self, kind, token): bot = Bot( @@ -75,7 +81,9 @@ async def init_new_bot(self, kind, token): ) await bot.delete_webhook(drop_pending_updates=True) await bot.set_webhook(self.base_url.format(kind=kind, bot_token=token)) + self.bots[token] = bot + logger.info("Bot with token {token} initialized...".format(token=token)) def start(self, asyncio_loop, host, port): web.run_app(self.app, loop=asyncio_loop, host=host, port=port) From 3fafe0dfddf0e359dbab3b6b3aaa7f093788e858 Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Fri, 27 Dec 2024 18:23:36 +0300 Subject: [PATCH 12/17] feat: tg chat type filter --- tg/bot/filter/chat_type.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tg/bot/filter/chat_type.py diff --git a/tg/bot/filter/chat_type.py b/tg/bot/filter/chat_type.py new file mode 100644 index 00000000..225b8270 --- /dev/null +++ b/tg/bot/filter/chat_type.py @@ -0,0 +1,18 @@ +from aiogram.filters import BaseFilter +from aiogram.types import Message + + +class ChatTypeFilter(BaseFilter): + def __init__(self, chat_type: str | list): + self.chat_type = chat_type + + async def __call__(self, message: Message) -> bool: + if isinstance(self.chat_type, str): + return message.chat.type == self.chat_type + else: + return message.chat.type in self.chat_type + + +class GroupOnlyFilter(ChatTypeFilter): + def __init__(self): + super().__init__(["group", "supergroup"]) From dc958582585be135cec5cdf90f58f71691bdfbc1 Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:29:10 +0300 Subject: [PATCH 13/17] feat: tg ai relayer bot logic --- app/entrypoints/tg.py | 8 +- .../kind/{general => ai_relayer}/__init__.py | 0 tg/bot/kind/ai_relayer/router.py | 170 ++++++++++++++++++ tg/bot/kind/general/router.py | 119 ------------ tg/bot/pool.py | 26 ++- tg/bot/types/kind.py | 2 +- 6 files changed, 197 insertions(+), 128 deletions(-) rename tg/bot/kind/{general => ai_relayer}/__init__.py (100%) create mode 100644 tg/bot/kind/ai_relayer/router.py delete mode 100644 tg/bot/kind/general/router.py diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index a40d602f..31f981f6 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -7,6 +7,7 @@ from app.config.config import config from app.models.db import Agent, get_engine, init_db +from tg.bot import pool from tg.bot.pool import BotPool logger = logging.getLogger(__name__) @@ -27,7 +28,8 @@ def check_new_bots(self): new_bots = [] for agent in agents: - if agent.telegram_config["token"] not in self.bot_pool.bots: + if agent.telegram_config["token"] not in pool._bots: + agent.telegram_config["agent_id"] = agent.id new_bots.append(agent.telegram_config) logger.info("New agent with id {id} found...".format(id=agent.id)) @@ -40,7 +42,9 @@ async def start(self, interval): await asyncio.sleep(interval) if self.check_new_bots() != None: for new_bot in self.check_new_bots(): - await self.bot_pool.init_new_bot(new_bot["kind"], new_bot["token"]) + await self.bot_pool.init_new_bot( + new_bot["agent_id"], new_bot["kind"], new_bot["token"] + ) def run_telegram_server() -> None: diff --git a/tg/bot/kind/general/__init__.py b/tg/bot/kind/ai_relayer/__init__.py similarity index 100% rename from tg/bot/kind/general/__init__.py rename to tg/bot/kind/ai_relayer/__init__.py diff --git a/tg/bot/kind/ai_relayer/router.py b/tg/bot/kind/ai_relayer/router.py new file mode 100644 index 00000000..ef6fc13e --- /dev/null +++ b/tg/bot/kind/ai_relayer/router.py @@ -0,0 +1,170 @@ +from aiogram import Router +from aiogram.filters import CommandStart +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from aiogram.types import Message + +from app.core.ai import execute_agent +from tg.bot import pool +from tg.bot.filter.chat_type import GroupOnlyFilter + +general_router = Router() + + +class GeneralForm(StatesGroup): + name = State() + like_bots = State() + language = State() + + +## group commands and messages + + +@general_router.message(GroupOnlyFilter(), CommandStart()) +async def gp_command_start(message: Message): + group_title = message.from_user.first_name + await message.answer( + text=f"🤖 Hi Everybody, {group_title}! 🎉\nGreetings, traveler of the digital realm! You've just awakened the mighty powers of this chat bot. Brace yourself for an adventure filled with wit, wisdom, and possibly a few jokes.", + ) + + +@general_router.message(GroupOnlyFilter()) +async def gp_process_message(message: Message) -> None: + bot = await message.bot.get_me() + if ( + message.reply_to_message + and message.reply_to_message.from_user.id == message.bot.id + ) or bot.username in message.text: + agent_id = pool.bot_by_token(message.bot.token)["agent_id"] + thread_id = pool.agent_thread_id(agent_id, message.chat.id) + response = execute_agent(agent_id, message.text, thread_id) + await message.answer( + text="\n".join(response), + reply_to_message_id=message.message_id, + ) + + +@general_router.message(CommandStart()) +async def command_start(message: Message, state: FSMContext) -> None: + first_name = message.from_user.first_name + await message.answer( + text=f"🤖 Hi, {first_name}! 🎉\nGreetings, traveler of the digital realm! You've just awakened the mighty powers of this chat bot. Brace yourself for an adventure filled with wit, wisdom, and possibly a few jokes.", + ) + + +@general_router.message() +async def process_message(message: Message, state: FSMContext) -> None: + agent_id = pool.bot_by_token(message.bot.token)["agent_id"] + thread_id = pool.agent_thread_id(agent_id, message.chat.id) + response = execute_agent(agent_id, message.text, thread_id) + await message.answer( + text="\n".join(response), + reply_to_message_id=message.message_id, + ) + + +# @general_router.message() +# async def process_name_group(message: Message, state: FSMContext) -> None: +# await state.update_data(name=message.text) +# await state.set_state(GeneralForm.like_bots) +# await message.answer( +# f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", +# reply_markup=ReplyKeyboardMarkup( +# keyboard=[ +# [ +# KeyboardButton(text="Yes"), +# KeyboardButton(text="No"), +# ] +# ], +# resize_keyboard=True, +# ), +# ) + + +# @general_router.message(Command("cancel")) +# @general_router.message(F.text.casefold() == "cancel") +# async def cancel_handler(message: Message, state: FSMContext) -> None: +# """ +# Allow user to cancel any action +# """ +# current_state = await state.get_state() +# if current_state is None: +# return + +# logging.info("Cancelling state %r", current_state) +# await state.clear() +# await message.answer( +# "Cancelled.", +# reply_markup=ReplyKeyboardRemove(), +# ) + + +# @general_router.message(GeneralForm.name) +# async def process_name(message: Message, state: FSMContext) -> None: +# await state.update_data(name=message.text) +# await state.set_state(GeneralForm.like_bots) +# await message.answer( +# f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", +# reply_markup=ReplyKeyboardMarkup( +# keyboard=[ +# [ +# KeyboardButton(text="Yes"), +# KeyboardButton(text="No"), +# ] +# ], +# resize_keyboard=True, +# ), +# ) + + +# @general_router.message(GeneralForm.like_bots, F.text.casefold() == "no") +# async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None: +# data = await state.get_data() +# await state.clear() +# await message.answer( +# "Not bad not terrible.\nSee you soon.", +# reply_markup=ReplyKeyboardRemove(), +# ) +# await show_summary(message=message, data=data, positive=False) + + +# @general_router.message(GeneralForm.like_bots, F.text.casefold() == "yes") +# async def process_like_write_bots(message: Message, state: FSMContext) -> None: +# await state.set_state(GeneralForm.language) + +# await message.reply( +# "Cool! I'm too!\nWhat programming language did you use for it?", +# reply_markup=ReplyKeyboardRemove(), +# ) + + +# @general_router.message(GeneralForm.like_bots) +# async def process_unknown_write_bots(message: Message) -> None: +# await message.reply("I don't understand you :(") + + +# @general_router.message(GeneralForm.language) +# async def process_language(message: Message, state: FSMContext) -> None: +# data = await state.update_data(language=message.text) +# await state.clear() + +# if message.text.casefold() == "python": +# await message.reply( +# "Python, you say? That's the language that makes my circuits light up! 😉" +# ) + +# await show_summary(message=message, data=data) + + +# async def show_summary( +# message: Message, data: Dict[str, Any], positive: bool = True +# ) -> None: +# name = data["name"] +# language = data.get("language", "") +# text = f"I'll keep in mind that, {html.quote(name)}, " +# text += ( +# f"you like to write bots with {html.quote(language)}." +# if positive +# else "you don't like to write bots, so sad..." +# ) +# await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) diff --git a/tg/bot/kind/general/router.py b/tg/bot/kind/general/router.py deleted file mode 100644 index 11e12a23..00000000 --- a/tg/bot/kind/general/router.py +++ /dev/null @@ -1,119 +0,0 @@ -import logging -from typing import Any, Dict - -from aiogram import F, Router, html -from aiogram.filters import Command, CommandStart -from aiogram.fsm.context import FSMContext -from aiogram.fsm.state import State, StatesGroup -from aiogram.types import ( - KeyboardButton, - Message, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, -) - -general_router = Router() - - -class GeneralForm(StatesGroup): - name = State() - like_bots = State() - language = State() - - -@general_router.message(CommandStart()) -async def command_start(message: Message, state: FSMContext) -> None: - await state.set_state(GeneralForm.name) - await message.answer( - "Hi there! What's your name?", - reply_markup=ReplyKeyboardRemove(), - ) - - -@general_router.message(Command("cancel")) -@general_router.message(F.text.casefold() == "cancel") -async def cancel_handler(message: Message, state: FSMContext) -> None: - """ - Allow user to cancel any action - """ - current_state = await state.get_state() - if current_state is None: - return - - logging.info("Cancelling state %r", current_state) - await state.clear() - await message.answer( - "Cancelled.", - reply_markup=ReplyKeyboardRemove(), - ) - - -@general_router.message(GeneralForm.name) -async def process_name(message: Message, state: FSMContext) -> None: - await state.update_data(name=message.text) - await state.set_state(GeneralForm.like_bots) - await message.answer( - f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", - reply_markup=ReplyKeyboardMarkup( - keyboard=[ - [ - KeyboardButton(text="Yes"), - KeyboardButton(text="No"), - ] - ], - resize_keyboard=True, - ), - ) - - -@general_router.message(GeneralForm.like_bots, F.text.casefold() == "no") -async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None: - data = await state.get_data() - await state.clear() - await message.answer( - "Not bad not terrible.\nSee you soon.", - reply_markup=ReplyKeyboardRemove(), - ) - await show_summary(message=message, data=data, positive=False) - - -@general_router.message(GeneralForm.like_bots, F.text.casefold() == "yes") -async def process_like_write_bots(message: Message, state: FSMContext) -> None: - await state.set_state(GeneralForm.language) - - await message.reply( - "Cool! I'm too!\nWhat programming language did you use for it?", - reply_markup=ReplyKeyboardRemove(), - ) - - -@general_router.message(GeneralForm.like_bots) -async def process_unknown_write_bots(message: Message) -> None: - await message.reply("I don't understand you :(") - - -@general_router.message(GeneralForm.language) -async def process_language(message: Message, state: FSMContext) -> None: - data = await state.update_data(language=message.text) - await state.clear() - - if message.text.casefold() == "python": - await message.reply( - "Python, you say? That's the language that makes my circuits light up! 😉" - ) - - await show_summary(message=message, data=data) - - -async def show_summary( - message: Message, data: Dict[str, Any], positive: bool = True -) -> None: - name = data["name"] - language = data.get("language", "") - text = f"I'll keep in mind that, {html.quote(name)}, " - text += ( - f"you like to write bots with {html.quote(language)}." - if positive - else "you don't like to write bots, so sad..." - ) - await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) diff --git a/tg/bot/pool.py b/tg/bot/pool.py index fe09cd3f..ec7b1730 100644 --- a/tg/bot/pool.py +++ b/tg/bot/pool.py @@ -11,7 +11,7 @@ ) from aiohttp import web -from tg.bot.kind.general.router import general_router +from tg.bot.kind.ai_relayer.router import general_router from tg.bot.kind.god.router import god_router from tg.bot.kind.god.startup import GOD_BOT_PATH, GOD_BOT_TOKEN, on_startup from tg.bot.types.kind import Kind @@ -21,6 +21,21 @@ BOTS_PATH = "/webhook/tgbot/{kind}/{bot_token}" +_bots = {} +_agent_bots = {} + + +def bot_by_token(token): + return _bots[token] + + +def bot_by_agent_id(agent_id): + return _agent_bots[agent_id] + + +def agent_thread_id(agent_id, chat_id): + return f"{agent_id}-telegram-{chat_id}" + async def health_handler(request): """Health check endpoint handler.""" @@ -33,11 +48,9 @@ def __init__(self, base_url): self.app.router.add_get("/health", health_handler) self.base_url = f"{base_url}{BOTS_PATH}" self.routers = { - Kind.General: RouterObj(general_router), + Kind.AiRelayer: RouterObj(general_router), } - self.bots = {} - def init_god_bot(self): if GOD_BOT_TOKEN is not None: logger.info("Initialize god bot...") @@ -74,7 +87,7 @@ def init_all_dispatchers(self): setup_application(self.app, b.get_dispatcher()) logger.info("{kind} router initialized...".format(kind=kind)) - async def init_new_bot(self, kind, token): + async def init_new_bot(self, agent_id, kind, token): bot = Bot( token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML), @@ -82,7 +95,8 @@ async def init_new_bot(self, kind, token): await bot.delete_webhook(drop_pending_updates=True) await bot.set_webhook(self.base_url.format(kind=kind, bot_token=token)) - self.bots[token] = bot + _bots[token] = {"agent_id": agent_id, "bot": bot} + _agent_bots[agent_id] = {"token": token, "bot": bot} logger.info("Bot with token {token} initialized...".format(token=token)) def start(self, asyncio_loop, host, port): diff --git a/tg/bot/types/kind.py b/tg/bot/types/kind.py index b751d371..14caab06 100644 --- a/tg/bot/types/kind.py +++ b/tg/bot/types/kind.py @@ -2,4 +2,4 @@ class Kind(Enum): - General = 1 + AiRelayer = 1 From bf89a4970d993b6ee87a2e549a19c2111a3bc6cb Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:36:42 +0300 Subject: [PATCH 14/17] feat: ai relay do not answer bot messages --- tg/bot/kind/ai_relayer/router.py | 122 ++++--------------------------- 1 file changed, 15 insertions(+), 107 deletions(-) diff --git a/tg/bot/kind/ai_relayer/router.py b/tg/bot/kind/ai_relayer/router.py index ef6fc13e..90828e85 100644 --- a/tg/bot/kind/ai_relayer/router.py +++ b/tg/bot/kind/ai_relayer/router.py @@ -22,6 +22,9 @@ class GeneralForm(StatesGroup): @general_router.message(GroupOnlyFilter(), CommandStart()) async def gp_command_start(message: Message): + if message.from_user.is_bot: + return + group_title = message.from_user.first_name await message.answer( text=f"🤖 Hi Everybody, {group_title}! 🎉\nGreetings, traveler of the digital realm! You've just awakened the mighty powers of this chat bot. Brace yourself for an adventure filled with wit, wisdom, and possibly a few jokes.", @@ -30,6 +33,9 @@ async def gp_command_start(message: Message): @general_router.message(GroupOnlyFilter()) async def gp_process_message(message: Message) -> None: + if message.from_user.is_bot: + return + bot = await message.bot.get_me() if ( message.reply_to_message @@ -44,8 +50,14 @@ async def gp_process_message(message: Message) -> None: ) +## direct commands and messages + + @general_router.message(CommandStart()) async def command_start(message: Message, state: FSMContext) -> None: + if message.from_user.is_bot: + return + first_name = message.from_user.first_name await message.answer( text=f"🤖 Hi, {first_name}! 🎉\nGreetings, traveler of the digital realm! You've just awakened the mighty powers of this chat bot. Brace yourself for an adventure filled with wit, wisdom, and possibly a few jokes.", @@ -54,6 +66,9 @@ async def command_start(message: Message, state: FSMContext) -> None: @general_router.message() async def process_message(message: Message, state: FSMContext) -> None: + if message.from_user.is_bot: + return + agent_id = pool.bot_by_token(message.bot.token)["agent_id"] thread_id = pool.agent_thread_id(agent_id, message.chat.id) response = execute_agent(agent_id, message.text, thread_id) @@ -61,110 +76,3 @@ async def process_message(message: Message, state: FSMContext) -> None: text="\n".join(response), reply_to_message_id=message.message_id, ) - - -# @general_router.message() -# async def process_name_group(message: Message, state: FSMContext) -> None: -# await state.update_data(name=message.text) -# await state.set_state(GeneralForm.like_bots) -# await message.answer( -# f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", -# reply_markup=ReplyKeyboardMarkup( -# keyboard=[ -# [ -# KeyboardButton(text="Yes"), -# KeyboardButton(text="No"), -# ] -# ], -# resize_keyboard=True, -# ), -# ) - - -# @general_router.message(Command("cancel")) -# @general_router.message(F.text.casefold() == "cancel") -# async def cancel_handler(message: Message, state: FSMContext) -> None: -# """ -# Allow user to cancel any action -# """ -# current_state = await state.get_state() -# if current_state is None: -# return - -# logging.info("Cancelling state %r", current_state) -# await state.clear() -# await message.answer( -# "Cancelled.", -# reply_markup=ReplyKeyboardRemove(), -# ) - - -# @general_router.message(GeneralForm.name) -# async def process_name(message: Message, state: FSMContext) -> None: -# await state.update_data(name=message.text) -# await state.set_state(GeneralForm.like_bots) -# await message.answer( -# f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?", -# reply_markup=ReplyKeyboardMarkup( -# keyboard=[ -# [ -# KeyboardButton(text="Yes"), -# KeyboardButton(text="No"), -# ] -# ], -# resize_keyboard=True, -# ), -# ) - - -# @general_router.message(GeneralForm.like_bots, F.text.casefold() == "no") -# async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None: -# data = await state.get_data() -# await state.clear() -# await message.answer( -# "Not bad not terrible.\nSee you soon.", -# reply_markup=ReplyKeyboardRemove(), -# ) -# await show_summary(message=message, data=data, positive=False) - - -# @general_router.message(GeneralForm.like_bots, F.text.casefold() == "yes") -# async def process_like_write_bots(message: Message, state: FSMContext) -> None: -# await state.set_state(GeneralForm.language) - -# await message.reply( -# "Cool! I'm too!\nWhat programming language did you use for it?", -# reply_markup=ReplyKeyboardRemove(), -# ) - - -# @general_router.message(GeneralForm.like_bots) -# async def process_unknown_write_bots(message: Message) -> None: -# await message.reply("I don't understand you :(") - - -# @general_router.message(GeneralForm.language) -# async def process_language(message: Message, state: FSMContext) -> None: -# data = await state.update_data(language=message.text) -# await state.clear() - -# if message.text.casefold() == "python": -# await message.reply( -# "Python, you say? That's the language that makes my circuits light up! 😉" -# ) - -# await show_summary(message=message, data=data) - - -# async def show_summary( -# message: Message, data: Dict[str, Any], positive: bool = True -# ) -> None: -# name = data["name"] -# language = data.get("language", "") -# text = f"I'll keep in mind that, {html.quote(name)}, " -# text += ( -# f"you like to write bots with {html.quote(language)}." -# if positive -# else "you don't like to write bots, so sad..." -# ) -# await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) From f11cea61d46e9e5383db32e8b48bc540dd580aaa Mon Sep 17 00:00:00 2001 From: TxCorpi0x <6095314+TxCorpi0x@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:20:09 +0300 Subject: [PATCH 15/17] feat: filter text messages only --- app/entrypoints/tg.py | 3 ++- app/models/agent.py | 5 +++++ tg/bot/filter/content_type.py | 15 +++++++++++++++ tg/bot/kind/ai_relayer/router.py | 13 ++++++++----- 4 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 tg/bot/filter/content_type.py diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index 31f981f6..d4e9808c 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -6,7 +6,8 @@ from sqlmodel import Session, select from app.config.config import config -from app.models.db import Agent, get_engine, init_db +from app.models.db import get_engine, init_db +from app.models.agent import Agent from tg.bot import pool from tg.bot.pool import BotPool diff --git a/app/models/agent.py b/app/models/agent.py index 42039773..d4e94b3e 100644 --- a/app/models/agent.py +++ b/app/models/agent.py @@ -34,6 +34,11 @@ class Agent(SQLModel, table=True): # twitter skills require config, but not require twitter_enabled flag. # As long as twitter_skills is not empty, the corresponding skills will be loaded. twitter_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) + telegram_enabled: bool = Field(default=False) + telegram_config: Optional[dict] = Field(sa_column=Column(JSONB, nullable=True)) + # twitter skills require config, but not require twitter_enabled flag. + # As long as twitter_skills is not empty, the corresponding skills will be loaded. + telegram_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) # crestal skills crestal_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String))) # skills not require config diff --git a/tg/bot/filter/content_type.py b/tg/bot/filter/content_type.py new file mode 100644 index 00000000..a54a3185 --- /dev/null +++ b/tg/bot/filter/content_type.py @@ -0,0 +1,15 @@ +from aiogram.filters import BaseFilter +from aiogram.types import Message, ContentType + + +class ContentTypeFilter(BaseFilter): + def __init__(self, content_types: ContentType | list): + self.content_types = content_types + + async def __call__(self, message: Message) -> bool: + return message.content_type in self.content_types + + +class TextOnlyFilter(ContentTypeFilter): + def __init__(self): + super().__init__([ContentType.TEXT]) diff --git a/tg/bot/kind/ai_relayer/router.py b/tg/bot/kind/ai_relayer/router.py index 90828e85..0b54967e 100644 --- a/tg/bot/kind/ai_relayer/router.py +++ b/tg/bot/kind/ai_relayer/router.py @@ -2,11 +2,12 @@ from aiogram.filters import CommandStart from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import Message +from aiogram.types import Message, ContentType from app.core.ai import execute_agent from tg.bot import pool from tg.bot.filter.chat_type import GroupOnlyFilter +from tg.bot.filter.content_type import TextOnlyFilter general_router = Router() @@ -20,7 +21,7 @@ class GeneralForm(StatesGroup): ## group commands and messages -@general_router.message(GroupOnlyFilter(), CommandStart()) +@general_router.message(GroupOnlyFilter(), TextOnlyFilter(), CommandStart()) async def gp_command_start(message: Message): if message.from_user.is_bot: return @@ -31,7 +32,7 @@ async def gp_command_start(message: Message): ) -@general_router.message(GroupOnlyFilter()) +@general_router.message(GroupOnlyFilter(), TextOnlyFilter()) async def gp_process_message(message: Message) -> None: if message.from_user.is_bot: return @@ -53,7 +54,7 @@ async def gp_process_message(message: Message) -> None: ## direct commands and messages -@general_router.message(CommandStart()) +@general_router.message(CommandStart(), TextOnlyFilter()) async def command_start(message: Message, state: FSMContext) -> None: if message.from_user.is_bot: return @@ -64,7 +65,9 @@ async def command_start(message: Message, state: FSMContext) -> None: ) -@general_router.message() +@general_router.message( + TextOnlyFilter(), +) async def process_message(message: Message, state: FSMContext) -> None: if message.from_user.is_bot: return From 64b6b62b8ac4c1ef5353dd4017bf0f675342ac50 Mon Sep 17 00:00:00 2001 From: Muninn Date: Tue, 31 Dec 2024 19:09:22 +0800 Subject: [PATCH 16/17] chore: fix lint issue --- app/entrypoints/tg.py | 4 ++-- poetry.lock | 8 ++++---- pyproject.toml | 2 +- tg/bot/kind/ai_relayer/router.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/entrypoints/tg.py b/app/entrypoints/tg.py index d4e9808c..f18c7e51 100644 --- a/app/entrypoints/tg.py +++ b/app/entrypoints/tg.py @@ -23,7 +23,7 @@ def check_new_bots(self): # Get all telegram agents agents = db.exec( select(Agent).where( - Agent.telegram_enabled == True, + Agent.telegram_enabled, ) ).all() @@ -41,7 +41,7 @@ async def start(self, interval): while True: logger.info("check for new bots...") await asyncio.sleep(interval) - if self.check_new_bots() != None: + if self.check_new_bots() is not None: for new_bot in self.check_new_bots(): await self.bot_pool.init_new_bot( new_bot["agent_id"], new_bot["kind"], new_bot["token"] diff --git a/poetry.lock b/poetry.lock index 1e103c3f..4c8d60d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -457,13 +457,13 @@ files = [ [[package]] name = "botocore" -version = "1.35.87" +version = "1.35.90" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.87-py3-none-any.whl", hash = "sha256:81cf84f12030d9ab3829484b04765d5641697ec53c2ac2b3987a99eefe501692"}, - {file = "botocore-1.35.87.tar.gz", hash = "sha256:3062d073ce4170a994099270f469864169dc1a1b8b3d4a21c14ce0ae995e0f89"}, + {file = "botocore-1.35.90-py3-none-any.whl", hash = "sha256:51dcbe1b32e2ac43dac17091f401a00ce5939f76afe999081802009cce1e92e4"}, + {file = "botocore-1.35.90.tar.gz", hash = "sha256:f007f58e8e3c1ad0412a6ddfae40ed92a7bca571c068cb959902bcf107f2ae48"}, ] [package.dependencies] @@ -3993,4 +3993,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "99ab1654f5e31068c163f274f578e436de0f8c6c428d727a6af546ca65caefd7" +content-hash = "0a3147bee62ac900c21007e9778ead24d22460bffd8d34a725d5e2d131a4ed81" diff --git a/pyproject.toml b/pyproject.toml index 22fca7a3..e5a0745e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ anyio = "^4.7.0" slack-sdk = "^3.34.0" requests = "^2.32.3" aws-secretsmanager-caching = "^1.1.3" -botocore = "^1.35.81" +botocore = "^1.35.90" aiogram = "^3.16.0" [tool.poetry.group.dev] diff --git a/tg/bot/kind/ai_relayer/router.py b/tg/bot/kind/ai_relayer/router.py index 0b54967e..48ed0c5f 100644 --- a/tg/bot/kind/ai_relayer/router.py +++ b/tg/bot/kind/ai_relayer/router.py @@ -2,7 +2,7 @@ from aiogram.filters import CommandStart from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import Message, ContentType +from aiogram.types import Message from app.core.ai import execute_agent from tg.bot import pool From b829bfcb415c1e6d82d6d37084ec908380f57c2f Mon Sep 17 00:00:00 2001 From: Muninn Date: Tue, 31 Dec 2024 19:39:19 +0800 Subject: [PATCH 17/17] chore: remove pr auto build --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d5d6d70e..1a4e2ac1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,6 @@ on: - '.github/**' - 'docs/**' - '*.md' - pull_request: jobs: docker: