Skip to content

Commit 1a35b09

Browse files
authored
Merge pull request #1 from crestalnetwork/feat/telegram-bot-mgmg-server
feat: telegram bot manager server
2 parents 863b106 + 057c69b commit 1a35b09

File tree

20 files changed

+435
-5
lines changed

20 files changed

+435
-5
lines changed

app/config/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ def __init__(self):
6666
self.cdp_api_key_name = self.load("CDP_API_KEY_NAME")
6767
self.cdp_api_key_private_key = self.load("CDP_API_KEY_PRIVATE_KEY")
6868
self.openai_api_key = self.load("OPENAI_API_KEY")
69+
self.slack_token = self.load("SLACK_TOKEN")
70+
self.slack_channel = self.load("SLACK_CHANNEL")
71+
self.tg_base_url = self.load("TG_BASE_URL")
72+
self.tg_server_host = self.load("TG_SERVER_HOST", "127.0.0.1")
73+
self.tg_server_port = self.load("TG_SERVER_PORT", "8081")
74+
self.tg_new_agent_poll_interval = self.load("TG_NEW_AGENT_POLL_INTERVAL", "60")
6975
self.twitter_endpoint_interval = int(
7076
self.load("TWITTER_ENDPOINT_INTERVAL", "15")
7177
) # in minutes

app/entrypoints/tg.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import asyncio
2+
import logging
3+
import signal
4+
import sys
5+
6+
from sqlmodel import Session, select
7+
8+
from app.config.config import config
9+
from app.models.db import get_engine, init_db
10+
from app.models.agent import Agent
11+
from tg.bot import pool
12+
from tg.bot.pool import BotPool
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class AgentScheduler:
18+
def __init__(self, bot_pool):
19+
self.bot_pool = bot_pool
20+
21+
def check_new_bots(self):
22+
with Session(get_engine()) as db:
23+
# Get all telegram agents
24+
agents = db.exec(
25+
select(Agent).where(
26+
Agent.telegram_enabled,
27+
)
28+
).all()
29+
30+
new_bots = []
31+
for agent in agents:
32+
if agent.telegram_config["token"] not in pool._bots:
33+
agent.telegram_config["agent_id"] = agent.id
34+
new_bots.append(agent.telegram_config)
35+
logger.info("New agent with id {id} found...".format(id=agent.id))
36+
37+
return new_bots
38+
39+
async def start(self, interval):
40+
logger.info("New agent addition tracking started...")
41+
while True:
42+
logger.info("check for new bots...")
43+
await asyncio.sleep(interval)
44+
if self.check_new_bots() is not None:
45+
for new_bot in self.check_new_bots():
46+
await self.bot_pool.init_new_bot(
47+
new_bot["agent_id"], new_bot["kind"], new_bot["token"]
48+
)
49+
50+
51+
def run_telegram_server() -> None:
52+
# Initialize database connection
53+
init_db(**config.db)
54+
55+
# Signal handler for graceful shutdown
56+
def signal_handler(signum, frame):
57+
logger.info("Received termination signal. Shutting down gracefully...")
58+
scheduler.shutdown()
59+
sys.exit(0)
60+
61+
# Register signal handlers
62+
signal.signal(signal.SIGINT, signal_handler)
63+
signal.signal(signal.SIGTERM, signal_handler)
64+
65+
logger.info("Initialize bot pool...")
66+
bot_pool = BotPool(config.tg_base_url)
67+
68+
bot_pool.init_god_bot()
69+
bot_pool.init_all_dispatchers()
70+
71+
scheduler = AgentScheduler(bot_pool)
72+
73+
loop = asyncio.new_event_loop()
74+
loop.create_task(scheduler.start(int(config.tg_new_agent_poll_interval)))
75+
76+
bot_pool.start(loop, config.tg_server_host, int(config.tg_server_port))
77+
78+
79+
if __name__ == "__main__":
80+
run_telegram_server()

app/models/agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class Agent(SQLModel, table=True):
3434
# twitter skills require config, but not require twitter_enabled flag.
3535
# As long as twitter_skills is not empty, the corresponding skills will be loaded.
3636
twitter_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String)))
37+
telegram_enabled: bool = Field(default=False)
38+
telegram_config: Optional[dict] = Field(sa_column=Column(JSONB, nullable=True))
39+
# twitter skills require config, but not require twitter_enabled flag.
40+
# As long as twitter_skills is not empty, the corresponding skills will be loaded.
41+
telegram_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String)))
3742
# crestal skills
3843
crestal_skills: Optional[List[str]] = Field(sa_column=Column(ARRAY(String)))
3944
# skills not require config

debug/create_agent.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
twitter_enabled=False,
2727
twitter_config={}, # Ensure this dict structure aligns with expected config format
2828
twitter_skills=[], # Confirm if no specific Twitter skills are to be enabled
29+
telegram_enabled=False,
30+
telegram_config={}, # Ensure this dict structure aligns with expected config format
31+
telegram_skills=[], # Confirm if no specific Telegram skills are to be enabled
2932
common_skills=[], # Confirm if no common skills are to be added initially
3033
)
3134

example.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ DB_PASSWORD=
1111
DB_NAME=
1212
DB_AUTO_MIGRATE=true
1313

14+
TG_TOKEN_GOD_BOT=
15+
TG_BASE_URL=
16+
TG_NEW_AGENT_POLL_INTERVAL=
17+
1418
CDP_API_KEY_NAME=
1519
CDP_API_KEY_PRIVATE_KEY=

poetry.lock

Lines changed: 59 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ anyio = "^4.7.0"
3939
slack-sdk = "^3.34.0"
4040
requests = "^2.32.3"
4141
aws-secretsmanager-caching = "^1.1.3"
42-
botocore = "^1.35.81"
42+
botocore = "^1.35.90"
43+
aiogram = "^3.16.0"
4344

4445
[tool.poetry.group.dev]
4546
optional = true

tg/__init__.py

Whitespace-only changes.

tg/bot/__init__.py

Whitespace-only changes.

tg/bot/filter/chat_type.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from aiogram.filters import BaseFilter
2+
from aiogram.types import Message
3+
4+
5+
class ChatTypeFilter(BaseFilter):
6+
def __init__(self, chat_type: str | list):
7+
self.chat_type = chat_type
8+
9+
async def __call__(self, message: Message) -> bool:
10+
if isinstance(self.chat_type, str):
11+
return message.chat.type == self.chat_type
12+
else:
13+
return message.chat.type in self.chat_type
14+
15+
16+
class GroupOnlyFilter(ChatTypeFilter):
17+
def __init__(self):
18+
super().__init__(["group", "supergroup"])

0 commit comments

Comments
 (0)