Skip to content

Commit 7fd3c83

Browse files
committed
feat: add telegram plugin to pypi
1 parent f2aa30b commit 7fd3c83

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

plugins/telegram/README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Telegram Plugin for GAME SDK
2+
3+
## Overview
4+
5+
The **Telegram Plugin** is an integration for the **Game SDK** that enables AI-driven interactions on Telegram. This plugin allows AI agents to handle messages, execute commands, and engage users through text, media, and polls.
6+
7+
## Features
8+
9+
- **Send Messages** – AI agents can send text messages to users.
10+
- **Send Media** – Supports sending photos, documents, videos, and audio.
11+
- **Create Polls** – AI agents can generate interactive polls.
12+
- **Pin & Unpin Messages** – Manage pinned messages in chats.
13+
- **Delete Messages** – Remove messages dynamically.
14+
- **AI-Powered Responses** – Leverages LLM to generate contextual replies.
15+
- **Real-Time Polling** – Runs asynchronously with Telegram’s polling system.
16+
- and more features to come!
17+
18+
## Installation
19+
### Pre-requisites
20+
Ensure you have Python 3.9+ installed. Then, install the plugin via **PyPI**:
21+
### Steps
22+
1. Install the plugin:
23+
```sh bash
24+
pip install telegram-plugin-gamesdk
25+
```
26+
2. Ensure you have a Telegram bot token and GAME API key and set them as environment variables:
27+
```sh bash
28+
export TELEGRAM_BOT_TOKEN="your-telegram-bot-token"
29+
export GAME_API_KEY="your-game-api-key"
30+
```
31+
3. Refer to the example and run the example bot:
32+
```sh bash
33+
python examples/test_telegram.py
34+
```
35+
36+
## Usage Examples
37+
### Initializing the Plugin
38+
39+
```python
40+
from telegram_plugin_gamesdk.telegram_plugin import TelegramPlugin
41+
42+
tg_bot = TelegramPlugin(bot_token='your-telegram-bot-token')
43+
tg_bot.start_polling()
44+
```
45+
46+
### Sending a Message
47+
```python
48+
tg_bot.send_message(chat_id=123456789, text="Hello from the AI Agent!")
49+
```
50+
51+
### Sending Media
52+
```python
53+
tg_bot.send_media(chat_id=123456789, media_type="photo", media="https://example.com/image.jpg", caption="Check this out!")
54+
```
55+
56+
### Creating a Poll
57+
```python
58+
tg_bot.create_poll(chat_id=123456789, question="What's your favorite color?", options=["Red", "Blue", "Green"])
59+
```
60+
61+
### Pinning and Unpinning Messages
62+
```python
63+
tg_bot.pin_message(chat_id=123456789, message_id=42)
64+
tg_bot.unpin_message(chat_id=123456789, message_id=42)
65+
```
66+
67+
### Deleting a Message
68+
```python
69+
tg_bot.delete_message(chat_id=123456789, message_id=42)
70+
```
71+
72+
## Integration with GAME Chat Agent
73+
Implement a message handler to integrate the Telegram Plugin with the GAME Chat Agent:
74+
```python
75+
from telegram import Update
76+
from telegram.ext import ContextTypes, filters, MessageHandler
77+
from game_sdk.game.chat_agent import ChatAgent
78+
79+
chat_agent = ChatAgent(
80+
prompt="You are a helpful assistant.",
81+
api_key="your-game-api-key",
82+
)
83+
84+
async def default_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
85+
"""Handles incoming messages but ignores messages from the bot itself unless it's mentioned in a group chat."""
86+
# Ignore messages from the bot itself
87+
if update.message.from_user.id == tg_plugin.bot.id:
88+
logger.info("Ignoring bot's own message.")
89+
return
90+
91+
user = update.message.from_user
92+
chat_id = update.message.chat.id
93+
chat_type = update.message.chat.type # "private", "group", "supergroup", or "channel"
94+
bot_username = f"@{tg_plugin.bot.username}"
95+
96+
logger.info(f"Update received: {update}")
97+
logger.info(f"Message received: {update.message.text}")
98+
99+
name = f"{user.first_name} (Telegram's chat_id: {chat_id}, this is not part of the partner's name but important for the telegram's function arguments)"
100+
101+
if not any(u["chat_id"] == chat_id for u in active_users):
102+
# Ignore group/supergroup messages unless the bot is mentioned
103+
if chat_type in ["group", "supergroup"] and bot_username not in update.message.text:
104+
logger.info(f"Ignoring group message not mentioning the bot: {update.message.text}")
105+
return
106+
active_users.append({"chat_id": chat_id, "name": name})
107+
logger.info(f"Active user added: {name}")
108+
logger.info(f"Active users: {active_users}")
109+
chat = chat_agent.create_chat(
110+
partner_id=str(chat_id),
111+
partner_name=name,
112+
action_space=agent_action_space,
113+
)
114+
active_chats[chat_id] = chat
115+
116+
response = active_chats[chat_id].next(update.message.text.replace(bot_username, "").strip()) # Remove bot mention
117+
logger.info(f"Response: {response}")
118+
119+
if response.message:
120+
await update.message.reply_text(response.message)
121+
122+
if response.is_finished:
123+
active_chats.pop(chat_id)
124+
active_users.remove({"chat_id": chat_id, "name": name})
125+
logger.info(f"Chat with {name} ended.")
126+
logger.info(f"Active users: {active_users}")
127+
128+
tg_plugin.add_handler(MessageHandler(filters.ALL, default_message_handler))
129+
```
130+
You can refer to [test_telegram.py](examples/test_telegram.py) for details.

