603 lines
21 KiB
Python
603 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""股票游戏插件 - 10只真实股票交易,与签到积分联动"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
import traceback
|
||
import urllib.error
|
||
import urllib.request
|
||
from datetime import datetime
|
||
from typing import Any
|
||
|
||
# 注意:不重定向 stderr,让框架单独处理错误输出
|
||
|
||
# ---------- 路径配置 ----------
|
||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
DATA_DIR = os.path.join(SCRIPT_DIR, "..", "data")
|
||
os.makedirs(DATA_DIR, exist_ok=True)
|
||
|
||
GUPIAO_FILE = os.path.join(DATA_DIR, "gupiao.json")
|
||
QIANDAN_FILE = os.path.join(
|
||
SCRIPT_DIR, "..", "..", "qiandao", "data", "qiandao.json"
|
||
)
|
||
|
||
# ---------- 股票池定义 ----------
|
||
STOCK_DEFINITIONS = [
|
||
{"symbol": "腾讯", "api_code": "hk00700"},
|
||
{"symbol": "阿里", "api_code": "hk09988"},
|
||
{"symbol": "苹果", "api_code": "usAAPL"},
|
||
{"symbol": "平安", "api_code": "sh601318"},
|
||
{"symbol": "特斯拉", "api_code": "usTSLA"},
|
||
{"symbol": "工业富联","api_code": "sh601138"},
|
||
{"symbol": "药明康德","api_code": "sh603259"},
|
||
{"symbol": "宁德时代","api_code": "sz300750"},
|
||
{"symbol": "茅台", "api_code": "sh600519"},
|
||
{"symbol": "三七互娱","api_code": "sz002555"},
|
||
]
|
||
SYMBOLS_LOWER = {s["symbol"].lower(): s for s in STOCK_DEFINITIONS}
|
||
|
||
# ---------- 游戏参数 ----------
|
||
TRANSACTION_FEE_RATE = 0.001 # 交易手续费 0.1%
|
||
TAX_RATE = 0.0005 # 印花税费率 0.05%(卖出时收取)
|
||
MIN_TRANSACTION_SHARES = 1 # 最小交易数量
|
||
API_TIMEOUT = 10 # API 请求超时(秒)
|
||
|
||
|
||
# ====================================================================
|
||
# API 数据获取
|
||
# ====================================================================
|
||
|
||
# --- qt.gtimg.cn (股票) ---
|
||
|
||
def parse_gtimg_response(raw: str) -> dict[str, Any] | None:
|
||
"""解析 qt.gtimg.cn 返回的 ~ 分隔数据。"""
|
||
try:
|
||
eq_pos = raw.find("=")
|
||
if eq_pos == -1:
|
||
return None
|
||
content = raw[eq_pos + 1:].strip().strip('"').strip(";")
|
||
fields = content.split("~")
|
||
if len(fields) < 34:
|
||
return None
|
||
name = fields[1] if len(fields) > 1 else ""
|
||
try:
|
||
price = float(fields[3]) if fields[3] else 0.0
|
||
except (ValueError, TypeError):
|
||
return None
|
||
try:
|
||
change = float(fields[31]) if fields[31] else 0.0
|
||
except ValueError:
|
||
change = 0.0
|
||
try:
|
||
change_pct = float(fields[32]) if fields[32] else 0.0
|
||
except ValueError:
|
||
change_pct = 0.0
|
||
try:
|
||
high = float(fields[33]) if fields[33] else price
|
||
except ValueError:
|
||
high = price
|
||
try:
|
||
low = float(fields[34]) if fields[34] else price
|
||
except ValueError:
|
||
low = price
|
||
return {
|
||
"name": name, "price": price,
|
||
"change": change, "change_pct": change_pct,
|
||
"high": high, "low": low,
|
||
}
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def fetch_stock_price(api_code: str) -> dict[str, Any] | None:
|
||
"""从 qt.gtimg.cn 获取单只股票实时行情。"""
|
||
url = f"https://qt.gtimg.cn/q={api_code}"
|
||
try:
|
||
req = urllib.request.Request(url, headers={
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||
})
|
||
with urllib.request.urlopen(req, timeout=API_TIMEOUT) as resp:
|
||
raw = resp.read().decode("gbk")
|
||
return parse_gtimg_response(raw)
|
||
except (urllib.error.URLError, TimeoutError, OSError):
|
||
return None
|
||
|
||
|
||
# --- 统一查询 ---
|
||
|
||
def fetch_all_prices() -> dict[str, dict[str, Any]]:
|
||
"""批量获取所有10只股票的真实价格。"""
|
||
results: dict[str, dict[str, Any]] = {}
|
||
for sd in STOCK_DEFINITIONS:
|
||
api_data = fetch_stock_price(sd["api_code"])
|
||
if api_data:
|
||
results[sd["symbol"]] = {
|
||
"price": api_data["price"],
|
||
"change": api_data.get("change", 0),
|
||
"change_pct": api_data.get("change_pct", 0),
|
||
"high": api_data.get("high", api_data["price"]),
|
||
"low": api_data.get("low", api_data["price"]),
|
||
}
|
||
else:
|
||
results[sd["symbol"]] = {"price": None, "error": True}
|
||
return results
|
||
|
||
|
||
# ====================================================================
|
||
# 数据加载 / 保存
|
||
# ====================================================================
|
||
|
||
def load_game_data() -> dict[str, Any]:
|
||
"""加载股票游戏数据(持仓和交易记录)。"""
|
||
if os.path.exists(GUPIAO_FILE):
|
||
try:
|
||
with open(GUPIAO_FILE, encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except (json.JSONDecodeError, OSError):
|
||
pass
|
||
return {"portfolios": {}}
|
||
|
||
|
||
def save_game_data(data: dict[str, Any]) -> None:
|
||
"""保存股票游戏数据。"""
|
||
with open(GUPIAO_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
|
||
def load_qiandao_data() -> dict[str, Any]:
|
||
"""加载签到数据。"""
|
||
if os.path.exists(QIANDAN_FILE):
|
||
try:
|
||
with open(QIANDAN_FILE, encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except (json.JSONDecodeError, OSError):
|
||
pass
|
||
return {"users": {}}
|
||
|
||
|
||
def save_qiandao_data(data: dict[str, Any]) -> None:
|
||
"""保存签到数据。"""
|
||
with open(QIANDAN_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
|
||
# ====================================================================
|
||
# 签到积分操作
|
||
# ====================================================================
|
||
|
||
def get_user_cash(user_id: str) -> float:
|
||
"""获取用户的可用现金。"""
|
||
qd = load_qiandao_data()
|
||
user = qd.get("users", {}).get(user_id)
|
||
if user is None:
|
||
return 0.0
|
||
return float(user.get("total_points", 0))
|
||
|
||
|
||
def deduct_cash(user_id: str, amount: float) -> bool:
|
||
"""从签到积分扣除金额(买入)。"""
|
||
qd = load_qiandao_data()
|
||
user = qd.get("users", {}).get(user_id)
|
||
if user is None:
|
||
return False
|
||
current = float(user.get("total_points", 0))
|
||
if current < amount - 0.001:
|
||
return False
|
||
user["total_points"] = round(current - amount, 2)
|
||
save_qiandao_data(qd)
|
||
return True
|
||
|
||
|
||
def add_cash(user_id: str, amount: float) -> None:
|
||
"""向签到积分增加金额(卖出所得)。"""
|
||
qd = load_qiandao_data()
|
||
user = qd.get("users", {}).get(user_id)
|
||
if user is None:
|
||
return
|
||
current = float(user.get("total_points", 0))
|
||
user["total_points"] = round(current + amount, 2)
|
||
save_qiandao_data(qd)
|
||
|
||
|
||
# ====================================================================
|
||
# 核心业务函数
|
||
# ====================================================================
|
||
|
||
def resolve_symbol(text: str) -> dict[str, Any] | None:
|
||
"""将用户输入转换为股票定义。支持「苹果」「苹果股票」等格式。"""
|
||
key = text.strip().lower().replace("股票", "").replace(" ", "")
|
||
return SYMBOLS_LOWER.get(key)
|
||
|
||
|
||
def parse_symbol_shares(combined: str) -> tuple[str | None, int | None]:
|
||
"""解析合并格式,如「苹果股票13股」→ (苹果, 13)。"""
|
||
raw = combined.strip()
|
||
# 提取尾部数字, support 13 or 13股
|
||
m = re.search(r'(\d+)(?:股)?$', raw)
|
||
if not m:
|
||
return None, None
|
||
shares = int(m.group(1))
|
||
sym_raw = raw[:m.start()].strip().replace("股票", "").replace(" ", "")
|
||
if sym_raw:
|
||
return sym_raw, shares
|
||
return None, None
|
||
|
||
|
||
def stock_display_name(sd: dict[str, Any]) -> str:
|
||
"""返回显示名,如「腾讯股票」「比特币股票」。"""
|
||
return f"{sd['symbol']}股票"
|
||
|
||
|
||
def market(user_id: str, nickname: str) -> str:
|
||
"""查看行情。"""
|
||
prices = fetch_all_prices()
|
||
lines = ["📈 股票行情", "━" * 22]
|
||
|
||
for sd in STOCK_DEFINITIONS:
|
||
sym = sd["symbol"]
|
||
info = prices.get(sym, {})
|
||
if info.get("error") or info.get("price") is None:
|
||
lines.append(f"⚠️ 【{stock_display_name(sd)}】行情获取失败...")
|
||
continue
|
||
|
||
price = info["price"]
|
||
change = info["change"]
|
||
arrow = "📈" if change >= 0 else "📉"
|
||
pct_abs = abs(info["change_pct"])
|
||
vol = "高" if pct_abs >= 2 else ("中" if pct_abs >= 0.5 else "低")
|
||
lines.append(
|
||
f"{arrow} 【{stock_display_name(sd)}】{price:.2f} (波动:{vol})"
|
||
)
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
def buy(user_id: str, nickname: str, symbol: str, shares: int) -> str:
|
||
"""买入股票。"""
|
||
if shares < MIN_TRANSACTION_SHARES:
|
||
return f"❌ 最少买入 {MIN_TRANSACTION_SHARES} 股"
|
||
|
||
sd = resolve_symbol(symbol)
|
||
if not sd:
|
||
return f"❌ 未知股票「{symbol}」,支持的:{', '.join(s['symbol'] for s in STOCK_DEFINITIONS)}"
|
||
|
||
# 获取实时价格
|
||
api_data = fetch_stock_price(sd["api_code"])
|
||
if not api_data:
|
||
return f"❌ 获取 {stock_display_name(sd)} 实时行情失败,请稍后再试"
|
||
price = api_data["price"]
|
||
|
||
total_cost = round(price * shares, 2)
|
||
fee = round(total_cost * TRANSACTION_FEE_RATE, 2)
|
||
total_need = round(total_cost + fee, 2)
|
||
fee_pct = TRANSACTION_FEE_RATE * 100
|
||
|
||
cash = get_user_cash(user_id)
|
||
if cash < total_need:
|
||
return (
|
||
f"❌ 现金不足!\n"
|
||
f" 💰 需要:{total_need:.2f} 积分(含手续费 {fee:.2f})\n"
|
||
f" 💵 持有:{cash:.2f} 积分\n"
|
||
f" 💡 还差 {total_need - cash:.2f} 积分,去签到赚积分吧!"
|
||
)
|
||
|
||
if not deduct_cash(user_id, total_need):
|
||
return "❌ 扣款失败,请重试"
|
||
data = load_game_data()
|
||
portfolio = data.setdefault("portfolios", {}).setdefault(
|
||
user_id, {"holdings": {}, "transactions": []}
|
||
)
|
||
holding = portfolio["holdings"].setdefault(sd["symbol"], {"shares": 0, "avg_cost": 0.0})
|
||
total_shares = holding["shares"] + shares
|
||
holding["avg_cost"] = round(
|
||
(holding["avg_cost"] * holding["shares"] + price * shares) / total_shares, 2
|
||
)
|
||
holding["shares"] = total_shares
|
||
portfolio["transactions"].append({
|
||
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||
"type": "BUY",
|
||
"symbol": sd["symbol"],
|
||
"shares": shares,
|
||
"price": price,
|
||
"fee": fee,
|
||
"total": total_need,
|
||
})
|
||
save_game_data(data)
|
||
|
||
return (
|
||
f"@{nickname} ✅ 买入 {stock_display_name(sd)} {shares}股\n"
|
||
f"💰 成交价:{price:.2f}\n"
|
||
f"💵 成本:{total_cost:.2f}\n"
|
||
f"💸 手续费:{fee:.2f}({fee_pct}%)\n"
|
||
f"💡 卖出时还需{fee_pct}%手续费+利润税"
|
||
)
|
||
|
||
|
||
def sell(user_id: str, nickname: str, symbol: str, shares: int) -> str:
|
||
"""卖出股票。"""
|
||
if shares < MIN_TRANSACTION_SHARES:
|
||
return f"❌ 最少卖出 {MIN_TRANSACTION_SHARES} 股"
|
||
|
||
sd = resolve_symbol(symbol)
|
||
if not sd:
|
||
return f"❌ 未知股票「{symbol}」,支持的:{', '.join(s['symbol'] for s in STOCK_DEFINITIONS)}"
|
||
|
||
data = load_game_data()
|
||
portfolio = data.get("portfolios", {}).get(user_id)
|
||
if not portfolio:
|
||
return "❌ 你还没有持仓哦,先买入一些股票吧!"
|
||
|
||
holding = portfolio["holdings"].get(sd["symbol"])
|
||
if not holding or holding["shares"] < shares:
|
||
return f"❌ 持仓不足!当前持有 {stock_display_name(sd)}:{holding['shares'] if holding else 0} 股"
|
||
|
||
is_liquidation = holding["shares"] == shares
|
||
|
||
# 获取实时价格
|
||
api_data = fetch_stock_price(sd["api_code"])
|
||
if not api_data:
|
||
return f"❌ 获取 {stock_display_name(sd)} 实时行情失败,请稍后再试"
|
||
sell_price = api_data["price"]
|
||
buy_price = holding["avg_cost"]
|
||
|
||
total_income = round(sell_price * shares, 2)
|
||
fee = round(total_income * TRANSACTION_FEE_RATE, 2)
|
||
tax = round(total_income * TAX_RATE, 2)
|
||
net_income = round(total_income - fee - tax, 2)
|
||
|
||
cost_basis = round(buy_price * shares, 2)
|
||
profit = round(net_income - cost_basis, 2)
|
||
|
||
add_cash(user_id, net_income)
|
||
holding["shares"] -= shares
|
||
if holding["shares"] <= 0:
|
||
del portfolio["holdings"][sd["symbol"]]
|
||
|
||
portfolio["transactions"].append({
|
||
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||
"type": "SELL",
|
||
"symbol": sd["symbol"],
|
||
"shares": shares,
|
||
"price": sell_price,
|
||
"fee": fee,
|
||
"tax": tax,
|
||
"total": net_income,
|
||
})
|
||
save_game_data(data)
|
||
|
||
action = "清仓" if is_liquidation else "卖出"
|
||
profit_emoji = "🟢" if profit >= 0 else "🔴"
|
||
return (
|
||
f"@{nickname} {action} {stock_display_name(sd)} {shares}股\n"
|
||
f"买入价格:{buy_price:.2f}\n"
|
||
f"抛售价格:{sell_price:.2f}\n"
|
||
f"💵 毛收入:{total_income:.2f}\n"
|
||
f"💸 手续费:{fee:.2f}\n"
|
||
f"📄 税费:{tax:.2f}\n"
|
||
f"📥 实际到账:{net_income:.2f}\n"
|
||
f"{profit_emoji} 总盈亏:{profit:+.2f}\n"
|
||
f"💰 资金已到账!"
|
||
)
|
||
|
||
|
||
def portfolio(user_id: str, nickname: str) -> str:
|
||
"""查看我的持仓。"""
|
||
data = load_game_data()
|
||
portfolio_data = data.get("portfolios", {}).get(user_id)
|
||
|
||
if not portfolio_data or not portfolio_data.get("holdings"):
|
||
qd = load_qiandao_data()
|
||
user = qd.get("users", {}).get(user_id, {})
|
||
name = user.get("nickname", nickname)
|
||
return (
|
||
f"📭 {name} 还没有持仓\n"
|
||
f"💡 发送 买入股票 名称 数量 来购买"
|
||
)
|
||
|
||
prices = fetch_all_prices()
|
||
lines = [f"📊 {nickname} 的持仓", "━" * 22]
|
||
|
||
total_market_value = 0.0
|
||
total_cost = 0.0
|
||
|
||
for sym, holding in portfolio_data["holdings"].items():
|
||
sd = SYMBOLS_LOWER.get(sym.lower())
|
||
if not sd:
|
||
continue
|
||
info = prices.get(sym, {})
|
||
price = info.get("price")
|
||
if price is None:
|
||
lines.append(f"⚠️ 【{stock_display_name(sd)}】行情获取失败...")
|
||
continue
|
||
|
||
shares = holding["shares"]
|
||
market_value = round(price * shares, 2)
|
||
cost = round(holding["avg_cost"] * shares, 2)
|
||
profit = round(market_value - cost, 2)
|
||
profit_pct = round((profit / cost) * 100, 2) if cost > 0 else 0
|
||
|
||
total_market_value += market_value
|
||
total_cost += cost
|
||
|
||
emoji = "📈" if profit >= 0 else "📉"
|
||
lines.append(
|
||
f"{emoji} 【{stock_display_name(sd)}】{shares}股\n"
|
||
f" 均价:{holding['avg_cost']:.2f} | 现价:{price:.2f}\n"
|
||
f" 市值:{market_value:.2f} | {emoji} {profit:+.2f}({profit_pct:+.1f}%)"
|
||
)
|
||
|
||
lines.append("━" * 22)
|
||
total_profit = round(total_market_value - total_cost, 2)
|
||
total_profit_pct = round((total_profit / (total_cost or 1)) * 100, 1)
|
||
lines.append(f"💎 总市值:{total_market_value:.2f}(投入{total_cost:.2f})")
|
||
emoji = "📈" if total_profit >= 0 else "📉"
|
||
lines.append(f"{emoji} 总盈亏:{total_profit:+.2f}({total_profit_pct:.1f}%)")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def rank(user_id: str, nickname: str, top_n: int = 10) -> str:
|
||
"""股市排行榜。"""
|
||
data = load_game_data()
|
||
prices = fetch_all_prices()
|
||
qd = load_qiandao_data()
|
||
qd_users = qd.get("users", {})
|
||
game_ports = data.get("portfolios", {})
|
||
|
||
user_list: list[tuple[str, float, float, float, str]] = []
|
||
|
||
for uid, quser in qd_users.items():
|
||
name = quser.get("nickname", uid)
|
||
holdings_value = 0.0
|
||
total_cost = 0.0
|
||
port = game_ports.get(uid, {}).get("holdings", {})
|
||
for sym, h in port.items():
|
||
info = prices.get(sym, {})
|
||
price = info.get("price")
|
||
if price is not None:
|
||
holdings_value += price * h["shares"]
|
||
total_cost += h["avg_cost"] * h["shares"]
|
||
if holdings_value > 0:
|
||
profit = round(holdings_value - total_cost, 2)
|
||
profit_pct = round((profit / (total_cost or 1)) * 100, 1)
|
||
user_list.append((uid, round(holdings_value, 2), profit, profit_pct, name))
|
||
|
||
user_list.sort(key=lambda x: x[1], reverse=True)
|
||
|
||
if not user_list:
|
||
return "📭 还没有人进行股票交易,快来成为第一个股神吧!"
|
||
|
||
lines = ["🏆 股市排行榜", "━" * 22]
|
||
for rank_i, (uid, mv, profit, pct, name) in enumerate(user_list[:top_n], 1):
|
||
medal = {1: "🥇", 2: "🥈", 3: "🥉"}.get(rank_i, f"#{rank_i}")
|
||
profit_str = f"+{profit}" if profit >= 0 else f"{profit}"
|
||
lines.append(
|
||
f"{medal} {name}\n"
|
||
f" 市值:{mv:.1f} | 盈亏:{profit_str} ({pct:.1f}%)"
|
||
)
|
||
|
||
lines.append("━" * 22)
|
||
lines.append("💡 发送 买入股票 名称 数量 来购买")
|
||
lines.append("💡 发送 我的持仓 查看持股")
|
||
lines.append("💡 发送 股市排行榜 查看股神榜")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def history(user_id: str, nickname: str, limit: int = 10) -> str:
|
||
"""查看交易记录。"""
|
||
data = load_game_data()
|
||
port = data.get("portfolios", {}).get(user_id)
|
||
if not port or not port.get("transactions"):
|
||
return f"📭 {nickname} 还没有交易记录"
|
||
|
||
txns = port["transactions"][-limit:]
|
||
lines = [f"📋 {nickname} 的交易记录(最近{len(txns)}条)", "─" * 28]
|
||
for txn in reversed(txns):
|
||
emoji = "🟢" if txn["type"] == "BUY" else "🔴"
|
||
lines.append(
|
||
f"{emoji} [{txn['time']}] {txn['type']} {txn['symbol']}\n"
|
||
f" 数量 {txn['shares']} 股 × {txn['price']:.2f} | 手续费 {txn['fee']:.2f}"
|
||
)
|
||
return "\n".join(lines)
|
||
|
||
|
||
def reset_game(user_id: str) -> str:
|
||
"""重置用户股票数据。"""
|
||
data = load_game_data()
|
||
if user_id in data.get("portfolios", {}):
|
||
del data["portfolios"][user_id]
|
||
save_game_data(data)
|
||
return f"✅ 已重置用户 {user_id} 的股票数据"
|
||
return f"❌ 用户 {user_id} 没有股票数据"
|
||
|
||
|
||
# ====================================================================
|
||
# CLI 主入口
|
||
# ====================================================================
|
||
|
||
def main() -> int:
|
||
"""CLI 入口。"""
|
||
args = sys.argv[1:]
|
||
if not args:
|
||
print("用法: python gupiao.py <命令> [参数...]")
|
||
print("命令: market, buy, sell, portfolio, rank, history, reset")
|
||
return 1
|
||
|
||
cmd = args[0]
|
||
|
||
if cmd == "market":
|
||
uid = args[1] if len(args) > 1 else "unknown"
|
||
nickname = args[2] if len(args) > 2 else uid
|
||
print(market(uid, nickname))
|
||
|
||
elif cmd in ("buy", "sell"):
|
||
if len(args) < 3:
|
||
print(f"用法: python gupiao.py {cmd} <user_id> <名称> <数量> [nickname]")
|
||
return 1
|
||
uid = args[1]
|
||
|
||
# 判断 args[2] 是否为合并格式(含数字),如「苹果股票13股」
|
||
if re.search(r'\d', args[2]):
|
||
sym_raw, shares_num = parse_symbol_shares(args[2])
|
||
if sym_raw is None or shares_num is None:
|
||
print("❌ 格式错误,示例: buy 腾讯 10 或 buy 腾讯股票10股")
|
||
return 1
|
||
symbol = sym_raw
|
||
shares = shares_num
|
||
nickname = args[3] if len(args) > 3 else uid
|
||
else:
|
||
if len(args) < 4:
|
||
print(f"用法: python gupiao.py {cmd} <user_id> <名称> <数量> [nickname]")
|
||
return 1
|
||
symbol = args[2]
|
||
try:
|
||
shares = int(args[3])
|
||
except ValueError:
|
||
print("❌ 数量必须是整数")
|
||
return 1
|
||
nickname = args[4] if len(args) > 4 else uid
|
||
|
||
if cmd == "buy":
|
||
print(buy(uid, nickname, symbol, shares))
|
||
else:
|
||
print(sell(uid, nickname, symbol, shares))
|
||
|
||
elif cmd == "portfolio":
|
||
uid = args[1] if len(args) > 1 else "unknown"
|
||
nickname = args[2] if len(args) > 2 else uid
|
||
print(portfolio(uid, nickname))
|
||
|
||
elif cmd == "rank":
|
||
top_n = int(args[1]) if len(args) > 1 else 10
|
||
print(rank("", "", top_n))
|
||
|
||
elif cmd == "history":
|
||
uid = args[1] if len(args) > 1 else "unknown"
|
||
limit = int(args[2]) if len(args) > 2 and args[2].isdigit() else 10
|
||
nickname = args[3] if len(args) > 3 else uid
|
||
print(history(uid, nickname, limit))
|
||
|
||
elif cmd == "reset":
|
||
if len(args) < 2:
|
||
print("请提供 user_id")
|
||
return 1
|
||
print(reset_game(args[1]))
|
||
|
||
else:
|
||
print(f"未知命令: {cmd}")
|
||
return 1
|
||
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
raise SystemExit(main())
|
||
except SystemExit:
|
||
raise
|
||
except Exception:
|
||
traceback.print_exc(file=sys.stdout)
|
||
raise SystemExit(1)
|