align API response format with maqt.top real specs: weapon-categories, weapons, schemes, schemes_aob, favorites check, encryption key derivation

This commit is contained in:
2026-05-24 01:47:08 +08:00
parent ea4e0f6e07
commit 8bcb6c7e7a
13 changed files with 1861 additions and 366 deletions

316
origet/api_spec.py Normal file
View File

@@ -0,0 +1,316 @@
"""
码枪堂 原版 (maqt.top) API 接口规格
来源: app.asar v7.0.4, dist/weapon-tuner.html + dist/assets/CfKCgw5l.js
"""
import hashlib
import base64
import json
from dataclasses import dataclass, field
from typing import Optional, Any
BASE_URL = "https://maqt.top"
ENCRYPTION_KEY = "maqt-delta-force-2024-secret-key-32"
AES_KEY = hashlib.sha256(ENCRYPTION_KEY.encode("utf-8")).digest() # 32 bytes
CATEGORY_MAP = {
"AR": "突击步枪",
"SMG": "冲锋枪",
"SR": "狙击步枪",
"LMG": "轻机枪",
"SG": "霰弹枪",
"Pistol": "手枪",
"Launcher": "发射器",
}
# ---- Data Models ----
@dataclass
class WeaponCategory:
category: str # 实际返回中文名 e.g. "突击步枪"
scheme_count: int = 0
@dataclass
class Weapon:
id: int
weapon_name: str # e.g. "M4A1"
display_name: str # e.g. "M4A1突击步枪"
category: str # 中文分类名
use_count: int = 0
@dataclass
class SchemeItem:
id: int
user_id: int
username: str
avatar: str
description: str
scheme_content: str # 含武器名+模式前缀,如 "M4A1突击步枪-烽火地带-..."
category: str
weapon_name: str
price: Optional[str] # 实际是字符串 e.g. "97W"
tags: list[str]
uses: int
total_historical_uses: int
status: Optional[str]
source: Optional[int] # 1:自行 2:别人分享 3:工具生成
created_at: str
updated_at: str
# 合作方字段
partner_type: Optional[str] # "club" | "none"
partner_level: Optional[str] # "bronze" | "gold" | "none"
partner_badge: Optional[str]
partner_logo: Optional[str]
social_link: Optional[str]
@dataclass
class Pagination:
page: int
limit: int
hasMore: bool
@dataclass
class SchemeListResponse:
success: bool
data: list[SchemeItem]
pagination: Pagination
@dataclass
class AdvertItem:
id: str
author: str
avatar: str
shareTime: str
title: str
description: str
image_url: Optional[str]
link_url: str
isVip: bool
@dataclass
class UserInfo:
id: int
username: str
avatar: Optional[str] = None
email: Optional[str] = None
status: str = "active"
isVip: int = 0
vipExpireAt: Optional[str] = None
freezeUntil: Optional[str] = None
@dataclass
class LoginResponse:
success: bool
message: str
token: Optional[str] = None
user: Optional[UserInfo] = None
@dataclass
class SessionStatusResponse:
success: bool
loggedIn: bool
user: Optional[UserInfo] = None
message: Optional[str] = None
@dataclass
class VipStatusData:
success: bool
isVip: bool
vipExpireAt: Optional[str]
daysRemaining: int
activatedAt: Optional[str]
activatedCardKey: Optional[str]
expired: bool
message: Optional[str]
# ---- Endpoints ----
ENDPOINTS = {
"weapon_categories": {
"method": "GET",
"path": "/api/weapon-categories",
"auth": False,
"params": None,
"returns": "list[WeaponCategory]",
},
"weapons": {
"method": "GET",
"path": "/api/weapons",
"auth": False,
"params": {"category": "武器分类代码, optional"},
"returns": "list[Weapon]",
},
"schemes": {
"method": "GET",
"path": "/api/schemes",
"auth": False,
"params": {
"sort": "hot | new",
"page": "页码 (1-based)",
"limit": "固定 12",
"weaponCategory": "中文分类名, optional",
"weaponName": "武器中文名, optional",
"minPrice": "最低价格, optional",
"maxPrice": "最高价格, optional",
"search": "搜索关键词, optional",
},
"returns": "SchemeListResponse (可能加密)",
},
"schemes_aob": {
"method": "GET",
"path": "/api/schemes_aob",
"auth": False,
"params": { # 同上
"sort": "hot | new",
"page": "页码 (1-based)",
"limit": "固定 12",
},
"returns": "SchemeListResponse (可能加密)",
},
"favorites": {
"method": "GET",
"path": "/api/favorites",
"auth": True,
"params": {"sort": "", "page": "", "limit": "12"},
"returns": "SchemeListResponse",
},
"login": {
"method": "POST",
"path": "/api/login",
"auth": False,
"body": {
"username": "string",
"password": "string",
"installId": "string, optional",
"deviceHash": "string, optional",
"platform": "string, optional",
"osVersion": "string, optional",
"appVersion": "string, optional",
},
"returns": "LoginResponse",
},
"register": {
"method": "POST",
"path": "/api/register",
"auth": False,
"body": {"username": "", "password": "", "email": ""},
"returns": "LoginResponse (with token)",
},
"session_status": {
"method": "GET",
"path": "/api/session-status",
"auth": True,
"params": None,
"returns": "SessionStatusResponse",
},
"vip_status": {
"method": "GET",
"path": "/api/vip-status",
"auth": True,
"params": None,
"returns": "VipStatusData (可能加密)",
},
"activate_vip": {
"method": "POST",
"path": "/api/activate-vip",
"auth": True,
"body": {"cardKey": "VIP卡密"},
},
"favorites_add": {
"method": "POST",
"path": "/api/favorites",
"auth": True,
"body": {"schemeId": "", "source": "烽火地带"},
},
"favorites_remove": {
"method": "DELETE",
"path": "/api/favorites/{schemeId}?source={source}",
"auth": True,
},
"favorites_check": {
"method": "GET",
"path": "/api/favorites/check?schemeId={id}&source={source}",
"auth": True,
},
"favorites_count": {
"method": "GET",
"path": "/api/favorites/count",
"auth": True,
},
"scheme_use": {
"method": "POST",
"path": "/api/{mode}/{schemeId}/use",
"auth": False,
},
"scheme_report": {
"method": "POST",
"path": "/api/{mode}/{schemeId}/report",
"auth": True,
"body": {"reason": "invalid|inappropriate", "description": ""},
},
"scheme_share": {
"method": "POST",
"path": "/api/schemes", # or /api/schemes_aob
"auth": False,
"headers": {"x-user-info": "base64(json({id,username}))"},
"body": {
"description": "方案描述 <=50字",
"category": "武器分类中文名",
"weaponName": "武器中文名",
"scheme": "配置代码",
"tags": [],
"price": 15, # 烽火模式必填
},
},
"adverts_list": {
"method": "GET",
"path": "/api/adverts/list",
"auth": False,
},
"adverts_click": {
"method": "POST",
"path": "/api/adverts/{advertId}/click",
"auth": False,
},
"user_stats": {
"method": "GET",
"path": "/api/user/stats/{userId}",
"auth": False,
},
"user_schemes": {
"method": "GET",
"path": "/api/user/schemes/{userId}",
"auth": False,
},
"user_favorited_count": {
"method": "GET",
"path": "/api/user/favorited-count/{userId}",
"auth": True,
},
"filter_share_categories": {
"method": "GET",
"path": "/api/filter-share/categories",
"auth": False,
},
"filter_shares": {
"method": "GET",
"path": "/api/filter-shares",
"auth": False,
},
"filter_share_copy": {
"method": "POST",
"path": "/api/filter-shares/{id}/copy",
"auth": False,
"body": {"slot": "primary"},
},
"activity_ping": {
"method": "GET",
"path": "/api/activity/ping",
"auth": True,
},
"game_map_password": {
"method": "GET",
"path": "/api/game/map-password",
"auth": False,
},
}

