Files
HeXi/hexi/plugins/nonebot_plugin_helldivers_tools/hd2_api.py
T

169 lines
6.1 KiB
Python
Raw 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()),
}