plugins/telegram/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# General Information
2+
plugin_name: "telegram_plugin_gamesdk"
3+
author: "Yang from Virtuals Protocol"
4+
logo_url: "https://drive.google.com/drive/folders/1AdRKQac0a-ORqdE6mtV4wGdF8y3DhIyo"
5+
release_date: "2025-03"
6+
7+
# Description
8+
short_description: "Telegram Plugin for Game SDK"
9+
detailed_description: "This plugin provides a Telegram integration for the Game SDK. It allows developers to easily integrate Telegram functionalities like sending, deleting, pin, messages or media files etc. Credits to the yk(yenkhoon) for building the Telegram plugin for GAME Node SDK."
10+
11+
# Contact & Support
12+
x_account_handle: "@GAME_Virtuals"
13+
support_contact: "https://discord.gg/virtualsio"
14+
community_link: "https://t.me/virtuals"

plugins/telegram/pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[project]
2+
name = "telegram-plugin-gamesdk"
3+
version = "0.1.0"
4+
description = "Telegram Plugin for Python SDK for GAME by Virtuals"
5+
authors = [
6+
{name = "Ang Weoy Yang", email = "[email protected]"}
7+
]
8+
readme = "README.md"
9+
requires-python = ">=3.9"
10+
dependencies = [
11+
"python-telegram-bot>=21.11.1",
12+
]
13+
14+
[build-system]
15+
requires = ["poetry-core>=2.0.0,<3.0.0"]
16+
build-backend = "poetry.core.masonry.api"
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import asyncio
2+
from typing import List, Union
3+
from telegram import Bot
4+
from telegram.ext import ApplicationBuilder
5+
6+
7+
def _run_async(coro):
8+
"""
9+
Runs an async function safely.
10+
- If an event loop is running, it schedules the coroutine with `asyncio.create_task()`.
11+
- Otherwise, it starts a new event loop with `asyncio.run()`.
12+
"""
13+
try:
14+
loop = asyncio.get_running_loop()
15+
return asyncio.create_task(coro)
16+
except RuntimeError:
17+
return asyncio.run(coro)
18+
19+
20+
class TelegramPlugin:
21+
"""
22+
A Telegram Bot SDK Plugin that integrates message handling and function-based execution.
23+
24+
Features:
25+
- Handles user interactions in Telegram.
26+
- Supports function-based execution (e.g., sending messages, polls).
27+
- Manages active user sessions.
28+
29+
Attributes:
30+
bot_token (str): The Telegram bot token, loaded from environment.
31+
application (Application): The Telegram application instance.
32+
bot (Bot): The Telegram bot instance.
33+
34+
Example:
35+
```python
36+
tgBot = TelegramPlugin(bot_token=os.getenv("TELEGRAM_BOT_TOKEN"))
37+
tgBot.start_polling()
38+
```
39+
"""
40+
41+
def __init__(self, bot_token: str):
42+
self.bot_token = bot_token
43+
self.application = ApplicationBuilder().token(self.bot_token).build()
44+
self.bot = self.application.bot
45+
46+
def send_message(self, chat_id: Union[int, str], text: str):
47+
"""Send a message to a chat safely while polling is running."""
48+
if not chat_id or not text:
49+
raise Exception("Error: chat_id and text are required.")
50+
51+
return _run_async(self.bot.send_message(chat_id=chat_id, text=text))
52+
53+
def send_media(
54+
self, chat_id: Union[int, str], media_type: str, media: str, caption: str = None
55+
):
56+
"""Send a media message (photo, document, video, audio) with an optional caption."""
57+
if not chat_id or not media_type or not media:
58+
raise Exception("Error: chat_id, media_type, and media are required.")
59+
60+
if media_type == "photo":
61+
return _run_async(self.bot.send_photo(chat_id=chat_id, photo=media, caption=caption))
62+
elif media_type == "document":
63+
return _run_async(self.bot.send_document(chat_id=chat_id, document=media, caption=caption))
64+
elif media_type == "video":
65+
return _run_async(self.bot.send_video(chat_id=chat_id, video=media, caption=caption))
66+
elif media_type == "audio":
67+
return _run_async(self.bot.send_audio(chat_id=chat_id, audio=media, caption=caption))
68+
else:
69+
raise Exception("Error: Invalid media_type. Use 'photo', 'document', 'video', or 'audio'.")
70+
71+
def create_poll(
72+
self, chat_id: Union[int, str], question: str, options: List[str], is_anonymous: bool = True,
73+
allows_multiple_answers: bool = False
74+
):
75+
"""Create a poll in a chat safely while polling is running."""
76+
if not chat_id or not question or not options:
77+
raise Exception("Error: chat_id, question, and options are required.")
78+
if not (2 <= len(options) <= 10):
79+
raise Exception("Poll must have between 2 and 10 options.")
80+
81+
return _run_async(
82+
self.bot.send_poll(
83+
chat_id=chat_id,
84+
question=question,
85+
options=options,
86+
is_anonymous=is_anonymous,
87+
allows_multiple_answers=allows_multiple_answers
88+
)
89+
)
90+
91+
def pin_message(self, chat_id: Union[int, str], message_id: int):
92+
"""Pin a message in the chat."""
93+
if chat_id is None or message_id is None:
94+
raise Exception("Error: chat_id and message_id are required to pin a message.")
95+
96+
return _run_async(self.bot.pin_chat_message(chat_id=chat_id, message_id=message_id))
97+
98+
def unpin_message(self, chat_id: Union[int, str], message_id: int):
99+
"""Unpin a specific message in the chat."""
100+
if chat_id is None or message_id is None:
101+
raise Exception("Error: chat_id and message_id are required to unpin a message.")
102+
103+
return _run_async(self.bot.unpin_chat_message(chat_id=chat_id, message_id=message_id))
104+
105+
def delete_message(self, chat_id: Union[int, str], message_id: int):
106+
"""Delete a message from the chat."""
107+
if chat_id is None or message_id is None:
108+
raise Exception("Error: chat_id and message_id are required to delete a message.")
109+
110+
return _run_async(self.bot.delete_message(chat_id=chat_id, message_id=message_id))
111+
112+
def start_polling(self):
113+
"""Start polling asynchronously in the main thread."""
114+
self.application.run_polling()
115+
116+
def add_handler(self, handler):
117+
"""Register a message handler for text messages."""
118+
self.application.add_handler(handler)

0 commit comments

Comments
 (0)