211
origet/client.py Normal file
View File

@@ -0,0 +1,211 @@
"""
码枪堂原版 (maqt.top) API 客户端
用法:
from client import MaqtClient
c = MaqtClient()
c.login("username", "password")
schemes = c.get_schemes(page=1, sort="hot")
print(schemes)
"""
import json
import requests
from typing import Optional, Any
from api_spec import BASE_URL, ENDPOINTS
# ---- Unicode-safe Base64 ----
def b64_encode_unicode(s: str) -> str:
import base64
encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
return encoded
# ---- Client ----
class MaqtClient:
def __init__(self, base_url: str = BASE_URL, token: Optional[str] = None):
self.base_url = base_url
self.token = token
self.session = requests.Session()
self.session.headers["User-Agent"] = "MaqiangTang/7.0.4"
# ---- low-level helpers ----
def _auth_headers(self) -> dict:
if self.token:
return {"Authorization": f"Bearer {self.token}"}
return {}
def _get(self, path: str, params: Optional[dict] = None, auth: bool = False) -> dict:
h = self._auth_headers() if auth else {}
r = self.session.get(f"{self.base_url}{path}", params=params, headers=h, timeout=15)
r.raise_for_status()
return r.json()
def _post(self, path: str, body: dict, extra_headers: Optional[dict] = None, auth: bool = False) -> dict:
h = {"Content-Type": "application/json"}
if auth:
h.update(self._auth_headers())
if extra_headers:
h.update(extra_headers)
r = self.session.post(f"{self.base_url}{path}", json=body, headers=h, timeout=15)
r.raise_for_status()
return r.json()
def _delete(self, path: str, auth: bool = True) -> dict:
h = self._auth_headers() if auth else {}
r = self.session.delete(f"{self.base_url}{path}", headers=h, timeout=15)
r.raise_for_status()
return r.json()
# ---- auth ----
def login(self, username: str, password: str) -> dict:
resp = self._post("/api/login", {"username": username, "password": password})
if resp.get("success") and resp.get("token"):
self.token = resp["token"]
return resp
def register(self, username: str, password: str, email: str) -> dict:
return self._post("/api/register", {"username": username, "password": password, "email": email})
def session_status(self) -> dict:
return self._get("/api/session-status", auth=True)
# ---- VIP ----
def vip_status(self) -> dict:
return self._get("/api/vip-status", auth=True)
def activate_vip(self, card_key: str) -> dict:
return self._post("/api/activate-vip", {"cardKey": card_key}, auth=True)
# ---- weapon data ----
def get_weapon_categories(self) -> dict:
return self._get("/api/weapon-categories")
def get_weapons(self, category: Optional[str] = None) -> dict:
"""category 传中文名如'突击步枪',不传返回全部"""
params = {"category": category} if category else None
return self._get("/api/weapons", params=params)
# ---- schemes ----
def get_schemes(
self,
page: int = 1,
sort: str = "hot",
limit: int = 12,
mode: str = "schemes",
weapon_category: Optional[str] = None,
weapon_name: Optional[str] = None,
min_price: Optional[int] = None,
max_price: Optional[int] = None,
search: Optional[str] = None,
) -> dict:
params = {"sort": sort, "page": str(page), "limit": str(limit)}
if weapon_category:
params["weaponCategory"] = weapon_category
if weapon_name:
params["weaponName"] = weapon_name
if min_price is not None:
params["minPrice"] = str(min_price)
if max_price is not None:
params["maxPrice"] = str(max_price)
if search:
params["search"] = search
return self._get(f"/api/{mode}", params=params)
def get_favorites(self, page: int = 1, sort: str = "hot") -> dict:
return self._get("/api/favorites", params={"sort": sort, "page": str(page), "limit": "12"}, auth=True)
def record_use(self, scheme_id: str, mode: str = "schemes") -> dict:
return self._post(f"/api/{mode}/{scheme_id}/use", {})
def report_scheme(self, scheme_id: str, reason: str, description: str = "", mode: str = "schemes") -> dict:
return self._post(
f"/api/{mode}/{scheme_id}/report",
{"reason": reason, "description": description},
auth=True,
)
def share_scheme(
self,
description: str,
category: str,
weapon_name: str,
scheme: str,
price: Optional[int] = None,
user_id: int = 0,
username: str = "",
mode: str = "schemes",
) -> dict:
user_info = json.dumps({"id": user_id, "username": username}, ensure_ascii=False)
body = {
"description": description,
"category": category,
"weaponName": weapon_name,
"scheme": scheme,
"tags": [],
}
if price is not None:
body["price"] = price
return self._post(
f"/api/{mode}",
body,
extra_headers={"x-user-info": b64_encode_unicode(user_info)},
)
# ---- favorites ----
def add_favorite(self, scheme_id: str, source: str = "烽火地带") -> dict:
return self._post("/api/favorites", {"schemeId": scheme_id, "source": source}, auth=True)
def remove_favorite(self, scheme_id: str, source: str = "烽火地带") -> dict:
import urllib.parse
qs = urllib.parse.urlencode({"source": source})
return self._delete(f"/api/favorites/{scheme_id}?{qs}")
def check_favorite(self, scheme_id: str, source: str = "烽火地带") -> dict:
import urllib.parse
qs = urllib.parse.urlencode({"schemeId": scheme_id, "source": source})
return self._get(f"/api/favorites/check?{qs}", auth=True)
def favorites_count(self) -> dict:
return self._get("/api/favorites/count", auth=True)
# ---- adverts ----
def get_adverts(self) -> dict:
return self._get("/api/adverts/list")
def click_advert(self, advert_id: str) -> dict:
return self._post(f"/api/adverts/{advert_id}/click", {})
# ---- user ----
def user_stats(self, user_id: int) -> dict:
return self._get(f"/api/user/stats/{user_id}")
def user_schemes(self, user_id: int) -> dict:
return self._get(f"/api/user/schemes/{user_id}")
def user_favorited_count(self, user_id: int) -> dict:
return self._get(f"/api/user/favorited-count/{user_id}", auth=True)
# ---- misc ----
def get_filter_share_categories(self) -> dict:
return self._get("/api/filter-share/categories")
def get_filter_shares(self) -> dict:
return self._get("/api/filter-shares")
def copy_filter_share(self, share_id: str, slot: str = "primary") -> dict:
return self._post(f"/api/filter-shares/{share_id}/copy", {"slot": slot})
def activity_ping(self) -> dict:
return self._get("/api/activity/ping", auth=True)
def get_game_map_password(self) -> dict:
return self._get("/api/game/map-password")

