11
This commit is contained in:
+10
-1
@@ -130,11 +130,20 @@
|
||||
"wzry_zhanji":{
|
||||
"url":"https://api.t1qq.com/api/tool/wzrr/morebattle",
|
||||
"method":"GET",
|
||||
"description":"魔盒中模糊查找剑网三的物品数据",
|
||||
"description":"王者荣耀战绩查询",
|
||||
"params":{
|
||||
"key": "vBpEzoiC9z5A9c9Nn83IhLn6M9",
|
||||
"id": "489048724",
|
||||
"option": "1"
|
||||
}
|
||||
},
|
||||
"wzry_ziliao":{
|
||||
"url":"https://api.t1qq.com/api/tool/wzrr/ydtp",
|
||||
"method":"GET",
|
||||
"description":"王者荣耀资料查询",
|
||||
"params":{
|
||||
"key": "vBpEzoiC9z5A9c9Nn83IhLn6M9",
|
||||
"id": "489048724"
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-118
@@ -6,64 +6,29 @@ from astrbot.api import logger
|
||||
|
||||
class APIClient:
|
||||
"""
|
||||
API客户端类,封装GET和POST请求功能
|
||||
API客户端类,支持GET/POST请求,同时兼容JSON和二进制数据
|
||||
"""
|
||||
|
||||
def __init__(self, base_timeout=10, ssl_verify=False):
|
||||
"""
|
||||
初始化APIClient
|
||||
|
||||
Args:
|
||||
base_timeout: 默认超时时间(秒)
|
||||
ssl_verify: SSL证书验证开关
|
||||
"""
|
||||
self.base_timeout = base_timeout
|
||||
self.ssl_verify = ssl_verify
|
||||
|
||||
async def _make_request(self, method, url, params_data=None):
|
||||
"""
|
||||
内部请求方法,统一处理请求逻辑
|
||||
|
||||
Args:
|
||||
method: 请求方法 ('GET', 'POST')
|
||||
url: 请求URL
|
||||
params_data: 统一的参数字典,如 {"name": "万花"}
|
||||
|
||||
Returns:
|
||||
成功时返回解析后的数据,失败返回None
|
||||
"""
|
||||
timeout = ClientTimeout(total=self.base_timeout)
|
||||
|
||||
try:
|
||||
# 添加请求日志
|
||||
logger.debug(f"发起 {method} 请求: {url}")
|
||||
logger.debug(f"参数数据: {params_data}")
|
||||
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
if method.upper() == 'GET':
|
||||
# GET请求:参数作为查询字符串
|
||||
async with session.get(
|
||||
url,
|
||||
params=params_data,
|
||||
ssl=self.ssl_verify
|
||||
) as response:
|
||||
async with session.get(url, params=params_data, ssl=self.ssl_verify) as response:
|
||||
return await self._handle_response(response)
|
||||
|
||||
elif method.upper() == 'POST':
|
||||
# POST请求:参数作为JSON数据
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
async with session.post(
|
||||
url,
|
||||
json=params_data, # 使用json参数而不是data
|
||||
headers=headers,
|
||||
ssl=self.ssl_verify
|
||||
) as response:
|
||||
async with session.post(url, json=params_data, headers=headers, ssl=self.ssl_verify) as response:
|
||||
return await self._handle_response(response)
|
||||
|
||||
else:
|
||||
logger.error(f"不支持的HTTP方法: {method}")
|
||||
return None
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"请求出错: {e}")
|
||||
return None
|
||||
@@ -73,24 +38,24 @@ class APIClient:
|
||||
|
||||
async def _handle_response(self, response):
|
||||
"""
|
||||
处理HTTP响应(增强版:兼容 text/json 和非标准 JSON)
|
||||
处理响应:支持 JSON 和二进制数据
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"响应状态: {response.status}")
|
||||
response.raise_for_status()
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
|
||||
# ----------------------
|
||||
# 强制兼容 text/json 类型
|
||||
# ----------------------
|
||||
if 'image' in content_type or 'octet-stream' in content_type:
|
||||
# 返回二进制数据
|
||||
data = await response.read()
|
||||
return data
|
||||
|
||||
# 尝试解析为 JSON
|
||||
try:
|
||||
# content_type=None 忽略 MIME 类型检查
|
||||
data = await response.json(content_type=None)
|
||||
except Exception:
|
||||
# 响应不是 JSON,尝试手动解析
|
||||
text = await response.text()
|
||||
logger.debug(f"原始文本响应: {text}")
|
||||
|
||||
# 尝试解析 JSON 字符串
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
@@ -98,9 +63,7 @@ class APIClient:
|
||||
return None
|
||||
|
||||
logger.debug(f"响应数据: {data}")
|
||||
|
||||
return self._check_response_data(data)
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"HTTP错误: {e}")
|
||||
return None
|
||||
@@ -108,18 +71,10 @@ class APIClient:
|
||||
logger.error(f"未知错误: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _check_response_data(self, data):
|
||||
"""
|
||||
检查API响应数据的通用逻辑
|
||||
|
||||
Args:
|
||||
data: API返回的数据
|
||||
|
||||
Returns:
|
||||
检查通过返回数据,否则返回None
|
||||
检查 API 返回 JSON 数据
|
||||
"""
|
||||
# 如果数据是字符串,尝试解析为JSON
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
data = json.loads(data)
|
||||
@@ -127,98 +82,57 @@ class APIClient:
|
||||
logger.error("响应数据是无效的JSON字符串")
|
||||
return None
|
||||
|
||||
# 检查是否有code字段
|
||||
if data and 'code' in data:
|
||||
# 有code字段时,检查是否成功
|
||||
if data and isinstance(data, dict) and 'code' in data:
|
||||
if data.get('code') not in [200, "0", 0, 1]:
|
||||
logger.error(f"API返回错误:{data.get('code', '未知状态')} {data.get('msg', '未知错误')}")
|
||||
return None
|
||||
else:
|
||||
# 无code字段时,检查数据是否为空
|
||||
if not data:
|
||||
logger.error("API返回空数据")
|
||||
return None
|
||||
elif not data:
|
||||
logger.error("API返回空数据")
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
async def post(self, api_url, params_data=None, outdata=None):
|
||||
"""
|
||||
POST请求方法
|
||||
|
||||
Args:
|
||||
api_url: API地址
|
||||
params_data: 参数字典,如 {"name": "万花"}
|
||||
outdata: 返回数据中要提取的字段
|
||||
|
||||
Returns:
|
||||
成功时返回outdata字段的数据,失败返回None
|
||||
"""
|
||||
data = await self._make_request('POST', api_url, params_data)
|
||||
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if outdata is None or outdata == "":
|
||||
if isinstance(data, bytes):
|
||||
return data
|
||||
if not outdata:
|
||||
return data
|
||||
return data.get(outdata, {})
|
||||
|
||||
async def get(self, api_url, params_data=None, outdata=None):
|
||||
"""
|
||||
GET请求方法
|
||||
|
||||
Args:
|
||||
api_url: API地址
|
||||
params_data: 参数字典,如 {"name": "万花"}
|
||||
outdata: 返回数据中要提取的字段
|
||||
|
||||
Returns:
|
||||
成功时返回outdata字段的数据,失败返回None
|
||||
"""
|
||||
data = await self._make_request('GET', api_url, params_data)
|
||||
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if outdata is None or outdata == "":
|
||||
if isinstance(data, bytes):
|
||||
return data
|
||||
if not outdata:
|
||||
return data
|
||||
return data.get(outdata, {})
|
||||
|
||||
|
||||
async def all_pages(self,http, api_url, params_data = None, outdata: str = "",listdata: str = "list", max_pages: int = 10):
|
||||
"""
|
||||
分页获取所有数据
|
||||
|
||||
Args:
|
||||
http: 请求方法 ('GET' 或 'POST')
|
||||
api_url: API地址
|
||||
params_data: 参数字典,如 {"name": "万花"}
|
||||
outdata: 返回数据中要提取的字段
|
||||
max_pages: 最大页数限制,0表示不限制
|
||||
listdata: 返回数据中包含列表数据的字段名,默认为"list"
|
||||
Returns:
|
||||
成功时返回所有页数据的列表,失败返回None
|
||||
"""
|
||||
async def all_pages(self, http, api_url, params_data=None, outdata: str = "", listdata: str = "list", max_pages: int = 10):
|
||||
all_data = []
|
||||
current_page = 1
|
||||
|
||||
while True:
|
||||
# 设置当前页码
|
||||
params = params_data.copy() if params_data else {}
|
||||
params["page"] = str(current_page)
|
||||
|
||||
# 请求数据
|
||||
if http.upper() == "POST":
|
||||
data = await self.post(api_url, params, outdata)
|
||||
else:
|
||||
data = await self.get(api_url, params, outdata)
|
||||
|
||||
if not data[listdata]:
|
||||
if not data or isinstance(data, bytes):
|
||||
# 二进制数据或者空数据,不分页
|
||||
break
|
||||
|
||||
if not data.get(listdata):
|
||||
break
|
||||
|
||||
# 添加当前页数据到总列表
|
||||
all_data.extend(data[listdata])
|
||||
|
||||
# 检查是否达到最大页数限制
|
||||
if max_pages and current_page >= max_pages:
|
||||
break
|
||||
|
||||
@@ -226,14 +140,11 @@ class APIClient:
|
||||
logger.info(f"已获取第 {current_page} 页数据")
|
||||
return all_data
|
||||
|
||||
|
||||
# 保持原有函数接口的兼容性
|
||||
# 保持原接口兼容
|
||||
async def api_data_post(api_url, params_data=None, outdata=None):
|
||||
"""兼容原有函数的POST请求"""
|
||||
client = APIClient()
|
||||
return await client.post(api_url, params_data, outdata)
|
||||
|
||||
async def api_data_get(api_url, params_data=None, outdata=None):
|
||||
"""兼容原有函数的GET请求"""
|
||||
client = APIClient()
|
||||
return await client.get(api_url, params_data, outdata)
|
||||
+36
-2
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
import base64
|
||||
from astrbot.api import logger
|
||||
|
||||
from .class_reqsest import APIClient
|
||||
@@ -69,7 +69,7 @@ class WZRYFunction:
|
||||
return_data["msg"] = "处理接口返回信息时出错"
|
||||
# 加载模板
|
||||
try:
|
||||
return_data["temp"] = load_template("temp_test.html")
|
||||
return_data["temp"] = load_template("wangzhezhanji.html")
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"加载模板失败: {e}")
|
||||
return_data["msg"] = "系统错误:模板文件不存在"
|
||||
@@ -77,4 +77,38 @@ class WZRYFunction:
|
||||
return_data["code"] = 200
|
||||
return return_data
|
||||
|
||||
async def ziliao(self,id: str):
|
||||
"""
|
||||
资料查询
|
||||
"""
|
||||
return_data = {
|
||||
"code": 0,
|
||||
"msg": "功能函数未执行",
|
||||
"data": {}
|
||||
}
|
||||
#在配置文件中获取接口配置
|
||||
api_config = self.__api_config["wzry_ziliao"]
|
||||
#更新参数
|
||||
api_config["params"]["id"] = id
|
||||
# 处理返回数据
|
||||
try:
|
||||
# 获取数据
|
||||
data = await self.__api.get(api_config["url"],api_config["params"])
|
||||
if not data:
|
||||
return_data["msg"] = "获取接口信息失败"
|
||||
return return_data
|
||||
# 数据处理
|
||||
return_data["data"]["img_base64"] = base64.b64encode(data).decode('utf-8')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理数据时出错: {e}")
|
||||
return_data["msg"] = "处理接口返回信息时出错"
|
||||
# 加载模板
|
||||
try:
|
||||
return_data["temp"] = load_template("temp_test.html")
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"加载模板失败: {e}")
|
||||
return_data["msg"] = "系统错误:模板文件不存在"
|
||||
return return_data
|
||||
return_data["code"] = 200
|
||||
return return_data
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ headers = {
|
||||
'Host': 'api.t1qq.com',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
conn.request("GET", "/api/tool/wzrr/morebattle?key=vBpEzoiC9z5A9c9Nn83IhLn6M9&id=489048724&option=1", payload, headers)
|
||||
conn.request("GET", "/api/tool/wzrr/ydtp?key=vBpEzoiC9z5A9c9Nn83IhLn6M9&id=489048724", payload, headers)
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
print(data.decode("utf-8"))
|
||||
print(data)
|
||||
@@ -294,7 +294,7 @@ class Jx3ApiPlugin(Star):
|
||||
|
||||
@wz.command("战绩")
|
||||
async def wz_zhanji(self, event: AstrMessageEvent,ID: str = "489048724",option: str = "1"):
|
||||
"""王者 战绩 服务器 天数"""
|
||||
"""王者 战绩 营地ID 对局类型"""
|
||||
try:
|
||||
data = await self.wzry.zhanji(ID,option)
|
||||
# logger.info(f"王者荣耀战绩查询结果{data}")
|
||||
@@ -308,6 +308,22 @@ class Jx3ApiPlugin(Star):
|
||||
logger.error(f"功能函数执行错误: {e}")
|
||||
yield event.plain_result("猪脑过载,请稍后再试")
|
||||
|
||||
@wz.command("资料")
|
||||
async def wz_ziliao(self, event: AstrMessageEvent,ID: str = "489048724"):
|
||||
"""王者 战绩 营地ID 对局类型"""
|
||||
try:
|
||||
data = await self.wzry.ziliao(ID)
|
||||
# logger.info(f"输出结果{data}")
|
||||
if data["code"] == 200:
|
||||
url = await self.html_render(data["temp"], data["data"], options={})
|
||||
yield event.image_result(url)
|
||||
else:
|
||||
yield event.plain_result(data["msg"])
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f"功能函数执行错误: {e}")
|
||||
yield event.plain_result("猪脑过载,请稍后再试")
|
||||
|
||||
|
||||
async def terminate(self):
|
||||
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
|
||||
|
||||
@@ -1,57 +1,3 @@
|
||||
<div style="font-family: 'Microsoft YaHei', Arial, sans-serif; padding:20px; background:#f5f7fa; max-width:900px; margin:0 auto;">
|
||||
<h1 style="text-align:center; font-size:28px; margin-bottom:20px;">王者荣耀 — 对局列表</h1>
|
||||
|
||||
<table style="width:100%; border-collapse: collapse; position:relative;">
|
||||
<tbody>
|
||||
{% for m in data %}
|
||||
<tr style="background:#fff; border-radius:10px; margin-bottom:12px; position:relative;">
|
||||
<td style="width:64px; padding:8px; vertical-align:top;">
|
||||
<img src="{{ m.heroIcon }}" style="width:64px; height:64px; object-fit:cover; border-radius:8px;">
|
||||
<!-- 对局描述 -->
|
||||
<div style="margin-top:6px; font-size:16px; color:#444; font-weight:bold; text-align:center;">
|
||||
{{ m.desc }}
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px; vertical-align:top; position:relative;">
|
||||
<!-- 胜负+MVP右上角 -->
|
||||
<div style="position:absolute; top:8px; right:8px; display:flex; gap:6px; align-items:center;">
|
||||
<!-- MVP -->
|
||||
<div style="background:#b69f1c; color:#fff; font-weight:bold; padding:4px 8px; border-radius:6px; font-size:12px; box-shadow:0 2px 4px rgba(0,0,0,0.2);">
|
||||
{{ m.MVP }}
|
||||
</div>
|
||||
<!-- 胜负 -->
|
||||
<div style="background: {{ m.gameresult_bg }};
|
||||
color: {{ m.gameresult_color }};
|
||||
padding:6px 10px; border-radius:8px; font-weight:bold; font-size:14px;
|
||||
box-shadow:0 2px 4px rgba(0,0,0,0.1);">
|
||||
{{ m.gameresult_label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第一行:时间、地图、角色、星数 -->
|
||||
<div style="margin-bottom:6px; color:#607785;">
|
||||
{{ m.gametime }} · {{ m.mapName }}<br>
|
||||
{{ m.roleJobName }} · ⭐{{ m.stars }}
|
||||
</div>
|
||||
|
||||
<!-- 第二行:击杀/死亡/助攻/时长 -->
|
||||
<div style="margin-bottom:6px; font-size:14px; color:#333;">
|
||||
击杀 {{ m.killcnt }} 死亡 {{ m.deadcnt }} 助攻 {{ m.assistcnt }} 时长 {{ m.time_str }}
|
||||
</div>
|
||||
|
||||
<!-- 第三行:评分 -->
|
||||
<div style="font-size:15px; color:#000000; font-weight:bold; margin-bottom:6px;">
|
||||
评分 {{ m.gradeGame }}
|
||||
</div>
|
||||
|
||||
<!-- 隐藏分右下角 -->
|
||||
<div style="position:absolute; bottom:8px; right:8px; font-size:14px; color:#056a3a; font-weight:bold;">
|
||||
巅峰赛积分 {{ m.oldMasterMatchScore }} → {{ m.newMasterMatchScore }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height:12px;"><td colspan="2"></td></tr> <!-- 间距 -->
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="text-align:center; margin-bottom:20px;">
|
||||
<img src="data:image/jpeg;base64,{{ img_base64 }}" style="max-width:100%; border-radius:10px;">
|
||||
</div>
|
||||
@@ -1,22 +1,37 @@
|
||||
<div style="font-family: 'Microsoft YaHei', Arial, sans-serif; padding:20px; background:#f5f7fa; max-width:900px; margin:0 auto;">
|
||||
<h1 style="text-align:center; font-size:28px; margin-bottom:20px;">王者荣耀 — 对局列表</h1>
|
||||
|
||||
<table style="width:100%; border-collapse: collapse;">
|
||||
<table style="width:100%; border-collapse: collapse; position:relative;">
|
||||
<tbody>
|
||||
{% for m in data %}
|
||||
<tr style="background:#fff; border-radius:10px; margin-bottom:12px;">
|
||||
<tr style="background:#fff; border-radius:10px; margin-bottom:12px; position:relative;">
|
||||
<td style="width:64px; padding:8px; vertical-align:top;">
|
||||
<img src="{{ m.heroIcon }}" style="width:64px; height:64px; object-fit:cover; border-radius:8px;">
|
||||
<!-- 对局描述 -->
|
||||
<div style="margin-top:6px; font-size:16px; color:#444; font-weight:bold; text-align:center;">
|
||||
{{ m.desc }}
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px; vertical-align:top;">
|
||||
<!-- 第一行:时间、地图、角色、星数、结果 -->
|
||||
<div style="margin-bottom:6px;">
|
||||
<span>{{ m.gametime }} · {{ m.mapName }}</span><br>
|
||||
<span style="color:#607785;">{{ m.roleJobName }} · ⭐{{ m.stars }}</span><br>
|
||||
<span style="padding: 2px 6px; border-radius:6px; font-size:13px;">
|
||||
<td style="padding:8px; vertical-align:top; position:relative;">
|
||||
<!-- 胜负+MVP右上角 -->
|
||||
<div style="position:absolute; top:8px; right:8px; display:flex; gap:6px; align-items:center;">
|
||||
<!-- MVP -->
|
||||
<div style="background:#b69f1c; color:#fff; font-weight:bold; padding:4px 8px; border-radius:6px; font-size:12px; box-shadow:0 2px 4px rgba(0,0,0,0.2);">
|
||||
{{ m.MVP }}
|
||||
</div>
|
||||
<!-- 胜负 -->
|
||||
<div style="background: {{ m.gameresult_bg }};
|
||||
color: {{ m.gameresult_color }};
|
||||
padding:6px 10px; border-radius:8px; font-weight:bold; font-size:14px;
|
||||
box-shadow:0 2px 4px rgba(0,0,0,0.1);">
|
||||
{{ m.gameresult_label }}
|
||||
</span>
|
||||
<span style="font-size:13px; color:#7b8b96; margin-left:6px;">评分 {{ m.gradeGame }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第一行:时间、地图、角色、星数 -->
|
||||
<div style="margin-bottom:6px; color:#607785;">
|
||||
{{ m.gametime }} · {{ m.mapName }}<br>
|
||||
{{ m.roleJobName }} · ⭐{{ m.stars }}
|
||||
</div>
|
||||
|
||||
<!-- 第二行:击杀/死亡/助攻/时长 -->
|
||||
@@ -24,10 +39,14 @@
|
||||
击杀 {{ m.killcnt }} 死亡 {{ m.deadcnt }} 助攻 {{ m.assistcnt }} 时长 {{ m.time_str }}
|
||||
</div>
|
||||
|
||||
<!-- 第三行:描述/隐分 -->
|
||||
<div style="font-size:13px; color:#61707a;">
|
||||
<span>{{ m.desc }}</span>
|
||||
<span style="float:right;">巅峰赛积分 {{ m.oldMasterMatchScore }} → {{ m.newMasterMatchScore }}</span>
|
||||
<!-- 第三行:评分 -->
|
||||
<div style="font-size:15px; color:#000000; font-weight:bold; margin-bottom:6px;">
|
||||
评分 {{ m.gradeGame }}
|
||||
</div>
|
||||
|
||||
<!-- 隐藏分右下角 -->
|
||||
<div style="position:absolute; bottom:8px; right:8px; font-size:14px; color:#056a3a; font-weight:bold;">
|
||||
巅峰赛积分 {{ m.oldMasterMatchScore }} → {{ m.newMasterMatchScore }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user