353 lines
12 KiB
Python
353 lines
12 KiB
Python
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from astrbot.api import logger
|
|
|
|
from ..fun_basic import gold_to_parts
|
|
from .base import DATA_PROCESSING_ERRORS, BaseDomainService
|
|
|
|
|
|
class TradeService(BaseDomainService):
|
|
async def _get_trade_item_groups(self) -> Optional[List[Dict[str, Any]]]:
|
|
"""获取交易行物品库,优先使用未过期缓存"""
|
|
cache_key = "trade_item_groups"
|
|
cached, expired = await self._cache.get(cache_key)
|
|
if isinstance(cached, list) and not expired:
|
|
return cached
|
|
|
|
data = await self.request("jx3box_trade_items")
|
|
if isinstance(data, list) and data:
|
|
await self._cache.set(cache_key, data)
|
|
return data
|
|
|
|
if isinstance(cached, list) and cached:
|
|
logger.warning("交易行物品库接口失败,使用旧缓存")
|
|
return cached
|
|
|
|
return None
|
|
|
|
def _flatten_trade_items(
|
|
self, groups: List[Dict[str, Any]]
|
|
) -> list[Dict[str, Any]]:
|
|
"""从交易行物品分组中提取可查询物品"""
|
|
items = []
|
|
for group in groups:
|
|
if not isinstance(group, dict):
|
|
continue
|
|
for item in group.get("items", []) or []:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
item_id = item.get("item_id")
|
|
label = item.get("label")
|
|
if not item_id or not label:
|
|
continue
|
|
items.append(
|
|
{
|
|
"item_id": str(item_id),
|
|
"label": str(label),
|
|
"icon": str(item.get("icon") or ""),
|
|
}
|
|
)
|
|
return items
|
|
|
|
def _match_trade_items(
|
|
self, items: list[Dict[str, Any]], keyword: str, limit: int = 50
|
|
) -> list[Dict[str, Any]]:
|
|
"""按物品名模糊匹配交易行物品"""
|
|
keyword = (keyword or "").strip()
|
|
if not keyword:
|
|
return []
|
|
|
|
matched = []
|
|
seen = set()
|
|
for item in items:
|
|
label = item.get("label", "")
|
|
item_id = item.get("item_id", "")
|
|
if keyword not in label or item_id in seen:
|
|
continue
|
|
seen.add(item_id)
|
|
if label == keyword:
|
|
rank = 0
|
|
elif label.startswith(keyword):
|
|
rank = 1
|
|
else:
|
|
rank = 2
|
|
matched.append((rank, len(label), label, item))
|
|
|
|
matched.sort(key=lambda row: (row[0], row[1], row[2]))
|
|
return [row[3] for row in matched[:limit]]
|
|
|
|
async def jinjia(self, server: str, limit: str) -> Dict[str, Any]:
|
|
"""区服金价"""
|
|
return_data = self.new_result()
|
|
|
|
params = {"server": server, "limit": limit}
|
|
data_list: Optional[List[Dict[str, Any]]] = await self.request(
|
|
"jx3_jinjia", params=params
|
|
)
|
|
|
|
if not data_list or not isinstance(data_list, list):
|
|
return_data["msg"] = "获取接口信息失败或数据格式错误"
|
|
return return_data
|
|
|
|
# 加载模板
|
|
if not await self.attach_template(return_data, "jinjia.html"):
|
|
return return_data
|
|
|
|
# 准备模板渲染数据
|
|
try:
|
|
return_data["data"]["items"] = data_list
|
|
|
|
except DATA_PROCESSING_ERRORS as e:
|
|
logger.error(f"模板数据准备失败: {e}")
|
|
return_data["msg"] = "系统错误:模板渲染数据准备失败"
|
|
return return_data
|
|
|
|
return_data["code"] = 200
|
|
|
|
return return_data
|
|
|
|
async def wujia(self, Name: str, server: str) -> Dict[str, Any]:
|
|
"""物价查询"""
|
|
params_search = {"name": Name, "server": server}
|
|
search_data: Optional[Dict[str, Any]] = await self.request(
|
|
"jx3_wujia", params=params_search
|
|
)
|
|
|
|
if not isinstance(search_data, dict) or not search_data:
|
|
return self.failure("未找到该外观")
|
|
|
|
return await self.template_result("wujia.html", search_data)
|
|
|
|
async def jiaoyihang(self, name: str, server: str) -> Dict[str, Any]:
|
|
"""区服交易行"""
|
|
return_data = self.new_result()
|
|
|
|
item_groups = await self._get_trade_item_groups()
|
|
if not item_groups:
|
|
return_data["msg"] = "交易行基础物品数据获取失败"
|
|
return return_data
|
|
|
|
trade_items = self._flatten_trade_items(item_groups)
|
|
matched_items = self._match_trade_items(trade_items, name, 50)
|
|
if not matched_items:
|
|
return_data["msg"] = "未找到匹配的交易行物品"
|
|
return return_data
|
|
|
|
item_map = {item["item_id"]: item for item in matched_items}
|
|
params = {
|
|
"item_ids": list(item_map.keys()),
|
|
"server": server,
|
|
"aggregate_type": "hourly",
|
|
}
|
|
price_data: Optional[List[Dict[str, Any]]] = await self.request(
|
|
"jx3_jiaoyihang", params=params, out_key=""
|
|
)
|
|
|
|
if not price_data or not isinstance(price_data, list):
|
|
return_data["msg"] = "未查询到交易行价格数据"
|
|
return return_data
|
|
|
|
try:
|
|
result = []
|
|
for price_item in price_data:
|
|
if not isinstance(price_item, dict):
|
|
continue
|
|
|
|
item_id = str(price_item.get("item_id") or "")
|
|
base_item = item_map.get(item_id)
|
|
if not base_item:
|
|
continue
|
|
|
|
timestamp = price_item.get("timestamp")
|
|
try:
|
|
created = datetime.fromtimestamp(int(timestamp)).strftime(
|
|
"%Y-%m-%d %H:%M:%S"
|
|
)
|
|
except (TypeError, ValueError, OSError):
|
|
created = ""
|
|
|
|
result.append(
|
|
{
|
|
"item_id": item_id,
|
|
"name": base_item.get("label", ""),
|
|
"icon": (
|
|
"https://icon.jx3box.com/icon/"
|
|
f"{base_item.get('icon', '')}.png"
|
|
),
|
|
"server": price_item.get("server", server),
|
|
"price": price_item.get("price", 0),
|
|
"price_parts": gold_to_parts(price_item.get("price", 0)),
|
|
"sample": price_item.get("sample", 0),
|
|
"created": created,
|
|
}
|
|
)
|
|
|
|
if not result:
|
|
return_data["msg"] = "未查询到交易行价格数据"
|
|
return return_data
|
|
|
|
return_data["data"] = {
|
|
"search_name": name,
|
|
"server": server,
|
|
"matched_count": len(matched_items),
|
|
"result_count": len(result),
|
|
"list": result,
|
|
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
}
|
|
except DATA_PROCESSING_ERRORS as e:
|
|
logger.error(f"处理交易行数据失败: {e}")
|
|
return_data["msg"] = "处理交易行数据失败"
|
|
return return_data
|
|
|
|
# 5. 模板渲染
|
|
if not await self.attach_template(return_data, "jiaoyihang.html"):
|
|
return return_data
|
|
return_data["code"] = 200
|
|
|
|
return return_data
|
|
|
|
async def tiebawujia(
|
|
self, name: str, limit: int = 5, server: str = ""
|
|
) -> Dict[str, Any]:
|
|
"""贴吧物价"""
|
|
return_data = self.new_result()
|
|
|
|
try:
|
|
limit = int(limit)
|
|
except (TypeError, ValueError):
|
|
return_data["msg"] = "记录数量必须是数字"
|
|
return return_data
|
|
|
|
if limit < 1 or limit > 50:
|
|
return_data["msg"] = "贴吧物价记录数量必须在 1-50 之间"
|
|
return return_data
|
|
|
|
params = {
|
|
"server": server,
|
|
"name": name,
|
|
"limit": limit,
|
|
}
|
|
|
|
data: Optional[List[Dict[str, Any]]] = await self.request(
|
|
"jx3_tiebawujia", params=params
|
|
)
|
|
|
|
if not data or not isinstance(data, list):
|
|
return_data["msg"] = "未查询到贴吧物价记录"
|
|
return return_data
|
|
|
|
try:
|
|
lines = [
|
|
f"贴吧物价:{name}",
|
|
f"服务器:{server}",
|
|
f"记录数:{len(data)}",
|
|
"",
|
|
]
|
|
|
|
for index, item in enumerate(data, start=1):
|
|
if not isinstance(item, dict):
|
|
continue
|
|
|
|
item_time = item.get("time", "")
|
|
if item_time:
|
|
try:
|
|
item_time = datetime.fromtimestamp(int(item_time)).strftime(
|
|
"%Y-%m-%d %H:%M:%S"
|
|
)
|
|
except (TypeError, ValueError, OSError):
|
|
item_time = str(item_time)
|
|
|
|
lines.extend(
|
|
[
|
|
f"{index}. {item.get('name', '')}",
|
|
(
|
|
f"区服:{item.get('zone', '')} "
|
|
f"服务器:{item.get('server', '')}"
|
|
),
|
|
f"内容:{item.get('context', '')}",
|
|
f"回复:{item.get('reply', '')} 楼层:{item.get('floor', '')}",
|
|
f"时间:{item_time}",
|
|
f"链接:https://tieba.baidu.com/p/{item.get('url', '')}",
|
|
"",
|
|
]
|
|
)
|
|
|
|
if len(lines) <= 4:
|
|
return_data["msg"] = "未查询到贴吧物价记录"
|
|
return return_data
|
|
|
|
return_data["data"] = "\n".join(lines).rstrip()
|
|
except DATA_PROCESSING_ERRORS as e:
|
|
logger.error(f"处理贴吧物价数据失败: {e}")
|
|
return_data["msg"] = "处理贴吧物价数据失败"
|
|
return return_data
|
|
|
|
return_data["code"] = 200
|
|
|
|
return return_data
|
|
|
|
async def diaoluo(
|
|
self, name: str, limit: int = 20, server: str = ""
|
|
) -> Dict[str, Any]:
|
|
"""物品掉落记录"""
|
|
return_data = self.new_result()
|
|
|
|
try:
|
|
limit = int(limit)
|
|
except (TypeError, ValueError):
|
|
return_data["msg"] = "数量必须是数字"
|
|
return return_data
|
|
|
|
if limit < 1 or limit > 100:
|
|
return_data["msg"] = "掉落记录数量需在 1-100 之间"
|
|
return return_data
|
|
|
|
# 1. 构造请求参数
|
|
params = {"server": server, "name": name, "limit": limit}
|
|
|
|
# 2. 调用基础请求
|
|
data: Optional[List[Dict[str, Any]]] = await self.request(
|
|
"jx3_diaoluo", params=params
|
|
)
|
|
|
|
if not data or not isinstance(data, list):
|
|
return_data["msg"] = "未查询到掉落记录"
|
|
return return_data
|
|
|
|
# 3. 处理返回数据
|
|
try:
|
|
for item in data:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
|
|
drop_time = item.get("time")
|
|
if drop_time:
|
|
try:
|
|
item["time"] = datetime.fromtimestamp(int(drop_time)).strftime(
|
|
"%Y-%m-%d %H:%M:%S"
|
|
)
|
|
except (TypeError, ValueError, OSError):
|
|
item["time"] = str(drop_time)
|
|
else:
|
|
item["time"] = ""
|
|
|
|
return_data["data"] = {
|
|
"items": data,
|
|
"name": name,
|
|
"limit": limit,
|
|
"server": server,
|
|
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
}
|
|
except DATA_PROCESSING_ERRORS as e:
|
|
logger.error(f"处理掉落数据失败: {e}")
|
|
return_data["msg"] = "处理掉落数据失败"
|
|
return return_data
|
|
|
|
# 4. 模板渲染
|
|
if not await self.attach_template(return_data, "diaoluo.html"):
|
|
return return_data
|
|
return_data["code"] = 200
|
|
|
|
return return_data
|