63
origet/decrypt.py Normal file
View File

@@ -0,0 +1,63 @@
"""
码枪堂 AES-256-CBC 解密工具
加密密钥: maqt-delta-force-2024-secret-key-32
流程: KEY -> SHA-256 -> 32-byte AES key -> AES-CBC decrypt(iv, data)
"""
import hashlib
import json
from typing import Any
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
KEY_STR = "maqt-delta-force-2024-secret-key-32"
AES_KEY = hashlib.sha256(KEY_STR.encode("utf-8")).digest()
def decrypt(encrypted_obj: dict) -> dict:
"""
解密 API 响应
encrypted_obj 格式: {"encrypted": true, "iv": "hex_string", "data": "hex_string"}
返回解密后的 JSON 对象
"""
iv = bytes.fromhex(encrypted_obj["iv"])
data = bytes.fromhex(encrypted_obj["data"])
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv=iv)
decrypted = unpad(cipher.decrypt(data), AES.block_size)
return json.loads(decrypted.decode("utf-8"))
def try_decrypt(response: dict) -> dict:
"""如果响应已加密则解密,否则原样返回"""
if response.get("encrypted") is True:
return decrypt(response)
return response
def encrypt(payload: dict) -> dict:
"""
加密数据(用于复刻服务端)
返回: {"encrypted": true, "iv": "hex", "data": "hex"}
"""
from Crypto.Random import get_random_bytes
plaintext = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
iv = get_random_bytes(16)
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv=iv)
from Crypto.Util.Padding import pad
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
return {"encrypted": True, "iv": iv.hex(), "data": ciphertext.hex()}
if __name__ == "__main__":
# 测试解密
from client import MaqtClient
c = MaqtClient()
resp = c.get_schemes(page=1)
print(f"encrypted={resp.get('encrypted')}")
if resp.get("encrypted"):
result = decrypt(resp)
print(json.dumps(result, ensure_ascii=False, indent=2))

