mirror of
https://github.com/mikumifa/biliTickerBuy.git
synced 2026-07-15 14:22:44 -04:00
217 lines
6.1 KiB
Python
217 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
import requests
|
|
|
|
|
|
REQUIRED_FIELDS = (
|
|
"detail",
|
|
"count",
|
|
"screen_id",
|
|
"project_id",
|
|
"sku_id",
|
|
"pay_money",
|
|
"buyer_info",
|
|
"buyer",
|
|
"tel",
|
|
"deliver_info",
|
|
"cookies",
|
|
)
|
|
BUYER_REQUIRED_FIELDS = ("name", "personal_id")
|
|
DELIVER_REQUIRED_FIELDS = ("name", "tel", "addr_id", "addr")
|
|
COOKIE_REQUIRED_FIELDS = ("name", "value")
|
|
SALES_FLAG_NUMBER_MAP = {
|
|
1: "不可售",
|
|
2: "预售",
|
|
3: "停售",
|
|
4: "售罄",
|
|
5: "不可用",
|
|
6: "库存紧张",
|
|
8: "暂时售罄",
|
|
9: "不在白名单",
|
|
101: "未开始",
|
|
102: "已结束",
|
|
103: "未完成",
|
|
105: "下架",
|
|
106: "已取消",
|
|
}
|
|
|
|
|
|
def _load_json_file(path: str | Path) -> Any:
|
|
with open(path, "r", encoding="utf-8") as handle:
|
|
return json.load(handle)
|
|
|
|
|
|
def _read_tinydb_value(path: str | Path, key: str) -> Any:
|
|
target = Path(path)
|
|
if not target.exists():
|
|
return None
|
|
try:
|
|
raw = _load_json_file(target)
|
|
except Exception:
|
|
return None
|
|
if isinstance(raw, dict):
|
|
default_group = raw.get("_default")
|
|
if isinstance(default_group, dict):
|
|
for item in default_group.values():
|
|
if isinstance(item, dict) and item.get("key") == key:
|
|
return item.get("value")
|
|
if raw.get("key") == key:
|
|
return raw.get("value")
|
|
if isinstance(raw, list):
|
|
for item in raw:
|
|
if isinstance(item, dict) and item.get("key") == key:
|
|
return item.get("value")
|
|
return None
|
|
|
|
|
|
def _cookie_store_path(cookies_path: str | Path | None) -> str | None:
|
|
if cookies_path is not None:
|
|
return str(Path(cookies_path))
|
|
|
|
package_root = Path(__file__).resolve().parents[1]
|
|
config_path = package_root / "config.json"
|
|
configured = _read_tinydb_value(config_path, "cookies_path")
|
|
if configured:
|
|
return str(Path(configured))
|
|
return str(package_root / "cookies.json")
|
|
|
|
|
|
def _coerce_cookie_store(raw: Any) -> list[dict[str, Any]] | None:
|
|
if raw is None:
|
|
return None
|
|
if isinstance(raw, list):
|
|
return copy.deepcopy(raw)
|
|
if isinstance(raw, dict):
|
|
if isinstance(raw.get("cookie"), list):
|
|
return copy.deepcopy(raw["cookie"])
|
|
default_group = raw.get("_default")
|
|
if isinstance(default_group, dict):
|
|
for item in default_group.values():
|
|
if isinstance(item, dict) and item.get("key") == "cookie":
|
|
value = item.get("value")
|
|
if isinstance(value, list):
|
|
return copy.deepcopy(value)
|
|
return None
|
|
|
|
|
|
def _resolve_cookie_list(
|
|
cookies: list[dict[str, Any]] | dict[str, Any] | None = None,
|
|
*,
|
|
cookies_path: str | Path | None = None,
|
|
) -> list[dict[str, Any]] | None:
|
|
if cookies is not None:
|
|
return _coerce_cookie_store(cookies)
|
|
|
|
store_path = _cookie_store_path(cookies_path)
|
|
if not store_path:
|
|
return None
|
|
try:
|
|
return _coerce_cookie_store(_load_json_file(store_path))
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def _cookies_to_header(cookies: list[dict[str, Any]] | None) -> str:
|
|
if not cookies:
|
|
return ""
|
|
parts: list[str] = []
|
|
for cookie in cookies:
|
|
name = cookie.get("name")
|
|
value = cookie.get("value")
|
|
if name and value is not None:
|
|
parts.append("{0}={1}".format(name, value))
|
|
return "; ".join(parts)
|
|
|
|
|
|
def _fetch_username_silently(
|
|
cookies: list[dict[str, Any]] | None,
|
|
*,
|
|
timeout: float = 10.0,
|
|
) -> str:
|
|
if not cookies:
|
|
return "Not login"
|
|
headers = {
|
|
"accept": "*/*",
|
|
"accept-language": "zh-CN,zh;q=0.9",
|
|
"referer": "https://show.bilibili.com/",
|
|
"user-agent": (
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
"Chrome/126.0.0.0 Safari/537.36"
|
|
),
|
|
"cookie": _cookies_to_header(cookies),
|
|
}
|
|
try:
|
|
response = requests.get(
|
|
"https://api.bilibili.com/x/web-interface/nav",
|
|
headers=headers,
|
|
timeout=timeout,
|
|
)
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
data = payload.get("data") or {}
|
|
username = data.get("uname")
|
|
if isinstance(username, str) and username.strip():
|
|
return username.strip()
|
|
except Exception:
|
|
return "Not login"
|
|
return "Not login"
|
|
|
|
|
|
def _deepcopy_dict(data: Any) -> dict[str, Any]:
|
|
if isinstance(data, dict):
|
|
return copy.deepcopy(data)
|
|
raise TypeError("config must be a dict or a json file path")
|
|
|
|
|
|
def _load_config(config_or_path: str | Path | dict[str, Any]) -> dict[str, Any]:
|
|
if isinstance(config_or_path, (str, Path)):
|
|
return _deepcopy_dict(_load_json_file(config_or_path))
|
|
return _deepcopy_dict(config_or_path)
|
|
|
|
|
|
def _extract_project_id(project_input: str | int) -> int:
|
|
if isinstance(project_input, int):
|
|
return project_input
|
|
|
|
text = str(project_input).strip()
|
|
if not text:
|
|
raise ValueError("project_input is empty")
|
|
if text.isdigit():
|
|
return int(text)
|
|
|
|
parsed = urlparse(text)
|
|
query = parse_qs(parsed.query)
|
|
project_ids = query.get("id", [])
|
|
if project_ids and project_ids[0].isdigit():
|
|
return int(project_ids[0])
|
|
raise ValueError("could not extract project id from input")
|
|
|
|
|
|
def _format_sale_status(ticket: dict[str, Any]) -> str:
|
|
sale_flag_number = ticket.get("sale_flag_number")
|
|
if sale_flag_number in SALES_FLAG_NUMBER_MAP:
|
|
return SALES_FLAG_NUMBER_MAP[sale_flag_number]
|
|
if "clickable" in ticket:
|
|
return "可购买" if ticket.get("clickable") else "不可购买"
|
|
return "未知状态"
|
|
|
|
|
|
def _make_request(
|
|
*,
|
|
cookies: list[dict[str, Any]] | dict[str, Any] | None = None,
|
|
cookies_path: str | Path | None = None,
|
|
) -> Any:
|
|
from util.request.BiliRequest import BiliRequest
|
|
|
|
return BiliRequest(
|
|
cookies=cookies,
|
|
cookies_config_path=_cookie_store_path(cookies_path),
|
|
)
|