コンテンツにスキップ
このページの翻訳は LLM によって生成されています。誤りに気づいた場合は GitHub で issue を開いてお知らせください。

初めてのSai Telegramボット

Saiの実データを取得するTelegramボットを、手順に沿って作成します。ボット開発の経験は不要です。


完成すると、次の機能を持つボットが動きます。

  • ウォレットごとの無期限先物取引を表示。
  • 全トークンのオラクル価格を表示。
  • 特定トークンを検索。
  • BTC、ETHなど資産別に絞り込み。
  • 次へボタンでページ分割。

データベースは不要です。SaiのGraphQL APIから実データを取得します。


開始前に次を確認してください。

  • 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. スマートフォン、PC、ウェブでTelegramを開きます。
  2. @BotFather を検索します。
  3. 会話を開始します。

歓迎メッセージとコマンドが返ってきます。

  1. /newbot を送ります。
  2. BotFatherに名前を聞かれます。
    • My Sai Trading Bot など、Telegramに表示する名前を入力します。
  3. ユーザー名を聞かれます。
    • my_sai_trading_bot など、末尾が bot の名前を入力します。
    • 例は sai_price_bot、trading_bot_sai。

次のようなメッセージが届きます。

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にコミットしたりしてはいけません。


手順1:プロジェクトフォルダー

Section titled “手順1:プロジェクトフォルダー”

ターミナルを開き、フォルダーを作ります。

Terminal window
mkdir sai-telegram-bot
cd sai-telegram-bot

処理内容:

  • sai-telegram-bot を作成。
  • そのフォルダーに移動。以後はここで作業します。

仮想環境は、他のPythonプロジェクトと依存パッケージを分けます。

Mac・Linux:

Terminal window
python3 -m venv venv
source venv/bin/activate

Windows:

Terminal window
python -m venv venv
venv\Scripts\activate

確認すること:

  • プロンプトの先頭に (venv)。
  • 例:(venv) user@computer sai-telegram-bot %。

(venv) があれば仮想環境が有効です。

プロジェクト専用のPython環境です。ここに入れるパッケージは他のプロジェクトに影響しません。

必要なフォルダーとファイルを作成します。

Terminal window
mkdir bot
touch bot/__init__.py
touch bot/main.py
touch bot/graphql.py
touch requirements.txt
touch .env
touch 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

パッケージはPythonの機能を追加します。このボットにはいくつか必要です。

エディターで requirements.txt に次を入力します。

python-telegram-bot==21.4
requests==2.32.3
python-dotenv==1.0.1
pytz==2024.1

各パッケージの役割:

パッケージ用途
python-telegram-botTelegramボット作成
requestsAPIへのHTTP要求
python-dotenv.envから秘密の設定を読み込み
pytzタイムゾーン処理

(venv) があることを確認して実行します。

Terminal window
pip install -r requirements.txt

すべてのパッケージをダウンロード・インストールします。表示が流れるので、完了を待ってください。

成功時の表示:

Terminal window
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分”

コードに露出しないよう、専用ファイルに保存します。

必要な設定を示します。実際の秘密値は入れません。

# Telegram Bot Configuration
# Get your bot token from @BotFather on Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here
# Sai GraphQL Endpoint (the server we get data from)
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query

サンプルをコピーします。

Terminal window
cp env.example .env

.env を開き、your_bot_token_here を実際のBotFatherトークンに置き換えます。

TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query

.env は共有・コミットしてはいけません。Gitを使う場合は .gitignore に追加してください。


第5部:GraphQLクライアント、中級

Section titled “第5部:GraphQLクライアント、中級”

SaiのデータAPIと通信するコードを作ります。順に説明します。

必要なデータを指定してサーバーに要求する方法です。メニューから選ぶように使えます。

  • REST: 取引の全データを要求。
  • GraphQL: ID、数量、価格だけを要求し、必要な項目を取得。

Sai APIとの全通信を処理します。

import os
from typing import Any, Dict, List, Optional
import requests
# Get the GraphQL endpoint from .env file, or use default
SAI_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 などの変数:実際の値を入れるプレースホルダー。

各機能を順に実装します。

bot/__init__.py を作成します。空で構いません。

# This file makes 'bot' a Python package

bot/main.py に読み込みと設定を記述します。

import os
import logging
from typing import Optional
from dotenv import load_dotenv
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from 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クライアントを読み込む。

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のマイクロ単位を通常のドルに変換。

bot/main.py に追加します。

# Popular tokens to show first
POPULAR_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などを先に表示。

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 は同じメッセージ。

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)}"
)

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)}")

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)}")

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()

プロジェクトフォルダーと有効な仮想環境を確認します。

Terminal window
# 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
Terminal window
python -m bot.main

次の表示が出ます。

INFO:__main__:🚀 Bot is starting...

ボットが動いています。このターミナルは開いたままにしてください。

  1. Telegramを開く。
  2. 作成したユーザー名で検索。
  3. /start を送信。
  4. 歓迎メッセージを確認。

次のコマンドを試します。

コマンド処理
/start歓迎メッセージ
/helpヘルプ
/prices全価格
/price BTCBTC価格
/trades nibiru1abc123...取引。実際のアドレスが必要

テスト後、ターミナルで Ctrl+C を押して停止します。


解決方法:

  1. ルートに .env があるか確認。
  2. トークンがあるか確認。
  3. 空白を入れない:TELEGRAM_BOT_TOKEN=123456789:ABC...。

解決方法:

  1. ターミナルに 🚀 Bot is starting... があり、稼働中か確認。
  2. 先に /start を試す。
  3. ターミナルのエラーを確認。

ModuleNotFoundError: No module named ‘bot’

Section titled “ModuleNotFoundError: No module named ‘bot’”

解決方法:

Terminal window
# Make sure you're in the project root directory
cd /path/to/sai-telegram-bot
# Make sure you activated the virtual environment
source venv/bin/activate # Mac/Linux
# or
venv\Scripts\activate # Windows
# Run with -m flag
python -m bot.main

解決方法:

  1. インターネット接続を確認。

  2. .env の接続先を確認。

    SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query
  3. ブラウザーで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等で24時間稼働。
  • systemd やDockerで維持。

初めてのTelegramボットで次ができるようになりました。

  • Saiへの接続。
  • 実データの取得。
  • 価格・取引の表示。
  • ページ分割。
  • 資産別絞り込み。

これでボット開発の基本を学べました。


Q:他の人にも共有できる? A:はい。Telegramでユーザー名を検索し、Startを押せば使えます。

Q:PCを閉じても動く? A:いいえ。24時間稼働にはサーバーにデプロイします。本番運用の説明を参照してください。

Q:機能を更新するには? A:Pythonファイルを変更し、Ctrl+Cで停止後 python -m bot.main で再起動します。

Q:トークンを共有しても安全? A:いいえ。パスワードとして扱い、.env に保存し、GitHubにコミットしないでください。


開発を続けてみてください。