2
origet/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
requests
pycryptodome

120
origet/test_apis.py Normal file
View File

@@ -0,0 +1,120 @@
"""
原版 maqt.top API 接口测试脚本
用法:
python test_apis.py
python test_apis.py --user <username> --pass <password>
python test_apis.py --user <username> --pass <password> --output results.json
"""
import json
import sys
import argparse
from datetime import datetime
from client import MaqtClient
from decrypt import try_decrypt
OUTPUT = None # global output dict
def banner(msg: str):
print(f"\n{'='*60}")
print(f" {msg}")
print(f"{'='*60}")
def test(name: str, fn, *args, **kwargs):
print(f"\n--- {name} ---")
rv = None
err = None
try:
result = fn(*args, **kwargs)
decrypted = try_decrypt(result)
print(f" OK (keys: {list(decrypted.keys()) if isinstance(decrypted, dict) else '?'})")
if isinstance(decrypted, dict):
if "data" in decrypted and isinstance(decrypted["data"], list):
print(f" data_len={len(decrypted['data'])}")
if decrypted.get("encrypted"):
print(f" (was encrypted)")
elif isinstance(decrypted, list):
print(f" list_len={len(decrypted)}")
rv = decrypted
except Exception as e:
print(f" [ERROR] {e}")
err = str(e)
OUTPUT[name] = {"ok": rv is not None, "data": rv, "error": err}
return rv
def main():
global OUTPUT
OUTPUT = {}
parser = argparse.ArgumentParser(description="maqt.top API tester")
parser.add_argument("--user", default="sixteenth")
parser.add_argument("--pass", dest="password", default="")
parser.add_argument("--output", default=None, help="Output JSON file path")
args = parser.parse_args()
outfile = args.output or f"test_output_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
print(f"[{datetime.now().isoformat()}] 开始测试...")
print(f"输出文件: {outfile}")
c = MaqtClient()
# ---- 1. 无需认证的接口 ----
banner("1. 武器分类")
cats = test("weapon-categories", c.get_weapon_categories)
banner("2. 武器列表 (AR)")
test("weapons?category=AR", c.get_weapons, category="AR")
banner("3. 武器列表 (无参数)")
test("weapons (all)", c.get_weapons)
banner("4. 方案列表 (schemes, hot, p1)")
schemes = test("schemes?sort=hot&page=1&limit=12", c.get_schemes, page=1, sort="hot")
banner("5. 方案列表 (schemes_aob)")
test("schemes_aob", c.get_schemes, mode="schemes_aob", page=1, sort="hot")
banner("6. 广告列表")
test("adverts", c.get_adverts)
# ---- 2. 登录 ----
if args.password:
banner("7. 登录")
login_resp = test("login", c.login, args.user, args.password)
if login_resp and login_resp.get("token"):
print(f" Token: {login_resp['token'][:50]}...")
banner("8. 会话状态")
test("session-status", c.session_status)
banner("9. VIP状态")
test("vip-status", c.vip_status)
banner("10. 收藏列表")
test("favorites", c.get_favorites, page=1)
banner("11. 收藏数量")
test("favorites/count", c.favorites_count)
banner("12. 用户统计")
uid = login_resp.get("user", {}).get("id", 0)
test(f"user/stats/{uid}", c.user_stats, uid)
banner("13. 活跃Ping")
test("activity/ping", c.activity_ping)
else:
print("\n 跳过登录测试 (未提供 --pass)")
banner("DONE")
with open(outfile, "w", encoding="utf-8") as f:
json.dump(OUTPUT, f, ensure_ascii=False, indent=2, default=str)
print(f"\n结果已保存到: {outfile}")
if __name__ == "__main__":
main()

