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