169 lines
6.1 KiB
Python
169 lines
6.1 KiB
Python
import asyncio
|
||||
|
|
import time
|
|||
|
|
from typing import Any, Awaitable, Callable
|
|||
|
|
|
|||
|
|
import httpx
|
|||
|
|
from nonebot import logger
|
|||
|
|
|
|||
|
|
from .config import config
|
|||
|
|
|
|||
|
|
# 简单的进程内缓存 {key: (过期时间戳, 数据)},用于聚合战况时只打一次接口
|
|||
|
|
_cache: dict[str, tuple[float, Any]] = {}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _headers(language: str | None = None) -> dict[str, str]:
|
|||
|
|
# 两个 X-Super-* 头必须都存在,否则公共实例(ValidateClients)会直接返回 400
|
|||
|
|
lang = language or config.hd2_api_language
|
|||
|
|
return {
|
|||
|
|
"Accept-Language": f"{lang},en-US;q=0.5",
|
|||
|
|
"X-Super-Client": config.hd2_super_client,
|
|||
|
|
"X-Super-Contact": config.hd2_super_contact or config.hd2_super_client,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _base_url() -> str:
|
|||
|
|
return config.hd2_api_base_url.rstrip("/")
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _get_json(url: str, language: str | None = None) -> Any:
|
|||
|
|
async with httpx.AsyncClient(timeout=15) as client:
|
|||
|
|
resp = await client.get(url, headers=_headers(language))
|
|||
|
|
if resp.status_code != 200:
|
|||
|
|
raise httpx.HTTPStatusError(
|
|||
|
|
f"{resp.status_code} {resp.reason_phrase} for {url}\n{resp.text[:400]}",
|
|||
|
|
request=resp.request,
|
|||
|
|
response=resp,
|
|||
|
|
)
|
|||
|
|
return resp.json()
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _cached(key: str, fetcher: Callable[[], Awaitable[Any]]) -> Any:
|
|||
|
|
now = time.time()
|
|||
|
|
hit = _cache.get(key)
|
|||
|
|
if hit and hit[0] > now:
|
|||
|
|
return hit[1]
|
|||
|
|
data = await fetcher()
|
|||
|
|
_cache[key] = (now + config.hd2_cache_ttl, data)
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---- 原始接口 fetchers(不带缓存,供各 get_* 包装) ----
|
|||
|
|
|
|||
|
|
async def _fetch_war() -> Any:
|
|||
|
|
return await _get_json(f"{_base_url()}/api/v1/war")
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _fetch_major_orders() -> Any:
|
|||
|
|
return await _get_json(f"{_base_url()}/api/v1/assignments")
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _fetch_campaigns(language: str | None) -> Any:
|
|||
|
|
return await _get_json(f"{_base_url()}/api/v1/campaigns", language)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _fetch_planets(language: str | None) -> Any:
|
|||
|
|
return await _get_json(f"{_base_url()}/api/v1/planets", language)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _fetch_planet_detail(index: int, language: str | None) -> Any:
|
|||
|
|
return await _get_json(f"{_base_url()}/api/v1/planets/{index}", language)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def _fetch_raw_status() -> Any:
|
|||
|
|
# 原始接口一次返回全部:planetActiveEffects / spaceStations / planetStatus 等
|
|||
|
|
return await _get_json(f"{_base_url()}/raw/api/WarSeason/801/Status")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---- 对外封装(各自带缓存,可单独调用) ----
|
|||
|
|
|
|||
|
|
async def get_war() -> Any:
|
|||
|
|
"""星系战况概要(年代起止/阵营/影响倍率等)。"""
|
|||
|
|
return await _cached("war", _fetch_war)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_major_orders() -> Any:
|
|||
|
|
"""重要指令(最高指令 / assignments,含任务目标、奖励、阵营)。"""
|
|||
|
|
return await _cached("assignments", _fetch_major_orders)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_campaigns(language: str | None = None) -> Any:
|
|||
|
|
"""当前参与战役的星球列表(含每颗参战星球的进度/事件/危害等)。"""
|
|||
|
|
key = f"campaigns:{language or ''}"
|
|||
|
|
return await _cached(key, lambda: _fetch_campaigns(language))
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_planets(language: str | None = None) -> Any:
|
|||
|
|
"""全星球表(每颗星的 biome/hazards/event/currentOwner/statistics 等)。"""
|
|||
|
|
key = f"planets:{language or ''}"
|
|||
|
|
return await _cached(key, lambda: _fetch_planets(language))
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_planet_detail(index: int, language: str | None = None) -> Any:
|
|||
|
|
"""单星球详细情况(biome/hazards/event/statistics/attacking/regions 等)。"""
|
|||
|
|
key = f"planet:{index}:{language or ''}"
|
|||
|
|
return await _cached(key, lambda: _fetch_planet_detail(index, language))
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_raw_status() -> Any:
|
|||
|
|
"""原始 WarSeason/801/Status(planetStatus/planetActiveEffects/spaceStations...)。"""
|
|||
|
|
return await _cached("raw_status", _fetch_raw_status)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_space_stations() -> list[Any]:
|
|||
|
|
"""空间站(民主空间站 DSS 等)。"""
|
|||
|
|
raw = await get_raw_status()
|
|||
|
|
return list((raw or {}).get("spaceStations") or [])
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def get_planet_effects(index: int | None = None) -> dict[int, list[int]]:
|
|||
|
|
"""星球当前生效效果 {星球index: [galacticEffectId...]};传 index 时只返回该星球的集合。"""
|
|||
|
|
raw = await get_raw_status()
|
|||
|
|
effects: dict[int, list[int]] = {}
|
|||
|
|
for item in (raw or {}).get("planetActiveEffects") or []:
|
|||
|
|
if (
|
|||
|
|
isinstance(item, dict)
|
|||
|
|
and item.get("index") is not None
|
|||
|
|
and item.get("galacticEffectId") is not None
|
|||
|
|
):
|
|||
|
|
effects.setdefault(item["index"], []).append(item["galacticEffectId"])
|
|||
|
|
if index is not None:
|
|||
|
|
return {index: effects.get(index, [])}
|
|||
|
|
return effects
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---- 聚合入口(渲染战报用;也可改为按需调用各 get_* 组装) ----
|
|||
|
|
|
|||
|
|
async def get_briefing_data() -> dict[str, Any]:
|
|||
|
|
"""并发拉取战报所需的分组数据,带缓存,返回聚合结果。"""
|
|||
|
|
war, assignments, campaigns, planets, raw_status = await asyncio.gather(
|
|||
|
|
get_war(),
|
|||
|
|
get_major_orders(),
|
|||
|
|
get_campaigns(),
|
|||
|
|
get_planets(language="en-US"), # 用英文星名匹配高清地形图;currentOwner 等不受语言影响
|
|||
|
|
get_raw_status(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
logger.info("已获取 helldivers 战况数据")
|
|||
|
|
planet_english = {
|
|||
|
|
p["index"]: p.get("name")
|
|||
|
|
for p in (planets or [])
|
|||
|
|
if p.get("index") is not None and p.get("name")
|
|||
|
|
}
|
|||
|
|
# 星球当前效果:{星球index: [galacticEffectId...]}
|
|||
|
|
planet_effects: dict[int, list[int]] = {}
|
|||
|
|
for item in (raw_status or {}).get("planetActiveEffects") or []:
|
|||
|
|
if isinstance(item, dict) and item.get("index") is not None and item.get("galacticEffectId") is not None:
|
|||
|
|
planet_effects.setdefault(item["index"], []).append(item["galacticEffectId"])
|
|||
|
|
# 空间站(民主空间站 DSS 等):独立展示,不混进普通星球效果
|
|||
|
|
space_stations = (raw_status or {}).get("spaceStations") or []
|
|||
|
|
return {
|
|||
|
|
"war": war,
|
|||
|
|
"assignments": assignments,
|
|||
|
|
"campaigns": campaigns,
|
|||
|
|
"planets": planets,
|
|||
|
|
"planet_english": planet_english,
|
|||
|
|
"planet_effects": planet_effects,
|
|||
|
|
"space_stations": space_stations,
|
|||
|
|
"fetched_at": int(time.time()),
|
|||
|
|
}
|