147
origet/test_local.py Normal file
View File

@@ -0,0 +1,147 @@
"""
本地 mqsrv (http://localhost:3001) 接口测试脚本
用法:
python test_local.py
python test_local.py --user sixteenth --pass "@WT65eijh10"
"""
import json
import sys
import argparse
from datetime import datetime
import requests
LOCAL_BASE = "http://localhost:3001"
# 复用 origet 的 AES 解密
import os, sys as _sys
_origet_dir = os.path.dirname(os.path.abspath(__file__))
if _origet_dir not in _sys.path:
_sys.path.insert(0, _origet_dir)
from decrypt import try_decrypt
def test(name, fn, *args, **kwargs):
print(f"\n--- {name} ---")
try:
result = fn(*args, **kwargs)
decrypted = try_decrypt(result)
if isinstance(decrypted, dict):
keys = list(decrypted.keys())
print(f" OK keys: {keys}")
if "data" in decrypted and isinstance(decrypted["data"], list):
print(f" data_len={len(decrypted['data'])}")
if decrypted["data"]:
print(f" first: {json.dumps(decrypted['data'][0], ensure_ascii=False)[:300]}")
if "pagination" in decrypted:
print(f" pagination: {decrypted['pagination']}")
elif isinstance(decrypted, list):
print(f" OK list_len={len(decrypted)}")
if decrypted:
print(f" first: {json.dumps(decrypted[0], ensure_ascii=False)[:200]}")
return decrypted
except requests.exceptions.ConnectionError:
print(f" [ERROR] 连接失败 - 请确保服务端已启动 (npm run dev)")
return None
except Exception as e:
print(f" [ERROR] {e}")
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--user", default="sixteenth")
parser.add_argument("--pass", dest="password", default="")
args = parser.parse_args()
s = requests.Session()
def get(path, params=None, auth=False, token=None):
h = {}
if auth and token:
h["Authorization"] = f"Bearer {token}"
r = s.get(f"{LOCAL_BASE}{path}", params=params, headers=h, timeout=10)
r.raise_for_status()
return r.json()
def post(path, body, auth=False, token=None, extra_headers=None):
h = {"Content-Type": "application/json"}
if auth and token:
h["Authorization"] = f"Bearer {token}"
if extra_headers:
h.update(extra_headers)
r = s.post(f"{LOCAL_BASE}{path}", json=body, headers=h, timeout=10)
r.raise_for_status()
return r.json()
def delete(path, token=None):
h = {"Authorization": f"Bearer {token}"} if token else {}
r = s.delete(f"{LOCAL_BASE}{path}", headers=h, timeout=10)
r.raise_for_status()
return r.json()
print(f"[{datetime.now().isoformat()}] 测试 {LOCAL_BASE}")
# 1. 公开接口
test("武器分类", get, "/api/weapon-categories")
test("武器列表 (全部)", get, "/api/weapons")
test("武器列表 (突击步枪)", get, "/api/weapons", params={"category": "突击步枪"})
test("方案列表 (schemes)", get, "/api/schemes", params={"sort": "hot", "page": "1", "limit": "6"})
test("方案列表 (schemes_aob)", get, "/api/schemes_aob", params={"sort": "hot", "page": "1", "limit": "6"})
test("广告列表", get, "/api/adverts/list")
# 2. 注册 + 登录
if args.password:
test("注册 (可能已存在)", post, "/api/register", body={
"username": args.user, "password": args.password, "email": f"{args.user}@test.com"
})
login_resp = test("登录", post, "/api/login", body={
"username": args.user, "password": args.password
})
token = login_resp.get("token") if login_resp else None
if token:
print(f"\n Token: {token[:50]}...")
# 3. 认证接口
test("会话状态", get, "/api/session-status", auth=True, token=token)
test("VIP状态", get, "/api/vip-status", auth=True, token=token)
# 4. 创建方案
scheme_resp = test("创建方案", post, "/api/schemes", body={
"description": "测试M4A1方案",
"category": "突击步枪",
"weaponName": "M4A1突击步枪",
"scheme": "M4A1突击步枪-烽火地带-TESTCODE123",
"price": 30,
}, auth=True, token=token,
extra_headers={
"x-user-info": "eyJpZCI6NDc5MTcsInVzZXJuYW1lIjoic2l4dGVlbnRoIn0=" # test userinfo base64
})
scheme_id = scheme_resp.get("data", {}).get("id") if scheme_resp else None
if scheme_id:
# 5. 收藏
test("添加收藏", post, "/api/favorites", body={
"schemeId": scheme_id, "source": "烽火地带"
}, auth=True, token=token)
test("检查收藏", get, "/api/favorites/check", params={
"schemeId": scheme_id, "source": "烽火地带"
}, auth=True, token=token)
test("收藏列表", get, "/api/favorites", auth=True, token=token)
test("收藏数量", get, "/api/favorites/count")
test("取消收藏", delete, f"/api/favorites/{scheme_id}?source=烽火地带", token=token)
# 6. 使用记录
test("记录使用", post, f"/api/schemes/{scheme_id}/use", body={}, token=token)
else:
print("\n 登录失败(可能未注册)")
else:
print("\n 跳过登录测试 (未提供 --pass)")
print(f"\n{'='*60}")
print(" DONE")
if __name__ == "__main__":
main()