初めてのSai Telegramボット
Saiの実データを取得するTelegramボットを、手順に沿って作成します。ボット開発の経験は不要です。
完成すると、次の機能を持つボットが動きます。
- ウォレットごとの無期限先物取引を表示。
- 全トークンのオラクル価格を表示。
- 特定トークンを検索。
- BTC、ETHなど資産別に絞り込み。
- 次へボタンでページ分割。
データベースは不要です。SaiのGraphQL APIから実データを取得します。
開始前に次を確認してください。
ソフトウェア
Section titled “ソフトウェア”- Python 3.10以降:ダウンロード。
- ターミナルで
python3 --versionを実行して確認。
- ターミナルで
- エディター:
- VS Code、初心者に推奨。
- PyCharm。
- Sublime Text。
- メモ帳でも使えます。
- ターミナル: コマンドをいくつか実行します。
- Telegramアカウント、テスト用。
- ボットトークン。公式作成ツールのBotFatherから取得します。
- Pythonのif文、関数、辞書。
- ターミナルでのフォルダー移動・実行。
- GraphQLの経験は不要です。ここで説明します。
Python未導入なら、上のリンクからOSを選んでインストールしてください。
第1部:ボットトークンを取得、5分
Section titled “第1部:ボットトークンを取得、5分”Telegramにボットを識別させる固有のトークンを取得します。
手順1:BotFatherを開く
Section titled “手順1:BotFatherを開く”- スマートフォン、PC、ウェブでTelegramを開きます。
@BotFatherを検索します。- 会話を開始します。
歓迎メッセージとコマンドが返ってきます。
手順2:ボットを作成
Section titled “手順2:ボットを作成”/newbotを送ります。- BotFatherに名前を聞かれます。
My Sai Trading Botなど、Telegramに表示する名前を入力します。
- ユーザー名を聞かれます。
my_sai_trading_botなど、末尾がbotの名前を入力します。- 例は
sai_price_bot、trading_bot_sai。
手順3:トークンをコピー
Section titled “手順3:トークンをコピー”次のようなメッセージが届きます。
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:ABCdefGHIjklMNOpqrsTUVwxyzトークンはパスワードと同じです。公開したりGitHubにコミットしたりしてはいけません。
第2部:PCの準備、10分
Section titled “第2部:PCの準備、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部:秘密のトークン設定、5分
Section titled “第4部:秘密のトークン設定、5分”コードに露出しないよう、専用ファイルに保存します。
手順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トークンに置き換えます。
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyzSAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query.env は共有・コミットしてはいけません。Gitを使う場合は .gitignore に追加してください。
第5部:GraphQLクライアント、中級
Section titled “第5部:GraphQLクライアント、中級”SaiのデータAPIと通信するコードを作ります。順に説明します。
GraphQLとは?
Section titled “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 packagebot/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があるか確認。 - トークンがあるか確認。
- 空白を入れない:
TELEGRAM_BOT_TOKEN=123456789:ABC...。
コマンドに応答しない
Section titled “コマンドに応答しない”解決方法:
- ターミナルに
🚀 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次のステップ
Section titled “次のステップ”動作したら次の機能を追加できます。
機能を増やす
Section titled “機能を増やす”- BTCが$100,000に達したら通知する価格アラート。
- 特定アドレスの新規取引を通知する監視。
- 市場やデータソースの追加。
- AWS、DigitalOcean等で24時間稼働。
systemdやDockerで維持。
初めてのTelegramボットで次ができるようになりました。
- Saiへの接続。
- 実データの取得。
- 価格・取引の表示。
- ページ分割。
- 資産別絞り込み。
これでボット開発の基本を学べました。
よくある質問
Section titled “よくある質問”Q:他の人にも共有できる? A:はい。Telegramでユーザー名を検索し、Startを押せば使えます。
Q:PCを閉じても動く? A:いいえ。24時間稼働にはサーバーにデプロイします。本番運用の説明を参照してください。
Q:機能を更新するには?
A:Pythonファイルを変更し、Ctrl+Cで停止後 python -m bot.main で再起動します。
Q:トークンを共有しても安全?
A:いいえ。パスワードとして扱い、.env に保存し、GitHubにコミットしないでください。
開発を続けてみてください。