Files

169 lines
6.1 KiB
Python
Raw Permalink Normal View History

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