创建第一个 Sai Telegram 机器人
按步骤创建查询 Sai 真实数据的 Telegram 机器人,无需机器人开发经验。
将要构建什么
Section titled “将要构建什么”完成后,机器人可以:
- 显示任意钱包的合约交易。
- 显示全部代币预言机价格。
- 搜索指定代币价格。
- 按 BTC、ETH 等资产筛选。
- 用下一页按钮分页。
无需数据库,全部数据直接来自 Sai GraphQL API。
开始前确认以下条件。
- Python 3.10+:下载。
- 在终端执行
python3 --version检查版本。
- 在终端执行
- 代码编辑器:
- VS Code,适合初学者。
- PyCharm。
- Sublime Text。
- 记事本也可以。
- 终端: 需要输入一些命令。
- Telegram 账户,用于测试。
- 机器人 Token,从官方创建工具 BotFather 获取。
- Python 基础,条件语句、函数、字典。
- 终端目录切换和执行命令。
- 无需 GraphQL 知识,指南会解释。
未安装 Python 时,从上述链接选择操作系统并安装。
第 1 部分:获取机器人 Token,5 分钟
Section titled “第 1 部分:获取机器人 Token,5 分钟”机器人需要唯一 Token 向 Telegram 表明身份,获取方法如下。
第 1 步:打开 BotFather
Section titled “第 1 步:打开 BotFather”- 在手机、桌面或网页打开 Telegram。
- 搜索
@BotFather。 - 开始对话。
BotFather 会回复欢迎消息和命令。
第 2 步:创建机器人
Section titled “第 2 步:创建机器人”- 发送
/newbot。 - BotFather 会询问机器人名称。
- 输入
My Sai Trading Bot等名称,用于 Telegram 显示。
- 输入
- 然后询问用户名,必须以
bot结尾。- 输入
my_sai_trading_bot。 - 也可用
sai_price_bot、trading_bot_sai。
- 输入
第 3 步:复制 Token
Section titled “第 3 步:复制 Token”BotFather 会发送类似消息。
Done! Congratulations on your new bot. You'll find it at t.me/my_sai_trading_bot.You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your bot and it's ready to go public, ping our Bot Support if you'd like a chance to be featured on the @BotStoreBot and the Bot Store within Telegram.
Here's your token:
123456789:ABCdefGHIjklMNOpqrsTUVwxyzToken 相当于密码,不得公开或提交到 GitHub。
第 2 部分:准备电脑,10 分钟
Section titled “第 2 部分:准备电脑,10 分钟”第 1 步:创建项目目录
Section titled “第 1 步:创建项目目录”打开终端,创建项目目录。
mkdir sai-telegram-botcd sai-telegram-bot作用:
- 创建
sai-telegram-bot。 - 进入目录,后续在此操作。
第 2 步:创建虚拟环境
Section titled “第 2 步:创建虚拟环境”虚拟环境将机器人依赖与其他 Python 项目隔离。
Mac、Linux:
python3 -m venv venvsource venv/bin/activateWindows:
python -m venv venvvenv\Scripts\activate预期结果:
- 提示符前出现
(venv)。 - 例如
(venv) user@computer sai-telegram-bot %。
出现 (venv) 表示已激活虚拟环境。
这是项目独立的 Python 环境,安装包不会影响其他项目。
第 3 步:创建项目结构
Section titled “第 3 步:创建项目结构”创建所需目录和文件。
mkdir bottouch bot/__init__.pytouch bot/main.pytouch bot/graphql.pytouch requirements.txttouch .envtouch env.example将创建:
sai-telegram-bot/├── venv/ # Virtual environment (created automatically)├── bot/│ ├── __init__.py # Makes 'bot' a Python package│ ├── main.py # Your bot's main code│ └── graphql.py # Code to talk to Sai's API├── requirements.txt # List of packages to install├── .env # Your secret config (token goes here)└── env.example # Template for .env第 3 部分:安装依赖,2 分钟
Section titled “第 3 部分:安装依赖,2 分钟”包为 Python 增加功能,本机器人需要以下依赖。
第 1 步:创建 requirements.txt
Section titled “第 1 步:创建 requirements.txt”在编辑器创建 requirements.txt,写入以下内容。
python-telegram-bot==21.4requests==2.32.3python-dotenv==1.0.1pytz==2024.1各依赖作用:
| 包 | 用途 |
|---|---|
python-telegram-bot | 创建 Telegram 机器人 |
requests | 向 API 发 HTTP 请求 |
python-dotenv | 从 .env 加载保密配置 |
pytz | 处理时区 |
第 2 步:安装依赖
Section titled “第 2 步:安装依赖”确认显示 (venv),在终端执行。
pip install -r requirements.txt命令下载并安装全部包,终端会输出日志,等待完成。
成功输出:
Successfully installed python-telegram-bot-21.4 requests-2.32.3 python-dotenv-1.0.1 pytz-2024.1第 4 部分:配置秘密 Token,5 分钟
Section titled “第 4 部分:配置秘密 Token,5 分钟”将 Token 存入独立文件,避免在代码中暴露。
第 1 步:创建 env.example
Section titled “第 1 步:创建 env.example”此文件展示所需配置,不包含真实秘密值。
# Telegram Bot Configuration# Get your bot token from @BotFather on TelegramTELEGRAM_BOT_TOKEN=your_bot_token_here
# Sai GraphQL Endpoint (the server we get data from)SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query第 2 步:创建 .env
Section titled “第 2 步:创建 .env”复制示例文件。
cp env.example .env打开 .env,将 your_bot_token_here 替换为 BotFather 提供的真实 Token。
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyzSAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query不得分享或提交 .env。使用 Git 时,将其加入 .gitignore。
第 5 部分:构建 GraphQL 客户端,中级
Section titled “第 5 部分:构建 GraphQL 客户端,中级”GraphQL 客户端负责与 Sai 数据 API 通信,下面逐步说明。
什么是 GraphQL?
Section titled “什么是 GraphQL?”GraphQL 让你向服务器请求指定数据,类似按菜单点菜。
- REST: 请求交易的全部数据。
- GraphQL: 只请求 ID、数量和价格,得到所需字段。
创建 bot/graphql.py
Section titled “创建 bot/graphql.py”此文件处理与 Sai API 的全部通信。
import osfrom typing import Any, Dict, List, Optionalimport requests
# Get the GraphQL endpoint from .env file, or use defaultSAI_GRAPHQL_ENDPOINT = os.environ.get( "SAI_GRAPHQL_ENDPOINT", "https://sai-keeper.nibiru.fi/query")
class SaiGQLClient: """ This class talks to the Sai GraphQL API. It's like a translator between your bot and Sai's data server. """
def __init__(self, endpoint: Optional[str] = None) -> None: """ Initialize the client.
Args: endpoint: URL of the GraphQL server (or None to use default) """ self.endpoint = endpoint or SAI_GRAPHQL_ENDPOINT
def query(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Send a GraphQL query to the server.
Args: query: The GraphQL query string (ask what data you want) variables: Dynamic values to plug into the query
Returns: Dictionary with the response data
Raises: RuntimeError if the query fails """ # Send POST request (like sending a letter) to GraphQL server resp = requests.post( self.endpoint, json={"query": query, "variables": variables or {}}, timeout=20, # Wait max 20 seconds for response )
# Check if the HTTP request itself failed resp.raise_for_status()
# Convert response from JSON format to Python dictionary try: payload = resp.json() except ValueError as e: # If response wasn't JSON, something went wrong raise RuntimeError( f"Invalid JSON response from {self.endpoint}: {resp.text[:200]}" )
# Check if GraphQL returned an error if "errors" in payload: raise RuntimeError(str(payload["errors"]))
# Return just the "data" part of the response return payload.get("data", {})
def fetch_trades( self, trader: str, is_open: Optional[bool] = None, limit: int = 100, base_symbol: Optional[str] = None ) -> List[Dict[str, Any]]: """ Get trades for a specific wallet address.
Args: trader: Wallet address (e.g., "nibiru1abc123...") is_open: - True: only open trades - False: only closed trades - None: all trades limit: Max number of trades to return base_symbol: Filter by token (e.g., "BTC" for Bitcoin only)
Returns: List of trade dictionaries """ # This is a GraphQL query (in plain English: "give me trades") query = """ query Trades($trader: String!, $isOpen: Boolean, $limit: Int!) { perp { trades( where: { trader: $trader, isOpen: $isOpen } limit: $limit order_by: sequence order_desc: true ) { id trader isOpen isLong leverage openPrice closePrice openCollateralAmount collateralAmount openBlock { block block_ts } closeBlock { block block_ts } state { positionValue liquidationPrice pnlCollateral pnlPct } perpBorrowing { marketId baseToken { id name symbol } quoteToken { id name symbol } } } } } """
# Send the query with the variables data = self.query(query, { "trader": trader, "isOpen": is_open, "limit": limit })
# Extract trades from the response trades = data.get("perp", {}).get("trades", [])
# Filter by symbol if user asked for it (e.g., BTC only) if base_symbol: base_symbol_upper = base_symbol.upper() trades = [ t for t in trades if (t.get("perpBorrowing", {}).get("baseToken", {}).get("symbol", "") or "").upper() == base_symbol_upper ]
return trades
def fetch_prices(self, limit: int = 200) -> List[Dict[str, Any]]: """ Get current prices for all tokens from the oracle.
Args: limit: Max number of prices to return
Returns: List of price dictionaries """ query = """ query Prices($limit: Int!) { oracle { tokenPricesUsd(limit: $limit, order_by: oracle_token_id) { priceUsd token { id name symbol } lastUpdatedBlock { block block_ts } } } } """ data = self.query(query, {"limit": limit}) return data.get("oracle", {}).get("tokenPricesUsd", [])
def fetch_price_by_symbol(self, symbol: str) -> Optional[Dict[str, Any]]: """ Get price for one specific token.
Args: symbol: Token symbol (e.g., "BTC", "ETH")
Returns: Price dictionary if found, None if not """ prices = self.fetch_prices(limit=200) symbol_upper = symbol.upper() for price in prices: token = price.get("token") or {} if (token.get("symbol") or "").upper() == symbol_upper: return price return None核心概念:
class SaiGQLClient::将相关函数组织起来的类。def query()::向 API 发送查询。fetch_trades()::获取钱包交易。fetch_prices()::获取价格。$trader等变量:填入真实值的占位符。
第 6 部分:机器人主逻辑,bot/main.py
Section titled “第 6 部分:机器人主逻辑,bot/main.py”分步实现机器人功能。
1. 导入与设置
Section titled “1. 导入与设置”创建 bot/__init__.py,可以为空。
# This file makes 'bot' a Python package创建 bot/main.py,从导入和设置开始。
import osimport loggingfrom typing import Optional
from dotenv import load_dotenvfrom telegram import Update, InlineKeyboardButton, InlineKeyboardMarkupfrom telegram.ext import ( Application, CommandHandler, CallbackQueryHandler, ContextTypes,)
from .graphql import SaiGQLClient
# Set up logging (so you can see what your bot is doing)logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)作用:
- 导入已安装的库。
- 设置调试日志。
- 导入 GraphQL 客户端。
2. 交易格式化函数
Section titled “2. 交易格式化函数”添加到 bot/main.py。
def format_trade_open(trade: dict) -> str: """ Format an open trade for display in Telegram. Shows all the important info: position value, profit/loss, etc. """ trade_id = trade.get('id', '?') is_long = trade.get('isLong', False) side = "🟢 LONG" if is_long else "🔴 SHORT"
# Extract market info (what's being traded) market = trade.get("perpBorrowing", {}) base_token = market.get("baseToken") or {} quote_token = market.get("quoteToken") or {} base = base_token.get("symbol") or base_token.get("name") or "?" quote = quote_token.get("symbol") or quote_token.get("name") or "?"
# Extract trade details leverage = trade.get('leverage') entry_price = trade.get('openPrice') open_collateral = trade.get('openCollateralAmount')
# Extract state info (for open trades) state = trade.get('state') or {} position_value = state.get('positionValue') # How much the position is worth liquidation_price = state.get('liquidationPrice') # When you get liquidated pnl = state.get('pnlCollateral') # Profit/Loss in dollars pnl_pct = state.get('pnlPct') # Profit/Loss in percentage
open_ts = trade.get("openBlock", {}).get("block_ts")
# Build the message lines = [ f"━━━━━━━━━━━━━━━━", f"Trade #{trade_id} ✅ OPEN", f"{base}/{quote} | {side} | {leverage}x leverage", f"Entry Price: {entry_price}", ]
if liquidation_price: lines.append(f"Liquidation Price: {liquidation_price}")
# Convert from micro units to dollars # (API stores values * 1,000,000, we need to divide back) if position_value is not None: position_value_usd = position_value / (10 ** 6) lines.append(f"Position Value: ${position_value_usd:,.2f}")
if pnl is not None: pnl_usd = pnl / (10 ** 6) pnl_sign = "+" if pnl_usd >= 0 else "" lines.append(f"PnL: {pnl_sign}${pnl_usd:,.2f}")
if pnl_pct is not None: pnl_sign = "+" if pnl_pct >= 0 else "" lines.append(f"PnL %: {pnl_sign}{pnl_pct:.2f}%")
if open_collateral: collateral_usd = open_collateral / (10 ** 6) lines.append(f"Collateral: ${collateral_usd:,.2f}")
if open_ts: lines.append(f"Opened: {open_ts}")
return "\n".join(lines)
def format_trade_closed(trade: dict) -> str: """ Format a closed trade for display. Shows entry/exit prices and when it was opened/closed. """ trade_id = trade.get('id', '?') is_long = trade.get('isLong', False) side = "🟢 LONG" if is_long else "🔴 SHORT"
market = trade.get("perpBorrowing", {}) base_token = market.get("baseToken") or {} quote_token = market.get("quoteToken") or {} base = base_token.get("symbol") or base_token.get("name") or "?" quote = quote_token.get("symbol") or quote_token.get("name") or "?"
leverage = trade.get('leverage') entry_price = trade.get('openPrice') exit_price = trade.get('closePrice')
open_ts = trade.get("openBlock", {}).get("block_ts") close_ts = trade.get("closeBlock", {}).get("block_ts")
lines = [ f"━━━━━━━━━━━━━━━━", f"Trade #{trade_id} ❌ CLOSED", f"{base}/{quote} | {side} | {leverage}x leverage", f"Entry Price: {entry_price}", ]
if exit_price: lines.append(f"Exit Price: {exit_price}")
if open_ts: lines.append(f"Opened: {open_ts}") if close_ts: lines.append(f"Closed: {close_ts}")
return "\n".join(lines)作用:
format_trade_open():显示未平仓交易和盈亏。format_trade_closed():显示已平仓的入场、退出价格。- 除以
10^6,将 API 微单位转换为普通美元。
3. 价格格式化函数
Section titled “3. 价格格式化函数”添加到 bot/main.py。
# Popular tokens to show firstPOPULAR_TOKENS = ["BTC", "ETH", "USDT", "USDC", "NIBI", "ATOM", "SOL"]
def format_prices(prices: list, start_idx: int = 0, page_size: int = 10) -> tuple: """ Format prices for display with pagination (Next buttons).
Args: prices: List of all prices start_idx: Which index to start at page_size: How many prices per page
Returns: (formatted_text, has_more_pages) """ if not prices: return "No prices found.", False
# Sort prices: popular tokens first, then by ID def sort_key(p): token = p.get("token") or {} symbol = (token.get("symbol") or "").upper() if symbol in POPULAR_TOKENS: return (0, POPULAR_TOKENS.index(symbol)) return (1, token.get("id", 9999))
sorted_prices = sorted(prices, key=sort_key)
# Get just this page of prices end_idx = min(start_idx + page_size, len(sorted_prices)) page_prices = sorted_prices[start_idx:end_idx] has_more = end_idx < len(sorted_prices)
# Format as text msg_lines = [f"💰 Oracle Prices ({end_idx} of {len(sorted_prices)})\n"] for p in page_prices: token = p.get("token") or {} symbol = token.get("symbol") or token.get("name") or "Unknown" price = p.get("priceUsd") if price is not None: price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}" msg_lines.append(f"• {symbol}: {price_str}")
return "\n".join(msg_lines), has_more
def format_price_single(price_data: dict) -> str: """Format a single price for display.""" token = price_data.get("token") or {} symbol = token.get("symbol") or token.get("name") or "?" price = price_data.get("priceUsd")
if price is None: return f"Price not available for {symbol}"
price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}" return f"💰 {symbol}: {price_str}"作用:
format_prices():多价格格式化和分页。format_price_single():格式化单个价格。- BTC、ETH 等热门资产优先。
4. 命令处理
Section titled “4. 命令处理”添加到 bot/main.py。
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle /start command - shows welcome message.""" text = ( "👋 Welcome to Sai Bot!\n\n" "Available commands:\n" "/trades <address> - View trades for a wallet\n" "/prices - Show all token prices\n" "/price <symbol> - Get price for one token\n" "/help - Show this message\n\n" "Examples:\n" "/trades nibiru1abc123def456\n" "/price BTC\n" "/prices" ) await update.effective_message.reply_text(text)
async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle /help command.""" await start(update, context)作用:
/start显示欢迎消息。/help显示同一消息。
5. 交易命令
Section titled “5. 交易命令”添加到 bot/main.py。
async def trades_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ Handle /trades command.
Usage: /trades <address> /trades <address> open /trades <address> closed /trades <address> btc """ if not context.args: await update.effective_message.reply_text( "Usage: /trades <wallet_address> [open|closed] [symbol]\n\n" "Examples:\n" "/trades nibiru1abc123...\n" "/trades nibiru1abc123... open\n" "/trades nibiru1abc123... btc" ) return
address = context.args[0].strip() is_open = None # None = all trades symbol = None
# Parse additional arguments for arg in context.args[1:]: arg_lower = arg.strip().lower() if arg_lower == "open": is_open = True elif arg_lower == "closed": is_open = False else: symbol = arg.strip().upper()
try: # Get trades from Sai gql = SaiGQLClient() trades = gql.fetch_trades( trader=address, is_open=is_open, limit=100, base_symbol=symbol )
if not trades: await update.effective_message.reply_text( f"❌ No trades found for {address}" ) return
# Separate open and closed trades open_trades = [t for t in trades if t.get('isOpen')] closed_trades = [t for t in trades if not t.get('isOpen')]
# Show open trades first if open_trades: for i, trade in enumerate(open_trades[:5]): # Show first 5 await update.effective_message.reply_text( format_trade_open(trade) )
# Show closed trades if closed_trades: for i, trade in enumerate(closed_trades[:5]): # Show first 5 await update.effective_message.reply_text( format_trade_closed(trade) )
except Exception as e: logger.error(f"Error in trades_cmd: {e}") await update.effective_message.reply_text( f"❌ Error: {str(e)}" )6. 价格命令
Section titled “6. 价格命令”添加到 bot/main.py。
async def prices_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle /prices command - show all prices.""" try: gql = SaiGQLClient() prices = gql.fetch_prices(limit=200)
if not prices: await update.effective_message.reply_text("No prices found.") return
# Get first page text, has_more = format_prices(prices, start_idx=0, page_size=10)
# Add keyboard with "Next" button if there are more prices reply_markup = None if has_more: keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]] reply_markup = InlineKeyboardMarkup(keyboard)
await update.effective_message.reply_text(text, reply_markup=reply_markup)
except Exception as e: logger.error(f"Error in prices_cmd: {e}") await update.effective_message.reply_text(f"❌ Error: {str(e)}")
async def price_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle /price <symbol> command - show single price.""" if not context.args: await update.effective_message.reply_text( "Usage: /price <symbol>\n\n" "Examples: /price BTC, /price ETH" ) return
symbol = context.args[0].strip()
try: gql = SaiGQLClient() price_data = gql.fetch_price_by_symbol(symbol)
if not price_data: await update.effective_message.reply_text( f"❌ Token '{symbol}' not found" ) return
text = format_price_single(price_data) await update.effective_message.reply_text(text)
except Exception as e: logger.error(f"Error in price_cmd: {e}") await update.effective_message.reply_text(f"❌ Error: {str(e)}")7. 按钮回调
Section titled “7. 按钮回调”添加到 bot/main.py。
async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Handle button clicks (pagination buttons).""" query = update.callback_query if not query: return
# Acknowledge the button click await query.answer()
# Handle "Next" button for prices if query.data == "prices_next": page = context.user_data.get("prices_page", 0) + 1 context.user_data["prices_page"] = page
try: gql = SaiGQLClient() prices = gql.fetch_prices(limit=200) text, has_more = format_prices(prices, start_idx=page * 10, page_size=10)
reply_markup = None if has_more: keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]] reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(text, reply_markup=reply_markup) except Exception as e: await query.edit_message_text(f"❌ Error: {str(e)}")8. 启动机器人
Section titled “8. 启动机器人”添加到 bot/main.py 末尾。
def main() -> None: """Main function - creates and starts the bot.""" # Load environment variables from .env file load_dotenv()
# Get bot token token = os.environ.get("TELEGRAM_BOT_TOKEN") if not token: raise RuntimeError("❌ TELEGRAM_BOT_TOKEN not found in .env!")
# Create the bot application app = Application.builder().token(token).build()
# Register command handlers app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("help", help_cmd)) app.add_handler(CommandHandler("trades", trades_cmd)) app.add_handler(CommandHandler("prices", prices_cmd)) app.add_handler(CommandHandler("price", price_cmd))
# Register button click handler app.add_handler(CallbackQueryHandler(callback_handler))
# Start the bot logger.info("🚀 Bot is starting...") app.run_polling()
if __name__ == "__main__": main()第 7 部分:测试,5 分钟
Section titled “第 7 部分:测试,5 分钟”第 1 步:激活虚拟环境
Section titled “第 1 步:激活虚拟环境”确认位于项目目录且虚拟环境已激活。
# You should see (venv) at the start of your terminal prompt# If not, activate it:
# Mac/Linux:source venv/bin/activate
# Windows:venv\Scripts\activate第 2 步:启动
Section titled “第 2 步:启动”python -m bot.main应看到以下输出。
INFO:__main__:🚀 Bot is starting...机器人已运行,请保持终端打开。
第 3 步:在 Telegram 测试
Section titled “第 3 步:在 Telegram 测试”- 打开 Telegram。
- 搜索创建的用户名。
- 发送
/start。 - 确认欢迎消息。
尝试以下命令。
| 命令 | 作用 |
|---|---|
/start | 欢迎消息 |
/help | 帮助 |
/prices | 全部价格 |
/price BTC | BTC 价格 |
/trades nibiru1abc123... | 交易,需真实钱包地址 |
第 4 步:停止
Section titled “第 4 步:停止”测试后,在终端按 Ctrl+C 停止。
TELEGRAM_BOT_TOKEN not found
Section titled “TELEGRAM_BOT_TOKEN not found”解决方法:
- 确认项目根目录存在
.env。 - 确认有 Token。
- 不留空格:
TELEGRAM_BOT_TOKEN=123456789:ABC...。
解决方法:
- 确认仍在运行,终端应有
🚀 Bot is starting...。 - 先尝试
/start。 - 检查终端错误。
ModuleNotFoundError: No module named ‘bot’
Section titled “ModuleNotFoundError: No module named ‘bot’”解决方法:
# Make sure you're in the project root directorycd /path/to/sai-telegram-bot
# Make sure you activated the virtual environmentsource venv/bin/activate # Mac/Linux# orvenv\Scripts\activate # Windows
# Run with -m flagpython -m bot.mainGraphQL 错误、No prices found
Section titled “GraphQL 错误、No prices found”解决方法:
-
检查网络。
-
验证
.env的端点。SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query -
在浏览器打开 URL 手动测试。
用户使用机器人时的流程如下。
1. User sends: /prices ↓2. Telegram receives it and forwards to your bot ↓3. prices_cmd() function is called ↓4. SaiGQLClient queries the Sai GraphQL API ↓5. API returns price data ↓6. format_prices() formats the data beautifully ↓7. Bot sends the message back to the user ↓8. User sees prices with a "Next" button ↓9. User clicks "Next" ↓10. callback_handler() fetches the next page ↓11. User sees next page of prices运行后,可继续添加功能。
- 价格提醒,如 BTC 达到 $100,000 时通知。
- 钱包监控,如指定地址开仓时通知。
- 增加市场和数据源。
- 在 AWS、DigitalOcean 等服务器全天运行。
- 用
systemd或 Docker 保持运行。
第一个 Telegram 机器人已支持:
- 连接 Sai。
- 查询实时数据。
- 显示价格和交易。
- 分页。
- 按资产筛选。
你已掌握机器人开发的基础。
问:可以分享给别人吗? 答:可以,在 Telegram 搜索用户名并点击 Start 即可。
问:关闭电脑后还会运行吗? 答:不会。全天运行需部署服务器,见生产部署部分。
问:如何更新功能?
答:编辑 Python 文件,Ctrl+C 停止,再运行 python -m bot.main。
问:可以分享 Token 吗?
答:不可以。视为密码,保存在 .env,不要提交 GitHub。
继续尝试开发吧。