diff --git a/.env.example b/.env.example index 24671b8..f0fb0ae 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,7 @@ TWITTER_ACCESS_TOKEN="" TWITTER_ACCESS_TOKEN_SECRET="" # DISCORD -DISCORD_TOKEN="" \ No newline at end of file +DISCORD_TOKEN="" + +# TELEGRAM +TELEGRAM_BOT_TOKEN="" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8fe8dbb..940b113 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ propcache==0.2.1 pydantic==2.10.5 pydantic_core==2.27.2 python-dotenv==1.0.1 +python-telegram-bot==22.5 PyYAML==6.0.2 requests==2.32.3 requests-oauthlib==2.0.0 diff --git a/src/agent/agent_config.py b/src/agent/agent_config.py index ed5e06a..e250fde 100644 --- a/src/agent/agent_config.py +++ b/src/agent/agent_config.py @@ -1,4 +1,5 @@ class AgentConfig: def __init__(self): self.TWITTER_ENABLED = True - self.DISCORD_ENABLED = True \ No newline at end of file + self.DISCORD_ENABLED = True + self.TELEGRAM_ENABLED = False \ No newline at end of file diff --git a/src/agent/agent_tools/telegram/README.md b/src/agent/agent_tools/telegram/README.md new file mode 100644 index 0000000..9bd640c --- /dev/null +++ b/src/agent/agent_tools/telegram/README.md @@ -0,0 +1,65 @@ +# Telegram Tool + +This tool enables the agent to interact with Telegram via a bot. The bot can respond to messages in private chats and group conversations. + +## Setup + +### 1. Create a Telegram Bot + +1. Open Telegram and search for [@BotFather](https://t.me/botfather) +2. Start a conversation and send `/newbot` +3. Follow the prompts to create your bot +4. Save the bot token provided by BotFather + +### 2. Add Bot Token to Environment + +Add your bot token to the `.env` file: +``` +TELEGRAM_BOT_TOKEN=your_bot_token_here +``` + +### 3. Enable Telegram Tool + +In `src/agent/agent_config.py`, set: +```python +self.TELEGRAM_ENABLED = True +``` + +## Configuration + +You can customize the bot's behavior in `telegram_config.py`: + +- `RESPONSE_PROMPT`: The prompt given to the model to generate responses + +## Testing + +To test the Telegram bot separately from the main agent: +```bash +python3 -m src.agent.agent_tools.telegram +``` + +Expected output: +``` +INFO: [TELEGRAM] Initializing Telegram client... +INFO: [TELEGRAM] Starting Telegram client... +``` + +## Usage + +### Private Chats +The bot will respond to all messages sent in private chats. + +### Group Chats +In group chats, the bot will only respond when mentioned with @. Add the bot to a group and mention it to get responses. + +## Troubleshooting + +**Bot not responding:** +- Check that `TELEGRAM_BOT_TOKEN` is correctly set in `.env` +- Verify that `TELEGRAM_ENABLED = True` in `agent_config.py` +- Check the logs for any error messages + +**Group mentions not working:** +- Make sure the bot has permission to read messages in the group +- The bot must be a member of the group +- Try mentioning the bot with @ followed by its username diff --git a/src/agent/agent_tools/telegram/__init__.py b/src/agent/agent_tools/telegram/__init__.py new file mode 100644 index 0000000..9f9b639 --- /dev/null +++ b/src/agent/agent_tools/telegram/__init__.py @@ -0,0 +1 @@ +# Telegram bot module diff --git a/src/agent/agent_tools/telegram/__main__.py b/src/agent/agent_tools/telegram/__main__.py new file mode 100644 index 0000000..7a85c1c --- /dev/null +++ b/src/agent/agent_tools/telegram/__main__.py @@ -0,0 +1,21 @@ +import os +from dotenv import load_dotenv +from .telegram import Telegram +from ..model.model import Model + +if __name__ == "__main__": + load_dotenv() + + bot_token = os.getenv("TELEGRAM_BOT_TOKEN") + if not bot_token: + print("Error: TELEGRAM_BOT_TOKEN not found in environment variables") + exit(1) + + model_api_key = os.getenv("MODEL_API_KEY") + if not model_api_key: + print("Error: MODEL_API_KEY not found in environment variables") + exit(1) + + model = Model(api_key=model_api_key) + telegram = Telegram(bot_token=bot_token, model=model) + telegram.run() diff --git a/src/agent/agent_tools/telegram/telegram.py b/src/agent/agent_tools/telegram/telegram.py new file mode 100644 index 0000000..fefb332 --- /dev/null +++ b/src/agent/agent_tools/telegram/telegram.py @@ -0,0 +1,115 @@ +import logging +from telegram import Update +from telegram.ext import Application, MessageHandler, filters, ContextTypes +from .telegram_config import TelegramConfig + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +logging.basicConfig(format="%(levelname)s: %(message)s") + +class Telegram: + """A class for interfacing with the Telegram Bot API. + + Attributes: + application (telegram.ext.Application): The Telegram bot application instance. + model: The language model used for generating responses. + config (TelegramConfig): Configuration settings for the Telegram bot. + + Methods: + run(): Starts the Telegram bot and begins polling for messages. + """ + + def __init__(self, bot_token, model): + """ + Initializes the Telegram class with the necessary parameters. + + Args: + bot_token (str): The bot token from @BotFather. + model: The language model instance for generating responses. + + Sets up the Telegram bot application and registers message handlers. + """ + logger.info("[TELEGRAM] Initializing Telegram client...") + self.bot_token = bot_token + self.model = model + self.config = TelegramConfig() + + self.application = Application.builder().token(self.bot_token).build() + + # Register message handlers + self.application.add_handler( + MessageHandler( + filters.TEXT & ~filters.COMMAND, + self._handle_message + ) + ) + + + def run(self): + """Starts the Telegram bot and begins polling for messages.""" + logger.info("[TELEGRAM] Starting Telegram client...") + + # Run the bot using run_polling which blocks + self.application.run_polling(allowed_updates=Update.ALL_TYPES) + + + async def _handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + """ + Handles incoming messages and generates responses. + + Args: + update (telegram.Update): The incoming update containing message info. + context (telegram.ext.ContextTypes.DEFAULT_TYPE): Context for the handler. + """ + message = update.message + + if not message or not message.text: + return + + logger.info(f"[TELEGRAM] Message received from {message.from_user.username}: {message.text}") + + # Check if bot should respond to this message + if not self._should_respond(message): + return + + try: + # Generate response using model + prompt = f"{self.config.RESPONSE_PROMPT} {message.text}" + response = self.model.query(prompt) + logger.info(f"[TELEGRAM] Response: {response}") + + # Send response + logger.info("[TELEGRAM] Sending response...") + await message.reply_text(response) + + except Exception as e: + logger.exception(f"[TELEGRAM] Error responding to message {message.message_id}. {e}") + + + def _should_respond(self, message) -> bool: + """ + Determines if the bot should respond to a message. + + Args: + message: The Telegram message object. + + Returns: + bool: True if the bot should respond, False otherwise. + """ + # In private chats, respond to everything + if message.chat.type == "private": + return True + + # In group chats, only respond if bot is mentioned + if message.chat.type in ["group", "supergroup"]: + # Check if bot is mentioned + if message.entities: + for entity in message.entities: + if entity.type == "mention": + # Get the mentioned username + mentioned = message.text[entity.offset:entity.offset + entity.length] + # This would need the bot's username to compare properly + # For now, respond if ANY mention is present + return True + + return False diff --git a/src/agent/agent_tools/telegram/telegram_config.py b/src/agent/agent_tools/telegram/telegram_config.py new file mode 100644 index 0000000..f38e6eb --- /dev/null +++ b/src/agent/agent_tools/telegram/telegram_config.py @@ -0,0 +1,5 @@ +class TelegramConfig: + def __init__(self): + # Prompt that is provided to model, along with telegram message, to + # generate a response + self.RESPONSE_PROMPT = "Respond to this telegram message."