fxdyz
This commit is contained in:
+96
-9
@@ -1,8 +1,11 @@
|
||||
import json
|
||||
import html
|
||||
import re
|
||||
import hashlib
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List, Union
|
||||
from typing import TYPE_CHECKING, Dict, Any, Optional, List, Union
|
||||
from inspect import isawaitable
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||
|
||||
@@ -14,6 +17,9 @@ from .request import APIClient, APIErrorResponse
|
||||
from .sqlite import AsyncSQLiteDB
|
||||
from .fun_basic import load_template,gold_to_parts,week_to_num,compare_date_str,format_time,format_remaining
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .cache import CacheService
|
||||
|
||||
|
||||
ROLE_RANK_NAMES = {
|
||||
"名士五十强",
|
||||
@@ -74,25 +80,33 @@ RANK_NAMES = frozenset().union(
|
||||
|
||||
|
||||
class JX3APIService:
|
||||
def __init__(self, config: AstrBotConfig, sqlite: AsyncSQLiteDB):
|
||||
def __init__(
|
||||
self,
|
||||
config: AstrBotConfig,
|
||||
sqlite: AsyncSQLiteDB,
|
||||
cache: Optional["CacheService"] = None,
|
||||
):
|
||||
# 实例化 API Client
|
||||
self._api: APIClient = APIClient()
|
||||
# 引用插件配置文件
|
||||
self._config = config
|
||||
# 引用sqlite
|
||||
self._sql_db = sqlite
|
||||
self._cache = cache
|
||||
self._token_stats_cache: tuple[float, Dict[str, Any]] | None = None
|
||||
self._token_stats_lock = asyncio.Lock()
|
||||
# 获取配置中的 Token
|
||||
self.token = self._config.get("jx3api_token", "")
|
||||
if self.token == "":
|
||||
logger.warning("获取配置token失败,请正确填写token,否则部分功能无法正常使用")
|
||||
else:
|
||||
logger.debug(f"获取配置token成功。{self.token}")
|
||||
logger.debug(f"获取配置token成功。")
|
||||
# 获取配置中的 ticket
|
||||
self.ticket = self._config.get("jx3api_ticket", "")
|
||||
if self.ticket == "":
|
||||
logger.warning("获取配置ticket失败,请正确填写ticket,否则部分功能无法正常使用")
|
||||
else:
|
||||
logger.debug(f"获取配置ticket成功。{self.ticket}")
|
||||
logger.debug(f"获取配置ticket成功。")
|
||||
|
||||
|
||||
async def close(self):
|
||||
@@ -100,11 +114,13 @@ class JX3APIService:
|
||||
if self._api:
|
||||
await self._api.close()
|
||||
|
||||
async def server_list(self) -> list[str]:
|
||||
async def server_list(self, force_refresh: bool = False) -> list[str]:
|
||||
"""获取当前有效区服名称,供会话绑定和参数消歧使用。"""
|
||||
data = await self._base_request(
|
||||
data, _ = await self._cached_request(
|
||||
"/server/status/check",
|
||||
{"server": "", "type": "其他"},
|
||||
force_refresh=force_refresh,
|
||||
allow_stale=not force_refresh,
|
||||
)
|
||||
if not isinstance(data, list):
|
||||
return []
|
||||
@@ -117,18 +133,38 @@ class JX3APIService:
|
||||
)
|
||||
|
||||
async def token_stats(self) -> Optional[Dict[str, Any]]:
|
||||
"""读取令牌统计;短时内存复用,避免 WebUI 保存配置时重复请求。"""
|
||||
if not str(self.token or "").strip():
|
||||
return None
|
||||
now = time.monotonic()
|
||||
if self._token_stats_cache and self._token_stats_cache[0] > now:
|
||||
return dict(self._token_stats_cache[1])
|
||||
|
||||
async with self._token_stats_lock:
|
||||
now = time.monotonic()
|
||||
if self._token_stats_cache and self._token_stats_cache[0] > now:
|
||||
return dict(self._token_stats_cache[1])
|
||||
result = await self._fetch_token_stats()
|
||||
if result is not None:
|
||||
self._token_stats_cache = (now + 30, dict(result))
|
||||
return result
|
||||
|
||||
async def _fetch_token_stats(self) -> Optional[Dict[str, Any]]:
|
||||
"""查询当前配置 JX3API Token 的等级、用量及有效状态。"""
|
||||
if not str(self.token or "").strip():
|
||||
return None
|
||||
|
||||
try:
|
||||
data = await self._api.post(
|
||||
async def requester():
|
||||
return await self._api.post(
|
||||
"https://www.jx3api.com/token/stats",
|
||||
data={"token": self.token},
|
||||
out_key="data",
|
||||
success_codes=(200, "200"),
|
||||
return_error=True,
|
||||
)
|
||||
|
||||
try:
|
||||
data = await requester()
|
||||
except Exception as exc:
|
||||
logger.warning(f"查询 JX3API Token 统计失败: {exc}")
|
||||
return None
|
||||
@@ -204,6 +240,56 @@ class JX3APIService:
|
||||
logger.error(f"基础请求调用出错 ({api_path}): {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _is_cacheable_response(data: Any) -> bool:
|
||||
return data is not None and not isinstance(data, APIErrorResponse)
|
||||
|
||||
async def _cached_request(
|
||||
self,
|
||||
api_path: str,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
out: Optional[str] = "data",
|
||||
force_refresh: bool = False,
|
||||
allow_stale: bool = True,
|
||||
) -> tuple[Any, dict[str, Any]]:
|
||||
request_params = params or {}
|
||||
if not self._cache:
|
||||
return await self._base_request(api_path, request_params, out), {
|
||||
"endpoint": api_path,
|
||||
"hit": False,
|
||||
"stale": False,
|
||||
"ttl_seconds": 0,
|
||||
}
|
||||
|
||||
cache_params = dict(request_params)
|
||||
credential_values = [
|
||||
str(value)
|
||||
for key, value in request_params.items()
|
||||
if str(key).lower() in {"token", "ticket"} and value
|
||||
]
|
||||
if credential_values:
|
||||
cache_params["__credential_scope"] = hashlib.sha256(
|
||||
"|".join(credential_values).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
try:
|
||||
return await self._cache.request_api(
|
||||
api_path,
|
||||
cache_params,
|
||||
lambda: self._base_request(api_path, request_params, out),
|
||||
self._is_cacheable_response,
|
||||
force_refresh=force_refresh,
|
||||
allow_stale=allow_stale,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"接口缓存不可用,直接请求 JX3API endpoint={api_path}: {exc}")
|
||||
return await self._base_request(api_path, request_params, out), {
|
||||
"endpoint": api_path,
|
||||
"hit": False,
|
||||
"stale": False,
|
||||
"ttl_seconds": 0,
|
||||
}
|
||||
|
||||
|
||||
async def _request_api(
|
||||
self,
|
||||
@@ -217,7 +303,8 @@ class JX3APIService:
|
||||
"""通用接口请求与模板处理。"""
|
||||
return_data = self._init_return_data()
|
||||
|
||||
data = await self._base_request(path, params)
|
||||
data, cache_metadata = await self._cached_request(path, params)
|
||||
return_data["_cache"] = cache_metadata
|
||||
if isinstance(data, APIErrorResponse):
|
||||
return_data["msg"] = data.message or "获取接口信息失败"
|
||||
return return_data
|
||||
|
||||
Reference in New Issue
Block a user