diff --git a/tools/telegram_bot/README.md b/tools/telegram_bot/README.md index bf7546746..eb204f688 100644 --- a/tools/telegram_bot/README.md +++ b/tools/telegram_bot/README.md @@ -1,23 +1,30 @@ -# RustChain Telegram Bot +# RustChain Telegram Community Bot -Telegram bot for RustChain community. +Telegram bot for the RustChain community. Provides real-time chain data. ## Commands - -- `/price` - wRTC price -- `/miners` - Active miners -- `/epoch` - Current epoch -- `/balance ` - Check balance -- `/health` - Node health +- `/price` — Current wRTC price from Raydium +- `/miners` — Active miner count +- `/epoch` — Current epoch info +- `/balance ` — Check RTC balance +- `/health` — Node health status ## Setup - ```bash pip install -r requirements.txt -# Edit bot.py and set BOT_TOKEN +export TELEGRAM_BOT_TOKEN="your-bot-token" python bot.py ``` -## Bounty +## Features +- Async (aiohttp + python-telegram-bot v21) +- Error handling with graceful fallbacks +- Inline query support (bonus) +- Markdown formatted responses -50 RTC - Issue #249 +## API Endpoints Used +- `GET /api/price` — wRTC price +- `GET /api/miners` — Miner data +- `GET /epoch` — Epoch info +- `GET /api/balance/` — Wallet balance +- `GET /api/health` — Node health diff --git a/tools/telegram_bot/bot.py b/tools/telegram_bot/bot.py index 8b45bf70b..52a2633a1 100644 --- a/tools/telegram_bot/bot.py +++ b/tools/telegram_bot/bot.py @@ -1,113 +1,145 @@ -""" -RustChain Telegram Community Bot -Bounty: 50 RTC -Issue: #249 -""" +#!/usr/bin/env python3 +"""RustChain Telegram Community Bot — Bounty #249""" -import asyncio +import os import logging -import requests +import aiohttp from telegram import Update -from telegram.ext import Application, CommandHandler, ContextTypes +from telegram.ext import Application, CommandHandler, ContextTypes, InlineQueryHandler +from telegram import InlineQueryResultArticle, InputTextMessageContent +import uuid -# Configure logging -logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -# RustChain API Base URL -RUSTCHAIN_API = "https://50.28.86.131" - -# Bot token - User needs to set this from @BotFather -BOT_TOKEN = "YOUR_BOT_TOKEN_HERE" - - -async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - welcome = """🛡️ *RustChain Bot* - -/price - wRTC price -/miners - Active miners -/epoch - Epoch info -/balance - Wallet balance -/health - Node health -/help - Commands""" - await update.message.reply_text(welcome, parse_mode='Markdown') - - -async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - help_text = """🛡️ *Commands* - -/price - wRTC price -/miners - Miner count -/epoch - Epoch info -/balance - Check balance -/health - Node status""" - await update.message.reply_text(help_text, parse_mode='Markdown') - - -async def price_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - await update.message.reply_text("📊 Price feature - coming soon!") - - -async def miners_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - try: - r = requests.get(f"{RUSTCHAIN_API}/api/miners", verify=False, timeout=10) - miners = r.json() if r.status_code == 200 else [] - count = len(miners) if isinstance(miners, list) else "N/A" - await update.message.reply_text(f"⛏️ *Miners:* {count}", parse_mode='Markdown') - except Exception as e: - await update.message.reply_text(f"❌ Error: {str(e)}") - - -async def epoch_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - try: - r = requests.get(f"{RUSTCHAIN_API}/epoch", verify=False, timeout=10) - if r.status_code == 200: - data = r.json() - epoch = data.get('epoch', 'N/A') - await update.message.reply_text(f"📅 *Epoch:* {epoch}", parse_mode='Markdown') - else: - await update.message.reply_text("❌ Could not fetch epoch") - except Exception as e: - await update.message.reply_text(f"❌ Error: {str(e)}") - - -async def balance_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - if not context.args: - await update.message.reply_text("Usage: /balance ") +API_BASE = "http://50.28.86.131" +BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"] + +async def fetch(session, path): + async with session.get(f"{API_BASE}{path}", timeout=aiohttp.ClientTimeout(total=10)) as r: + if r.status == 200: + return await r.json() + return None + +async def cmd_price(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Current wRTC price from Raydium""" + async with aiohttp.ClientSession() as s: + data = await fetch(s, "/api/price") + if data: + price = data.get("price", "N/A") + change = data.get("change_24h", "N/A") + await update.message.reply_text( + f"💰 *wRTC Price*\n" + f"Price: `${price}`\n" + f"24h Change: `{change}%`", + parse_mode="Markdown" + ) + else: + await update.message.reply_text("⚠️ Could not fetch price data.") + +async def cmd_miners(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Active miner count""" + async with aiohttp.ClientSession() as s: + data = await fetch(s, "/api/miners") + if data: + count = data.get("active", data.get("count", len(data) if isinstance(data, list) else "N/A")) + await update.message.reply_text( + f"⛏️ *Active Miners*\nCount: `{count}`", + parse_mode="Markdown" + ) + else: + await update.message.reply_text("⚠️ Could not fetch miner data.") + +async def cmd_epoch(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Current epoch info""" + async with aiohttp.ClientSession() as s: + data = await fetch(s, "/epoch") + if data: + epoch = data.get("epoch", data.get("current", "N/A")) + await update.message.reply_text( + f"🔄 *Epoch Info*\n" + f"Current Epoch: `{epoch}`\n" + f"Details: `{data}`", + parse_mode="Markdown" + ) + else: + await update.message.reply_text("⚠️ Could not fetch epoch data.") + +async def cmd_balance(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Check RTC balance for a wallet""" + if not ctx.args: + await update.message.reply_text("Usage: `/balance `", parse_mode="Markdown") return - wallet = context.args[0] - try: - r = requests.get(f"{RUSTCHAIN_API}/wallet/balance", params={"miner_id": wallet}, verify=False, timeout=10) - data = r.json() if r.status_code == 200 else {} - balance = data.get('balance', 'N/A') - await update.message.reply_text(f"💰 *Balance:* {balance} wRTC", parse_mode='Markdown') - except Exception as e: - await update.message.reply_text(f"❌ Error: {str(e)}") - - -async def health_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - try: - r = requests.get(f"{RUSTCHAIN_API}/health", verify=False, timeout=10) - if r.status_code == 200: - await update.message.reply_text("❤️ *Node: Healthy*", parse_mode='Markdown') - else: - await update.message.reply_text("❌ Node: Unhealthy") - except Exception as e: - await update.message.reply_text(f"❌ Error: {str(e)}") - + wallet = ctx.args[0] + async with aiohttp.ClientSession() as s: + data = await fetch(s, f"/api/balance/{wallet}") + if data: + bal = data.get("balance", "N/A") + await update.message.reply_text( + f"💳 *Wallet Balance*\n" + f"Address: `{wallet[:8]}...{wallet[-6:]}`\n" + f"Balance: `{bal} RTC`", + parse_mode="Markdown" + ) + else: + await update.message.reply_text("⚠️ Could not fetch balance. Check wallet address.") + +async def cmd_health(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Node health status""" + async with aiohttp.ClientSession() as s: + data = await fetch(s, "/api/health") + if data: + status = data.get("status", "unknown") + emoji = "🟢" if status == "ok" else "🔴" + await update.message.reply_text( + f"{emoji} *Node Health*\nStatus: `{status}`\nDetails: `{data}`", + parse_mode="Markdown" + ) + else: + await update.message.reply_text("⚠️ Node unreachable or unhealthy.") + +async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + await update.message.reply_text( + "⛓️ *RustChain Community Bot*\n\n" + "Commands:\n" + "/price — wRTC price from Raydium\n" + "/miners — Active miner count\n" + "/epoch — Current epoch info\n" + "/balance — Check RTC balance\n" + "/health — Node health status\n", + parse_mode="Markdown" + ) + +async def inline_query(update: Update, ctx: ContextTypes.DEFAULT_TYPE): + """Inline query support (bonus)""" + query = update.inline_query.query.lower() + results = [] + if "price" in query or not query: + results.append(InlineQueryResultArticle( + id=str(uuid.uuid4()), title="wRTC Price", + input_message_content=InputTextMessageContent("Fetching wRTC price... Use /price in the bot chat."), + description="Get current wRTC price" + )) + if "miners" in query or not query: + results.append(InlineQueryResultArticle( + id=str(uuid.uuid4()), title="Active Miners", + input_message_content=InputTextMessageContent("Use /miners in the bot chat to see active miners."), + description="Check active miner count" + )) + await update.inline_query.answer(results[:5]) def main(): app = Application.builder().token(BOT_TOKEN).build() - app.add_handler(CommandHandler("start", start_command)) - app.add_handler(CommandHandler("help", help_command)) - app.add_handler(CommandHandler("price", price_command)) - app.add_handler(CommandHandler("miners", miners_command)) - app.add_handler(CommandHandler("epoch", epoch_command)) - app.add_handler(CommandHandler("balance", balance_command)) - app.add_handler(CommandHandler("health", health_command)) - print("🤖 RustChain Bot starting...") - app.run_polling(ping_interval=30) - + app.add_handler(CommandHandler("start", cmd_start)) + app.add_handler(CommandHandler("help", cmd_start)) + app.add_handler(CommandHandler("price", cmd_price)) + app.add_handler(CommandHandler("miners", cmd_miners)) + app.add_handler(CommandHandler("epoch", cmd_epoch)) + app.add_handler(CommandHandler("balance", cmd_balance)) + app.add_handler(CommandHandler("health", cmd_health)) + app.add_handler(InlineQueryHandler(inline_query)) + logger.info("RustChain bot starting...") + app.run_polling() if __name__ == "__main__": main() diff --git a/tools/telegram_bot/requirements.txt b/tools/telegram_bot/requirements.txt index 2f1df77c0..1c14e02b5 100644 --- a/tools/telegram_bot/requirements.txt +++ b/tools/telegram_bot/requirements.txt @@ -1,3 +1,2 @@ -python-telegram-bot>=20.0 -requests -urllib3 +python-telegram-bot>=21.0 +aiohttp>=3.9.0