Compare commits

..

10 Commits

Author SHA1 Message Date
qsc20001102 63f079673b OK 2026-02-27 12:40:27 +08:00
qsc20001102 5fca36d1ed ok 2026-02-14 20:01:28 +08:00
qsc20001102 2548da9af3 OK 2026-02-14 18:33:05 +08:00
qsc20001102 90aa479ade ok 2026-02-12 16:03:51 +08:00
qsc20001102 0f7ada27ce ok 2026-02-12 16:01:58 +08:00
qsc20001102 33f921c40d ok 2026-02-12 15:37:16 +08:00
qsc20001102 547f5fec3b ok 2026-02-07 20:18:10 +08:00
qsc20001102 f8205fdf61 ok 2026-02-07 19:58:25 +08:00
qsc20001102 bfb40c9160 ok 2026-02-07 15:23:12 +08:00
qsc20001102 b2064ed9ad ok 2026-02-07 10:42:51 +08:00
23 changed files with 1222 additions and 925 deletions
+17
View File
@@ -0,0 +1,17 @@
## 更新记录
### version: 1.0.3
调整输入营地ID查询的判定条件。
### version: 1.0.2
新增战绩评价功能,参考最新10把战绩,调用模型进行简单的评价。指令触发方式调整,删除王者字符。如果还需要王者战绩来触发需要在指令前缀里面添加 王者。
### version: 1.0.1
新增英雄的上榜战力查询,需要柠柚API。
### version: 1.0.0
大部分重要功能已经实现
+37 -5
View File
@@ -1,9 +1,41 @@
# Gok
# 王者数据查询工具
AstrBot 插件模板
## 简介
A template plugin for AstrBot plugin feature
主要是自用,查询战绩,在游戏中隐藏的战绩也可以查,如果营地的战绩也隐藏了就查不了。只有营地里面大号的ID才可查询。
# 支持
插件功能绝大部分都是用AI写的,又进行了调试。
[帮助文档](https://astrbot.app)
插件数据依赖外部接口提供,需要自行支持创建令牌才可使用。(免费调用)
## 功能特点
- **处理接口数据**:将接口返回的数据处理成,玩家能读懂的消息发送
- **HTML渲染图片**:利用astrbot里面的文转图服务,处理HTML页面渲染成图片
## 插件配置
**指令前缀**
开启后必须要在指令功能前面加上下面配置的前缀才能正常触发。关闭后直接只有指令功能即可。
**应天API 令牌**
前往https://api.t1qq.com 注册并创建令牌(免费)
**柠柚API 令牌**
前往https://api.nycnm.cn 注册并创建令牌(免费)
## 使用方式
如果开启了前缀,需要在所有指令前面加上设定的前缀。
在聊天中输入 **功能** 即可获取插件所有的功能使用说明。
在使用 **战绩****资料** 两个功能时后面可以直接输入营地ID进行查询,或者输入提前自定义好的角色来查询。
在使用 **上榜战力** 时,可以在英雄名称后面加一个大区参数 aqq awx iqq iwx 四个大区,不写默认aqq。
指令 **角色查看**、**角色添加**、**角色修改**、**角色删除**、**角色查询** 就是用来操作角色数据的,给王者营地ID起一个别名,方便自己记忆,也方便查询。
+49 -1
View File
@@ -1,3 +1,51 @@
{
"prefix": {
"description": "指令前缀",
"type": "object",
"items": {
"enable": {
"description": "是否启用",
"type": "bool",
"default": true,
"hint": "是否启用指令前缀检查功能"
},
"text": {
"description": "前缀内容",
"type": "string",
"default": "王者",
"hint": "为了避免和其他插件的指令冲突可以增加指令的前缀,为空默认值为王者。"
}
}
},
"ytapi_token": {
"description": "应天API 令牌",
"type": "string",
"default": "",
"hint": "前往https://api.t1qq.com 注册并创建令牌(免费)。"
},
"nyapi_token": {
"description": "柠柚API 令牌",
"type": "string",
"default": "",
"hint": "前往https://api.nycnm.cn 注册并创建令牌(免费)。"
},
"comment": {
"description": "战绩锐评",
"type": "object",
"items": {
"enable": {
"description": "是否启用",
"type": "bool",
"default": false,
"hint": "是否启用战绩锐评功能"
},
"select_provider": {
"description": "锐评模型",
"type": "string",
"hint": "锐评战绩的模型,留空使用会话默认模型",
"default": "",
"_special": "select_provider"
}
}
}
}
-21
View File
@@ -1,21 +0,0 @@
{
"wzry_zhanji":{
"url":"https://api.t1qq.com/api/tool/wzrr/morebattle",
"method":"GET",
"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"
}
}
}
-300
View File
@@ -1,300 +0,0 @@
from datetime import datetime
import base64
from astrbot.api import logger
from .request import APIClient
from .async_mysql import AsyncMySQL
from .function_basic import load_template,extract_fields
class WZRYFunction:
def __init__(self, api_config,db: AsyncMySQL ):
self.__api = APIClient()
self.__db = db
self.__api_config = api_config
async def _SELECT_ID(self,name: str):
"""
查询ID
"""
sql = "SELECT id FROM wzydid WHERE name = %s or id = %s"
sqlid = await self.__db.fetch_one(sql, (name,name))
return sqlid["id"] if sqlid else None
async def all_user(self):
"""
查询所有用户
"""
sql = "SELECT id,name FROM wzydid"
sqlid = await self.__db.fetch_all(sql)
return_data = "营地ID\t\t昵称\n"
for m in sqlid:
return_data += f"{m['id']}\t{m['name']}\n"
return return_data
async def add_user(self,id: str ,name: str):
"""
添加用户
"""
self_id = await self._SELECT_ID(id)
if self_id is not None:
return "该用户已存在,无需重复添加"
sql = "INSERT INTO wzydid (id,name) VALUES (%s, %s)"
rowcount = await self.__db.execute(sql, (id,name))
if rowcount > 0:
return f"id:{id}昵称:{name}\n添加成功"
else:
return f"id:{id}昵称:{name}\n添加失败,请稍后再试"
async def update_user(self,id: str ,name: str):
"""
更新用户
"""
self_id = await self._SELECT_ID(id)
if self_id is None:
return "该用户不存在,请先添加用户"
sql = "UPDATE wzydid SET name = %s WHERE id = %s"
rowcount = await self.__db.execute(sql, (name,id))
if rowcount > 0:
return f"id:{id}昵称:{name}\n更新成功"
else:
return f"id:{id}昵称:{name}\n更新失败,请稍后再试"
async def delete_user(self,id: str ):
"""
删除用户
"""
self_id = await self._SELECT_ID(id)
if self_id is None:
return "该用户不存在,无需删除"
sql = "DELETE FROM wzydid WHERE id = %s"
rowcount = await self.__db.execute(sql, (id,))
if rowcount > 0:
return f"id:{id}\n删除成功"
else:
return f"id:{id}\n删除失败,请稍后再试"
async def zhanji(self,name: str ,option: str):
"""
战绩查询
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["wzry_zhanji"]
# ID查询
sql_id = await self._SELECT_ID(name)
if sql_id is None:
return_data["msg"] = "未查询到该用户,请确认输入正确的昵称或营地ID"
return return_data
#更新参数
api_config["params"]["id"] = sql_id
api_config["params"]["option"] = option
# 需要提取的字段
fields = ["gametime","killcnt","deadcnt","assistcnt","gameresult","mvpcnt","losemvp","mapName",
"oldMasterMatchScore","newMasterMatchScore","usedTime","winNum","failNum","roleJobName","stars","desc",
"gradeGame","heroIcon","godLikeCnt", "firstBlood","hero1TripleKillCnt","hero1UltraKillCnt","hero1RampageCnt","evaluateUrlV3","mvpUrlV3"]
# 处理返回数据
try:
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 提取字段
result = extract_fields(data["list"], fields)
result = result[:25]
# 数据处理
for m in result:
minutes = m["usedTime"] // 60
seconds = m["usedTime"] % 60
m["time_str"] = f"{minutes}:{seconds:02d}"
return_data["data"] = result
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
# 加载模板
try:
return_data["temp"] = load_template("wangzhezhanji.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
async def ziliao(self, name: str):
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 查询 ID
sql_id = await self._SELECT_ID(name)
if sql_id is None:
return_data["msg"] = "未查询到该用户,请确认输入正确的昵称或营地ID"
return return_data
api_config = self.__api_config["wzry_ziliao"]
api_config["params"]["id"] = sql_id
try:
data = await self.__api.get(api_config["url"], api_config["params"])
# 转 base64
return_data["data"]["img_base64"] = base64.b64encode(data).decode("utf-8")
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
# 加载模板
try:
return_data["temp"] = load_template("wzry_zl.html")
except FileNotFoundError:
logger.error(f"加载模板失败")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
async def bilei_all(self):
"""
查询仇人列表
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 查询所有
try:
sql = "SELECT * FROM wzrybl"
sqlid = await self.__db.fetch_all(sql)
except Exception as e:
logger.error(f"SQL执行错误: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
# 加载模板
try:
return_data["temp"] = load_template("bilei.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["data"]["lists"] = sqlid
return_data["code"] = 200
return return_data
async def bilei_add(self,name:str, text:str, user:str):
"""
仇人 添加
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 获取系统时间
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 插入数据
try:
sql = "INSERT INTO wzrybl (name, text, time, user) VALUES (%s, %s, %s, %s)"
await self.__db.execute(sql, (name, text, now, user))
except Exception as e:
logger.error(f"SQL执行错误: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
return_data["msg"] = "添加成功\n"
return_data["msg"] += "仇人名称:"+name+"\n"
return_data["msg"] += "仇人备注:"+text+"\n"
return_data["msg"] += "添加时间:"+now+"\n"
return_data["msg"] += "记录人:"+user+"\n"
return_data["code"] = 200
return return_data
async def bilei_select(self,name:str):
"""
仇人 查找
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 查询数据
try:
sql = "SELECT * FROM wzrybl WHERE name LIKE %s"
sqlid = await self.__db.fetch_all(sql, ("%"+name+"%",))
except Exception as e:
logger.error(f"SQL执行错误: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
# 加载模板
try:
return_data["temp"] = load_template("bilei.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["data"]["lists"] = sqlid
return_data["code"] = 200
return return_data
async def bilei_update(self, id:int, name:str, text:str, user:str):
"""
仇人 修改 ID 名称 备注
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 获取系统时间
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 查询数据
try:
sql = "UPDATE wzrybl SET name = %s, text = %s, time = %s, user = %s WHERE id = %s"
rowcount = await self.__db.execute(sql, (name, text, now, user, id))
if rowcount > 0:
return_data["msg"] = "修改成功\n"
return_data["msg"] += "仇人名称:"+name+"\n"
return_data["msg"] += "仇人备注:"+text+"\n"
return_data["msg"] += "修改时间:"+now+"\n"
return_data["msg"] += "记录人:"+user+"\n"
return_data["code"] = 200
else:
return_data["msg"] = "修改失败,未找到对应记录"
except Exception as e:
logger.error(f"SQL执行错误: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
return return_data
async def bilei_delete(self, id:int):
"""
仇人 删除 ID
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
# 查询数据
try:
sql = "DELETE FROM wzrybl WHERE id = %s"
rowcount = await self.__db.execute(sql, (id,))
if rowcount > 0:
return_data["msg"] = "删除成功\n"
return_data["code"] = 200
else:
return_data["msg"] = "删除失败,未找到对应记录"
except Exception as e:
logger.error(f"SQL执行错误: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
return return_data
Binary file not shown.
Binary file not shown.
-90
View File
@@ -1,90 +0,0 @@
import aiomysql
class AsyncMySQL:
def __init__(self, db_config: dict):
self.db_config = db_config
self.pool = None
async def init_pool(self):
"""初始化连接池"""
if self.pool is None:
self.pool = await aiomysql.create_pool(**self.db_config)
async def close_pool(self):
"""关闭连接池"""
if self.pool:
self.pool.close()
await self.pool.wait_closed()
self.pool = None
async def fetch_one(self, sql: str, params=None):
"""查询单条数据"""
await self.init_pool()
async with self.pool.acquire() as conn: # type: ignore
async with conn.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(sql, params or ())
return await cursor.fetchone()
async def fetch_all(self, sql: str, params=None):
"""查询多条数据"""
await self.init_pool()
async with self.pool.acquire() as conn: # type: ignore
async with conn.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(sql, params or ())
return await cursor.fetchall()
async def execute(self, sql: str, params=None):
"""执行 SQLinsert/update/delete"""
await self.init_pool()
async with self.pool.acquire() as conn: # type: ignore
async with conn.cursor() as cursor:
await cursor.execute(sql, params or ())
await conn.commit()
return cursor.rowcount
async def executemany(self, sql: str, params_list):
"""批量执行 SQL"""
await self.init_pool()
async with self.pool.acquire() as conn: # type: ignore
async with conn.cursor() as cursor:
await cursor.executemany(sql, params_list)
await conn.commit()
return cursor.rowcount
async def truncate_table(self, table_name: str):
"""清空指定表"""
await self.init_pool()
async with self.pool.acquire() as conn: # type: ignore
async with conn.cursor() as cursor:
sql = f"TRUNCATE TABLE `{table_name}`"
await cursor.execute(sql)
await conn.commit()
return True
# ----------------------------------------------------------------------
# 新增:自动生成 SQL 的增删改功能
# ----------------------------------------------------------------------
async def insert_record(self, table: str, data: dict):
"""插入记录:data 是 dict"""
keys = ", ".join(f"`{k}`" for k in data.keys())
placeholders = ", ".join(["%s"] * len(data))
sql = f"INSERT INTO `{table}` ({keys}) VALUES ({placeholders})"
return await self.execute(sql, tuple(data.values()))
async def update_record(self, table: str, data: dict, where: dict):
"""更新记录:data、where 都是 dict"""
set_clause = ", ".join(f"`{k}`=%s" for k in data.keys())
where_clause = " AND ".join(f"`{k}`=%s" for k in where.keys())
sql = f"UPDATE `{table}` SET {set_clause} WHERE {where_clause}"
params = tuple(data.values()) + tuple(where.values())
return await self.execute(sql, params)
async def delete_record(self, table: str, where: dict):
"""删除记录:where 是 dict"""
where_clause = " AND ".join(f"`{k}`=%s" for k in where.keys())
sql = f"DELETE FROM `{table}` WHERE {where_clause}"
return await self.execute(sql, tuple(where.values()))
+40
View File
@@ -0,0 +1,40 @@
from pathlib import Path
import aiofiles
async def load_template(template_name: str) -> str:
"""
异步加载模板内容(非阻塞)
"""
plugin_dir = Path(__file__).parent.parent
template_path = plugin_dir / "templates" / template_name
if not template_path.exists():
raise FileNotFoundError(f"模板文件不存在: {template_path}")
async with aiofiles.open(template_path, "r", encoding="utf-8") as f:
return await f.read()
def extract_fields(data_list, fields):
"""
从字典列表中提取多个字段
Args:
data_list (list[dict]): 包含字典的列表
fields (list[str]): 要提取的字段名列表
Returns:
list[dict]: 只包含指定字段的新字典列表
"""
result = []
try:
for item in data_list:
extracted = {field: item.get(field) for field in fields}
result.append(extracted)
except Exception as e:
return []
return result
-167
View File
@@ -1,167 +0,0 @@
import os
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from io import BytesIO
import base64
def load_template(template_name):
"""
从模板文件加载模板内容
Args:
template_name: 模板文件名(不带路径)
Returns:
str: 模板内容
"""
# 获取模板文件路径
plugin_dir = Path(__file__).parent.parent
template_path = plugin_dir / "templates" / template_name
# 检查文件是否存在
if not template_path.exists():
raise FileNotFoundError(f"模板文件不存在: {template_path}")
# 读取模板内容
with open(template_path, 'r', encoding='utf-8') as f:
return f.read()
def flatten_field(data_list, field_name):
"""
提取并扁平化指定字段的值
从一个字典列表中,收集指定字段的所有值。
如果字段的值是列表,则会被展开(扁平化)加入结果;
如果字段的值是单个元素,则直接加入结果。
Args:
data_list (list[dict]): 数据列表,每个元素是字典
field_name (str): 要提取的字段名
Returns:
list: 扁平化后的字段值列表
"""
extracted_data = []
for item in data_list:
if field_name in item and item[field_name]:
# 如果是 list,展开
if isinstance(item[field_name], list):
extracted_data.extend(item[field_name])
else:
extracted_data.append(item[field_name])
return extracted_data
def extract_fields(data_list, fields):
"""
从字典列表中提取多个字段
Args:
data_list (list[dict]): 包含字典的列表
fields (list[str]): 要提取的字段名列表
Returns:
list[dict]: 只包含指定字段的新字典列表
"""
result = []
try:
for item in data_list:
extracted = {field: item.get(field) for field in fields}
result.append(extracted)
except Exception as e:
print(f"提取字段时出错: {e}")
return []
return result
def extract_field(data_list, field_name):
"""
从列表中的字典提取指定字段的所有值
Args:
data_list (list): 数据列表,每个元素是 dict
field_name (str): 要提取的字段名
Returns:
list: 提取出来的字段值列表
"""
return [item[field_name] for item in data_list if field_name in item]
def gold_to_string(gold_amount):
"""
将金钱数值转换为字符串表示形式
Args:
gold_amount (int): 金钱数值,单位为铜币
Returns:
str: 格式化后的金钱字符串,例如 "1金2银3铜"
"""
if not gold_amount:
return "无价格"
parts = []
started = False # 标记是否已经遇到第一个非零位
bricks = gold_amount // 100000000
gold = (gold_amount % 100000000) // 10000
silver = (gold_amount % 10000) // 100
copper = gold_amount % 100
for value, unit in [(bricks, ""), (gold, ""), (silver, ""), (copper, "")]:
if value != 0:
started = True
if started:
parts.append(f"{value}{unit}")
return "".join(parts)
def plot_line_chart_base64(data, x_field, y_field, title=None, reverse_x=False):
"""
根据列表数据绘制折线图,返回 base64 图片字符串。
:param data: list[dict] 数据列表
:param x_field: str X轴字段
:param y_field: str Y轴字段
:param title: str 图表标题(可选)
:param reverse_x: bool 是否反转 X 轴方向(默认 False:从左往右;True:从右往左)
:return: str base64 图片字符串,可直接放到 <img src="..."> 中
"""
if not data:
raise ValueError("数据列表不能为空")
# 字体设置(支持中文)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 提取 X / Y 数据
x_values = [str(item.get(x_field, "")) for item in data]
y_values = [float(item.get(y_field, 0)) for item in data]
# 创建画布
plt.figure(figsize=(8, 5))
plt.plot(x_values, y_values, marker='o', color='#4a90e2', linewidth=2)
# 反转 X 轴
if reverse_x:
plt.gca().invert_xaxis()
# 标题与标签
if title is None:
title = f"{y_field} 折线图"
plt.grid(True, linestyle='--', alpha=0.5)
plt.xticks(rotation=30)
plt.tight_layout()
# 转 Base64
buffer = BytesIO()
plt.savefig(buffer, format='png', dpi=150)
plt.close()
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
+495
View File
@@ -0,0 +1,495 @@
# pyright: reportArgumentType=false
# pyright: reportAttributeAccessIssue=false
# pyright: reportIndexIssue=false
# pyright: reportOptionalMemberAccess=false
# pyright: reportCallIssue=false
from datetime import datetime
from typing import Dict, Any, Optional, List, Union
import base64
from astrbot.api import logger
from astrbot.api import AstrBotConfig
from .request import APIClient
from .sqlite import AsyncSQLiteDB
from .fun_basic import load_template,extract_fields
class GOKServer:
def __init__(self, api_config, config:AstrBotConfig, sqlite:AsyncSQLiteDB ):
self._api = APIClient()
# 引用API配置文件
self._api_config = api_config
# 引用插件配置文件
self._config = config
# 引用数据库类
self._sql_db = sqlite
# 获取配置中的 Token
self.ytapi_token = self._config.get("ytapi_token", "")
if self.ytapi_token == "":
logger.warning("获取应天API令牌配置失败,请正确填写令牌,否则部分功能无法正常使用")
else:
logger.debug(f"获取应天API令牌成功。{self.ytapi_token}")
self.nyapi_token = self._config.get("nyapi_token", "")
if self.nyapi_token == "":
logger.warning("获取柠柚API令牌配置失败,请正确填写令牌,否则部分功能无法正常使用")
else:
logger.debug(f"获取柠柚API令牌成功。{self.nyapi_token}")
async def close(self):
"""释放底层 APIClient 资源"""
if self._api:
await self._api.close()
self._api = None
def _init_return_data(self) -> Dict[str, Any]:
"""初始化标准的返回数据结构"""
return {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
async def _base_request(
self,
config_key: str,
method: str,
params: Optional[Dict[str, Any]] = None,
out_key: Optional[str] = "data"
) -> Optional[Any]:
"""
基础请求封装,处理配置获取和API调用。
:param config_key: 配置字典中对应 API 的键名。
:param method: HTTP方法 ('GET''POST')。
:param params: 请求参数或 Body 数据。
:param out_key: 响应数据中需要提取的字段。
:return: 成功时返回提取后的数据,失败时返回 None。
"""
try:
api_config = self._api_config.get(config_key)
if not api_config:
logger.error(f"配置文件中未找到 key: {config_key}")
return None
# 复制 params,避免修改原始配置模板
request_params = api_config.get("params", {}).copy()
if params:
request_params.update(params)
url = api_config.get("url", "")
if not url:
logger.error(f"API配置缺少 URL: {config_key}")
return None
if method.upper() == 'POST':
data = await self._api.post(url, data=request_params, out_key=out_key)
else: # 默认为 GET
data = await self._api.get(url, params=request_params, out_key=out_key)
if not data:
logger.warning(f"获取接口信息失败或返回空数据: {config_key}")
return data
except Exception as e:
logger.error(f"基础请求调用出错 ({config_key}): {e}")
return None
# --- 业务功能函数 ---
async def helps(self) -> Dict[str, Any]:
"""功能"""
return_data = self._init_return_data()
# 加载模板
try:
return_data["temp"] = await load_template("helps.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
async def add(self,gokid: int, name: str) -> Dict[str, Any]:
"""角色添加 王者营地ID 角色"""
return_data = self._init_return_data()
# 添加数据
try:
await self._sql_db.insert(
"users",
{
"gokid": gokid,
"name": name,
}
)
except FileNotFoundError as e:
logger.error(f"添加角色失败: {e}")
return_data["msg"] = "添加角色失败"
return return_data
return_data["data"] = (
"角色添加成功\n"
f"王者营地ID{gokid}\n"
f"角色名称:{name}\n"
)
return_data["code"] = 200
return return_data
async def all(self) -> Dict[str, Any]:
"""角色查看"""
return_data = self._init_return_data()
# 查询数据
try:
data = await self._sql_db.select_all("users")
except FileNotFoundError as e:
logger.error(f"查看角色失败: {e}")
return_data["msg"] = "查看角色失败"
return return_data
if not data:
return_data["msg"] = "未找到角色数据"
return return_data
# 加载模板
try:
return_data["temp"] = await load_template("jueshe.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
# 数据处理
return_data["data"]["lists"] = data
return_data["code"] = 200
return return_data
async def select(self, name) -> Dict[str, Any]:
"""角色查询 名称"""
return_data = self._init_return_data()
# 判断输入是否为整数
try:
int(name)
if int(name) >=100000000:
like = "gokid LIKE ?"
else:
raise Exception("id数据异常")
except (ValueError, TypeError, Exception):
like = "name LIKE ?"
# 模糊拼接
like_name = f"%{name}%"
# 查询数据
try:
data = await self._sql_db.select_all(
"users",
like,
(like_name,)
)
except FileNotFoundError as e:
logger.error(f"查询角色失败: {e}")
return_data["msg"] = "查询角色失败"
return return_data
if not data:
return_data["msg"] = "未查询到角色数据"
return return_data
# 加载模板
try:
return_data["temp"] = await load_template("jueshe.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
# 数据处理
return_data["data"]["lists"] = data
return_data["code"] = 200
return return_data
async def update(self, gokid:int, name: str) -> Dict[str, Any]:
"""角色修改 王者营地ID 角色"""
return_data = self._init_return_data()
data = await self._sql_db.select_one(
"users",
"gokid=?",
(gokid,)
)
if not data:
return_data["msg"] = "没有当前ID"
return return_data
# 修改数据
try:
await self._sql_db.update(
"users",
{
"name": name,
},
"gokid=?",
(gokid,)
)
except FileNotFoundError as e:
logger.error(f"避雷修改失败: {e}")
return_data["msg"] = "避雷修改失败"
return return_data
return_data["data"] = (
"角色修改成功\n"
f"王者营地ID{gokid}\n"
f"角色名称:{name}\n"
)
return_data["code"] = 200
return return_data
async def delete(self, gokid:int) -> Dict[str, Any]:
"""角色删除 王者营地ID"""
return_data = self._init_return_data()
data = await self._sql_db.select_one(
"users",
"gokid=?",
(gokid,)
)
if not data:
return_data["msg"] = "没有当前ID"
return return_data
# 删除
try:
await self._sql_db.delete(
"users",
"gokid=?",
(gokid,)
)
except FileNotFoundError as e:
logger.error(f"角色删除失败: {e}")
return_data["msg"] = "角色删除失败"
return return_data
return_data["data"] = f"角色删除成功。王者营地ID{gokid}"
return_data["code"] = 200
return return_data
async def get_gokid(self,name: str):
# 判断输入是否为整数
try:
# 直接返回输入
int(name)
if int(name) >=10000000:
gokid = name
return gokid
else:
raise Exception("输入不是ID")
except (ValueError, TypeError, Exception):
# 查询数据
try:
data = await self._sql_db.select_one(
"users",
"name=?",
(name,)
)
if not data:
gokid = None
return gokid
gokid = data['gokid']
return gokid
except FileNotFoundError as e:
logger.error(f"查询角色失败: {e}")
gokid = None
return gokid
async def zhanji(self,name: str ,option: str):
"""
战绩查询
"""
return_data = self._init_return_data()
# 获取配置中的 Token
if self.ytapi_token == "":
return_data["msg"] = "系统未配置API访问Token"
return return_data
# ID查询
gokid = await self.get_gokid(name)
if not gokid :
return_data["msg"] = "未查询到该用户,请确认输入正确的角色或营地ID"
return return_data
#更新参数
params = {"id": gokid, "option": option, "key": self.ytapi_token}
# 需要提取的字段
fields = ["gametime","killcnt","deadcnt","assistcnt","gameresult","mvpcnt","losemvp","mapName",
"oldMasterMatchScore","newMasterMatchScore","usedTime","winNum","failNum","roleJobName","stars","desc",
"gradeGame","heroIcon","godLikeCnt", "firstBlood","hero1TripleKillCnt","hero1UltraKillCnt","hero1RampageCnt","evaluateUrlV3","mvpUrlV3"]
# 锐评提取字段
comment = ["gametime","killcnt","deadcnt","assistcnt","gameresult","mvpcnt","losemvp","gradeGame"]
# 获取数据
data: Optional[List[Dict[str, Any]]] = await self._base_request("gok_zhanji", "GET", params=params)
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
# 提取锐评数据
return_data["comment"] = {}
comments = extract_fields(data['list'], comment)
comments = comments[:10]
return_data["comment"]["data"] = comments
# 提取字段
result = extract_fields(data['list'], fields)
result = result[:25]
# 数据处理
for m in result:
minutes = m["usedTime"] // 60
seconds = m["usedTime"] % 60
m["time_str"] = f"{minutes}:{seconds:02d}"
return_data["data"]["data"] = result
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
# 加载模板
try:
return_data["temp"] = await load_template("wangzhezhanji.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
async def ziliao(self, name: str):
return_data = self._init_return_data()
# 获取配置中的 Token
if self.ytapi_token == "":
return_data["msg"] = "系统未配置API访问Token"
return return_data
# ID查询
gokid = await self.get_gokid(name)
if not gokid :
return_data["msg"] = "未查询到该用户,请确认输入正确的角色或营地ID"
return return_data
#更新参数
params = {"id": gokid, "key": self.ytapi_token}
# 获取数据
data: Optional[List[Dict[str, Any]]] = await self._base_request("gok_ziliao", "GET", params=params, out_key="")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
try:
# 转 base64
return_data["data"]["img_base64"] = base64.b64encode(data).decode("utf-8")
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
# 加载模板
try:
return_data["temp"] = await load_template("wzry_zl.html")
except FileNotFoundError:
logger.error(f"加载模板失败")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
async def zhanli(self,hero: str, type: str):
return_data = self._init_return_data()
# 获取配置中的 Token
if self.nyapi_token == "":
return_data["msg"] = "系统未配置API访问Token"
return return_data
#更新参数
params = {"hero": hero, "type": type, "apikey": self.nyapi_token}
# 获取数据
data: Optional[List[Dict[str, Any]]] = await self._base_request("gok_zhanli", "GET", params=params)
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
try:
data0 = data['info']
msg = "英雄的最低上榜地区战力\n"
msg += f"英雄:{data0['name']}\n"
msg += f"省标:{data0['province']}--战力:{data0['provincePower']}\n"
msg += f"市标:{data0['city']}--战力:{data0['cityPower']}\n"
msg += f"区标:{data0['area']}--战力:{data0['areaPower']}\n"
msg += f"数据更新时间:{data0['updatetime']}\n"
return_data["data"] = msg
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
return_data["code"] = 200
return return_data
+6 -7
View File
@@ -1,9 +1,10 @@
# core/request.py
import json
import aiohttp
import asyncio
from typing import Optional, Dict, Any, Union, List
from aiohttp import ClientTimeout, ClientSession
from astrbot.api import logger
class APIClient:
@@ -32,6 +33,7 @@ class APIClient:
"""关闭 Session"""
if self._session and not self._session.closed:
await self._session.close()
self._session = None
async def __aenter__(self):
await self.get_session()
@@ -74,23 +76,20 @@ class APIClient:
"""处理响应:自动识别二进制或JSON"""
try:
logger.debug(f"响应状态: {response.status}")
response.raise_for_status() # 如果是 4xx/5xx 直接抛出异常
response.raise_for_status()
content_type = response.headers.get('Content-Type', '').lower()
# 处理二进制流 (图片、文件等)
if 'image' in content_type or 'octet-stream' in content_type:
return await response.read()
# 处理 JSON
# 优先尝试标准 json 解析
try:
data = await response.json()
except Exception:
# 容错:有些 API 返回 text/html 但内容是 JSON
text = await response.text()
try:
data = json.loads(text)
loop = asyncio.get_running_loop()
data = await loop.run_in_executor(None, json.loads, text)
except json.JSONDecodeError:
logger.error(f"无法解析响应为 JSON。原始内容: {text[:100]}...")
return None
+71
View File
@@ -0,0 +1,71 @@
# pyright: reportOptionalMemberAccess=false
import aiosqlite
from typing import Any, Dict, List, Optional, Tuple
class AsyncSQLiteDB:
def __init__(self, db_path: str = "data.db"):
self.db_path = db_path
self.conn: Optional[aiosqlite.Connection] = None
# ======================
# 生命周期
# ======================
async def connect(self):
self.conn = await aiosqlite.connect(self.db_path)
self.conn.row_factory = aiosqlite.Row
async def close(self):
if self.conn:
await self.conn.close()
# ======================
# 基础执行
# ======================
async def execute(self, sql: str, params: Tuple = ()):
async with self.conn.execute(sql, params):
await self.conn.commit()
async def fetch_one(self, sql: str, params: Tuple = ()) -> Optional[Dict[str, Any]]:
async with self.conn.execute(sql, params) as cursor:
row = await cursor.fetchone()
return dict(row) if row else None
async def fetch_all(self, sql: str, params: Tuple = ()) -> List[Dict[str, Any]]:
async with self.conn.execute(sql, params) as cursor:
rows = await cursor.fetchall()
return [dict(r) for r in rows]
# ======================
# CRUD
# ======================
async def insert(self, table: str, data: Dict[str, Any]):
keys = ", ".join(data.keys())
placeholders = ", ".join(["?"] * len(data))
sql = f"INSERT INTO {table} ({keys}) VALUES ({placeholders})"
await self.execute(sql, tuple(data.values()))
async def update(self, table: str, data: Dict[str, Any], where: str, params: Tuple):
set_clause = ", ".join([f"{k}=?" for k in data.keys()])
sql = f"UPDATE {table} SET {set_clause} WHERE {where}"
await self.execute(sql, tuple(data.values()) + params)
async def delete(self, table: str, where: str, params: Tuple):
sql = f"DELETE FROM {table} WHERE {where}"
await self.execute(sql, params)
async def select_one(self, table: str, where: str = "", params: Tuple = ()):
sql = f"SELECT * FROM {table}"
if where:
sql += f" WHERE {where}"
return await self.fetch_one(sql, params)
async def select_all(self, table: str, where: str = "", params: Tuple = ()):
sql = f"SELECT * FROM {table}"
if where:
sql += f" WHERE {where}"
return await self.fetch_all(sql, params)
-14
View File
@@ -1,14 +0,0 @@
import http.client
conn = http.client.HTTPSConnection("api.t1qq.com")
payload = ''
headers = {
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Accept': '*/*',
'Host': 'api.t1qq.com',
'Connection': 'keep-alive'
}
conn.request("GET", "/api/tool/wzrr/ydtp?key=vBpEzoiC9z5A9c9Nn83IhLn6M9&id=489048724", payload, headers)
res = conn.getresponse()
data = res.read()
print(data)
+32
View File
@@ -0,0 +1,32 @@
{
"gok_zhanji":{
"url":"https://api.t1qq.com/api/tool/wzrr/morebattle",
"method":"GET",
"description":"王者荣耀战绩查询",
"params":{
"key": "",
"id": "",
"option": "1"
}
},
"gok_ziliao":{
"url":"https://api.t1qq.com/api/tool/wzrr/ydtp",
"method":"GET",
"description":"王者荣耀资料查询",
"params":{
"key": "",
"id": ""
}
},
"gok_zhanli":{
"url":"https://api.nycnm.cn/API/wzzl.php",
"method":"GET",
"description":"王者英雄战力最低上榜战力",
"params":{
"hero": "",
"type": "",
"format": "json",
"apikey": ""
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

+294 -184
View File
@@ -1,217 +1,327 @@
# pyright: reportOptionalMemberAccess=false
# pyright: reportCallIssue=false
# pyright: reportArgumentType=false
import json
import asyncio
import inspect
from pathlib import Path
from datetime import datetime
from astrbot.api.event import filter, AstrMessageEvent, MessageEventResult, MessageChain
from astrbot.api.star import Context, Star, register, StarTools
from astrbot.api import logger
from astrbot.api import AstrBotConfig
from .core.async_mysql import AsyncMySQL
from .core.WZRYFunction import WZRYFunction
from .core.sqlite import AsyncSQLiteDB
from .core.gok_data import GOKServer
@register("astrbot_plugin_gok",
"飞翔大野猪",
"通过接口接口获取王者荣耀游戏数据",
"1.0.0",
"https://github.com/qsc20001102/astrbot_plugin_gok"
"fxdyz",
"通过接口获取王者荣耀游戏数据",
"1.0.3",
"https://github.com/qsc20001102/astrbot_plugin_gok.git"
)
class GokApiPlugin(Star):
def __init__(self, context: Context, config: AstrBotConfig):
super().__init__(context)
#获取配置
self.conf = config
# 本地数据存储路径
self.local_data_dir = StarTools.get_data_dir("astrbot_plugin_gok")
# api数据文件
self.api_file_path = Path(__file__).parent / "api_config.json"
# 读取文件内容
# SQLite本地路径
self.sqlite_path = Path(self.local_data_dir) /"sqlite.db"
logger.info(f"SQLite数据文件路径:{self.sqlite_path}")
# 读取API配置文件
self.api_file_path = Path(__file__).parent / "data" / "api_config.json"
with open(self.api_file_path, 'r', encoding='utf-8') as f:
self.api_config = json.load(f)
# 初始化数据
logger.info("王者荣耀插件初始化完成")
# 声明指令集
self.command_map = {}
# 指令前缀功能
self.prefix_en = self.conf.get("prefix").get("enable")
self.prefix_text = self.conf.get("prefix").get("text")
if not self.prefix_text:
self.prefix_text = "王者"
if self.prefix_en:
logger.info(f"已启用指令前缀功能,前缀为:{self.prefix_text}")
else:
logger.info(f"未启用指令前缀功能。")
# 战绩锐评功能
self.comment_en = self.conf.get("comment").get("enable")
self.comment_provider = self.conf.get("comment").get("select_provider")
if self.comment_en:
logger.info(f"锐评功能已经启用,模型为:{self.comment_provider}")
else:
logger.info(f"未启用锐评功能")
logger.info("GOK 插件初始化完成")
async def initialize(self):
"""可选择实现异步的插件初始化方法,当实例化该插件类之后会自动调用该方法。"""
# 数据库配置
db_config = {
'host': '38.12.28.24',
'port': 3306,
'user': 'asrtbot',
'password': 'qsc123456',
'db': 'asrtbot',
'charset': 'utf8mb4',
'autocommit': True
}
#创建类实例
self.db = AsyncMySQL(db_config)
self.wzry = WZRYFunction(self.api_config,self.db)
# 周期函数调用
logger.info("王者荣耀插件创建实例完成")
@filter.command_group("王者")
def wz(self):
pass
@wz.command("列表")
async def wz_yingdilist(self, event: AstrMessageEvent):
"""王者 用户列表"""
try:
data = await self.wzry.all_user()
logger.info(f"王者荣耀营地列表查询结果{data}")
yield event.plain_result(data)
# sqlite 实例化
self.sql_db = AsyncSQLiteDB(self.sqlite_path)
await self.sql_db.connect()
await self.sql_db.execute("""
CREATE TABLE IF NOT EXISTS users(
gokid INTEGER,
name TEXT
)
""")
# 王者功能 实例化
self.gokfun = GOKServer(self.api_config, self.conf, self.sql_db)
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
logger.error(f"功能模块初始化失败: {e}")
raise
@wz.command("添加")
async def wz_adduesr(self, event: AstrMessageEvent,id: str,name: str):
"""王者 添加用户"""
try:
data = await self.wzry.add_user(id,name)
yield event.plain_result(data)
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
# 指令集
self.ini_command_map()
@wz.command("修改")
async def wz_updateuser(self, event: AstrMessageEvent,id: str,name: str):
"""王者 修改用户"""
try:
data = await self.wzry.update_user(id,name)
yield event.plain_result(data)
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@wz.command("删除")
async def wz_deleteuser(self, event: AstrMessageEvent,id: str):
"""王者 删除用户"""
try:
data = await self.wzry.delete_user(id)
yield event.plain_result(data)
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@wz.command("战绩")
async def wz_zhanji(self, event: AstrMessageEvent,name: str = "飞翔大野猪",option: str = "0"):
"""王者 战绩 营地ID 对局类型"""
try:
data = await self.wzry.zhanji(name,option)
# logger.info(f"王者荣耀战绩查询结果{data}")
#prompt = f"{data['data']}\n"
#prompt += f"以上数据是获取到的王者荣耀战绩信息,请根据这些数据生成一两句话的战绩总结,突出玩家的优势和特点,语言风格轻松幽默,适合在游戏群内分享。"
if data["code"] == 200:
url = await self.html_render(data["temp"],{"data":data["data"]}, options={})
yield event.image_result(url)
else:
yield event.plain_result(data["msg"])
return
#provider_id = await self.context.get_current_chat_provider_id(umo=event.unified_msg_origin)
#llm_resp = await self.context.llm_generate(chat_provider_id=provider_id, prompt=prompt,)
#out = llm_resp.completion_text
#yield event.plain_result(f"{out}")
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@wz.command("资料")
async def wz_ziliao(self, event: AstrMessageEvent,name: str = "489048724"):
"""王者 战绩 营地ID 对局类型"""
try:
data = await self.wzry.ziliao(name)
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("猪脑过载,请稍后再试")
@filter.command_group("仇人")
def bilei(self):
pass
@bilei.command("列表")
async def bilei_list(self, event: AstrMessageEvent,):
"""仇人 列表"""
try:
data = await self.wzry.bilei_all()
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("猪脑过载,请稍后再试")
@bilei.command("添加")
async def bilei_add(self, event: AstrMessageEvent,name: str, text: str):
"""仇人 添加 名称 备注"""
try:
data = await self.wzry.bilei_add(name, text, event.get_sender_name())
yield event.plain_result(data["msg"])
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@bilei.command("查找")
async def bilei_select(self, event: AstrMessageEvent,name: str):
"""仇人 查找 名称(模糊查找)"""
try:
data = await self.wzry.bilei_select(name)
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("猪脑过载,请稍后再试")
@bilei.command("修改")
async def bilei_update(self, event: AstrMessageEvent,id:int, name: str, text: str):
"""仇人 修改 ID 名称 备注"""
try:
data = await self.wzry.bilei_update(id, name, text, event.get_sender_name())
yield event.plain_result(data["msg"])
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@bilei.command("删除")
async def bilei_delete(self, event: AstrMessageEvent,id:int):
"""仇人 删除 ID """
try:
data = await self.wzry.bilei_delete(id)
yield event.plain_result(data["msg"])
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
logger.info("GOK 异步插件初始化完成")
async def terminate(self):
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
await self.db.close_pool()
# 后台进程销毁
logger.info("王者荣耀插件已卸载/停用")
if self.gokfun:
await self.gokfun.close()
self.gokfun = None
if self.sql_db:
await self.sql_db.close()
self.sql_db = None
logger.info("GOK 插件已卸载/停用")
def parse_message(self, text: str) -> list[str] | None:
"""消息解析"""
text = text.strip()
if not text:
return None
# 前缀模式
if self.prefix_en:
prefix = self.prefix_text
if text.startswith(prefix):
text = text[len(prefix):].strip()
else:
# 非前缀消息,直接忽略
return None
return text.split()
async def _call_with_auto_args(self, handler, event: AstrMessageEvent, args: list[str]):
"""指令执行函数"""
sig = inspect.signature(handler)
params = list(sig.parameters.values())
call_args = []
arg_index = 0
for p in params:
if p.name == "self":
continue
if p.name == "event":
call_args.append(event)
continue
if arg_index < len(args):
raw = args[arg_index]
arg_index += 1
try:
if p.annotation is int:
call_args.append(int(raw))
elif p.annotation is float:
call_args.append(float(raw))
else:
call_args.append(raw)
except Exception:
call_args.append(p.default)
else:
if p.default is not inspect._empty:
call_args.append(p.default)
else:
raise ValueError(f"缺少参数: {p.name}")
# 只允许 coroutine
return await handler(*call_args)
@filter.event_message_type(filter.EventMessageType.ALL)
async def on_all_message(self, event: AstrMessageEvent):
"""解析所有消息"""
if not self.command_map:
logger.debug("插件尚未初始化完成,忽略消息")
return
parts = self.parse_message(event.message_str)
if not parts:
logger.debug("未触发指令,忽略消息")
return
cmd, *args = parts
handler = self.command_map.get(cmd)
if not handler:
logger.debug("指令函数为空,忽略消息")
return
try:
event.stop_event()
ret = await self._call_with_auto_args(handler, event, args)
if ret is not None:
yield ret
except Exception as e:
logger.exception(f"指令执行失败: {cmd}, error={e}")
yield event.plain_result("参数错误或执行失败")
def ini_command_map(self):
"""初始化指令集"""
self.command_map = {
"功能": self.gok_helps,
"战绩": self.gok_zhanji,
"资料": self.gok_ziliao,
"上榜战力": self.gok_zhanli,
"角色查看": self.gok_user_all,
"角色添加": self.gok_user_add,
"角色修改": self.gok_user_update,
"角色删除": self.gok_user_delete,
"角色查询": self.gok_user_select
}
async def plain_msg(self, event: AstrMessageEvent, action):
"""最终将数据整理成文本发送"""
data= await action()
try:
if data["code"] == 200:
await event.send( event.plain_result(data["data"]))
else:
await event.send(event.plain_result(data["msg"]))
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
await event.send(event.plain_result("猪脑过载,请稍后再试"))
async def T2I_image_msg(self, event: AstrMessageEvent, action):
"""最终将数据渲染成图片发送"""
data = await action()
try:
if data["code"] == 200:
url = await self.html_render(data["temp"], data["data"], options={})
await event.send(event.image_result(url))
else:
await event.send(event.plain_result(data["msg"]))
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
await event.send(event.plain_result("猪脑过载,请稍后再试"))
async def image_msg(self, event: AstrMessageEvent, action):
"""最终将数据整理成图片发送"""
data = await action()
try:
if data["code"] == 200:
await event.send(event.image_result(data["data"]))
else:
await event.send(event.plain_result(data["msg"]))
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
await event.send(event.plain_result("猪脑过载,请稍后再试"))
async def T2I_image_and_plain_msg(self, event: AstrMessageEvent, action):
"""战绩定制功能"""
data = await action()
# 发送渲染战绩图片
try:
if data["code"] == 200:
url = await self.html_render(data["temp"], data["data"], options={})
await event.send(event.image_result(url))
else:
await event.send(event.plain_result(data["msg"]))
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
await event.send(event.plain_result("猪脑过载,请稍后再试"))
# 对战绩进行锐评
try:
if data["code"] == 200 and self.comment_en:
# 确定使用模型
if self.comment_provider == "":
umo = event.unified_msg_origin
provider_id = await self.context.get_current_chat_provider_id(umo=umo)
else:
provider_id = self.comment_provider
# 模型提示词构建
prompt = "请根据下面提供的王者荣耀最近10把的战绩数据,用简短的一句话进行锐评吐槽。"
prompt += f"这是战绩列表\n{data['comment']['data']}\n"
prompt += f"gametime 字段 对局开始时间\n"
prompt += f"killcnt 字段 击杀数\n"
prompt += f"deadcnt 字段 死亡数\n"
prompt += f"assistcnt 字段 助攻数\n"
prompt += f"gameresult 字段 1代表胜利 2代表失败 3代表平局\n"
prompt += f"mvpcnt 字段 1代表是胜利方MVP 0表示不是\n"
prompt += f"losemvp 字段 1代表是失败方MVP 0表示不是\n"
prompt += f"gradeGame 字段 系统给的评分,满分16分\n"
# 调用模型
llm_resp = await self.context.llm_generate(chat_provider_id=provider_id, prompt=prompt)
# 发送消息
await event.send(event.plain_result(llm_resp.completion_text))
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
await event.send(event.plain_result("猪脑过载,请稍后再试"))
async def gok_helps(self, event: AstrMessageEvent):
"""王者功能"""
return await self.T2I_image_msg(event, self.gokfun.helps)
async def gok_zhanji(self, event: AstrMessageEvent,name: str,option:str = 0):
"""王者战绩"""
return await self.T2I_image_and_plain_msg(event, lambda: self.gokfun.zhanji(name ,option))
async def gok_ziliao(self, event: AstrMessageEvent,name: str):
"""王者资料"""
return await self.T2I_image_msg(event, lambda: self.gokfun.ziliao(name))
async def gok_zhanli(self, event: AstrMessageEvent, hero: str, type: str = "aqq"):
"""英雄战力 名称 大区"""
return await self.plain_msg(event, lambda: self.gokfun.zhanli(hero,type))
async def gok_user_all(self, event: AstrMessageEvent):
"""角色查看"""
return await self.T2I_image_msg(event, self.gokfun.all)
async def gok_user_add(self, event: AstrMessageEvent, gokid: int, name: str):
"""角色添加 王者营地ID 名称"""
return await self.plain_msg(event, lambda: self.gokfun.add(gokid,name))
async def gok_user_update(self, event: AstrMessageEvent, gokid: int, name: str):
"""角色修改 王者营地ID 名称"""
return await self.plain_msg(event, lambda: self.gokfun.update(gokid,name))
async def gok_user_delete(self, event: AstrMessageEvent, gokid:int):
"""角色删除 王者营地ID"""
return await self.plain_msg(event, lambda: self.gokfun.delete(gokid))
async def gok_user_select(self, event: AstrMessageEvent, gokid):
"""角色查询 王者营地ID"""
return await self.T2I_image_msg(event, lambda: self.gokfun.select(gokid))
+9 -6
View File
@@ -1,6 +1,9 @@
name: astrbot_plugin_gok # 这是你的插件的唯一识别名。
display_name: 王者荣耀数据查询工具 # 插件的显示名称
desc: 王者荣耀的战绩、资料等内容的查询 # 插件简短描述
version: v1.0.0 # 插件版本号。格式:v1.1.1 或者 v1.1
author: 飞翔大野猪 # 作者
repo: https://github.com/qsc20001102/astrbot_plugin_gok # 插件的仓库地址
name: astrbot_plugin_gok
display_name: 王者荣耀游戏数据查询工具
desc: 王者荣耀的战绩、资料等内容的查询。数据来源王者营地,可查询在游戏内隐藏的战绩,营地也隐藏就不行了。
version: v1.0.3
author: 飞翔大野猪
repo: https://github.com/qsc20001102/astrbot_plugin_gok
astrbot_version: ">=4.11.0"
support_platforms:
- aiocqhttp
+1 -2
View File
@@ -1,2 +1 @@
aiomysql
matplotlib
aiosqlite
+159
View File
@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>王者插件功能</title>
<style>
body {
margin: 0;
padding: 40px 20px;
background: linear-gradient(180deg, #f5f7fb 0%, #eef1f7 100%);
color: #2c2c33;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
/* 页面主体宽度控制 */
.container {
max-width: 980px;
margin: 0 auto;
}
/* 顶部标题 */
.header {
margin-bottom: 36px;
}
.header h1 {
font-size: 42px;
margin: 0 0 10px;
color: #1f2a44;
}
.header p {
font-size: 20px;
color: #5a627a;
margin: 0;
}
/* 分组 */
.group {
margin-bottom: 42px;
}
.group-title {
font-size: 30px;
font-weight: bold;
margin-bottom: 18px;
padding-left: 14px;
border-left: 6px solid #4f7cff;
color: #1f2a44;
}
/* 单列 */
.command-grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
/* 指令行 */
.command {
background: #ffffff;
border-radius: 12px;
padding: 14px 18px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
gap: 18px;
}
.command::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 4px;
height: 100%;
background: #4f7cff;
}
.cmd-name {
font-size: 24px;
font-weight: bold;
color: #2b3a67;
white-space: nowrap;
min-width: 140px;
}
.cmd-usage {
font-size: 22px;
color: #1f2a44;
background: #f0f3ff;
padding: 6px 12px;
border-radius: 6px;
font-family: Consolas, "Courier New", monospace;
white-space: nowrap;
}
/* 提示 */
.tip {
margin-top: 40px;
font-size: 16px;
color: #5a5f73;
background: #fffbe6;
padding: 16px 20px;
border-left: 6px solid #ffcc00;
border-radius: 10px;
line-height: 1.6;
}
/* 小屏适配 */
@media (max-width: 640px) {
.cmd-name {
min-width: auto;
}
.command {
flex-wrap: wrap;
gap: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>王者插件功能</h1>
<p>astrbot_plugin_gok 指令说明与使用示例</p>
</div>
<div class="group">
<div class="group-title">基础功能</div>
<div class="command-grid">
<div class="command"><div class="cmd-name">功能</div><div class="cmd-usage">功能</div></div>
<div class="command"><div class="cmd-name">对局战绩</div><div class="cmd-usage">战绩 角色/营地ID</div></div>
<div class="command"><div class="cmd-name">角色资料</div><div class="cmd-usage">资料 角色/营地ID</div></div>
<div class="command"><div class="cmd-name">上榜战力</div><div class="cmd-usage">上榜战力 英雄 大区</div></div>
<div class="command"><div class="cmd-name">角色查看</div><div class="cmd-usage">角色查看</div></div>
<div class="command"><div class="cmd-name">角色添加</div><div class="cmd-usage">角色添加 营地ID 角色</div></div>
<div class="command"><div class="cmd-name">角色修改</div><div class="cmd-usage">角色修改 营地ID 角色</div></div>
<div class="command"><div class="cmd-name">角色删除</div><div class="cmd-usage">角色删除 营地ID</div></div>
<div class="command"><div class="cmd-name">角色查询</div><div class="cmd-usage">角色查询 营地ID/角色</div></div>
</div>
</div>
<div class="tip">
<strong>使用提示:</strong><br>
1. 指令里面的角色指的是自定义的查询名称<br>
2. 战绩和资料的指令必须配置令牌才可使用<br>
3. 如果启用指令前缀功能,需要在指令前面加上设定的前缀<br>
</div>
</div>
</body>
</html>
+12 -17
View File
@@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>王者荣耀 — 仇人列表</title>
<title>角色查看</title>
<style>
body {
@@ -19,7 +19,7 @@
}
h1 {
font-size: 32px;
font-size: 48px;
margin-bottom: 25px;
color: #222;
font-weight: 800;
@@ -27,26 +27,27 @@
}
table {
width: 100%;
width: 66%;
border-collapse: collapse;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
margin-top: 10px;
text-align: left; /* 不影响表格内容 */
text-align: left;
margin: 10px auto;
}
thead {
background: #d93939;
color: white;
font-size: 18px;
font-size: 27px;
}
th, td {
padding: 14px 12px;
border-bottom: 1px solid #eee;
font-size: 16px;
font-size: 24px;
}
tbody tr:hover {
@@ -65,27 +66,21 @@
<body>
<div class="container">
<h1>仇人列表</h1>
<h1>角色查看</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>描述</th>
<th>时间</th>
<th>记录者</th>
<th>王者营地ID</th>
<th>角色名称</th>
</tr>
</thead>
<tbody>
{% for m in lists %}
<tr>
<td>{{ m.id }}</td>
<td>{{ m.gokid }}</td>
<td>{{ m.name }}</td>
<td class="text-col">{{ m.text }}</td>
<td>{{ m.time }}</td>
<td>{{ m.user }}</td>
</tr>
{% endfor %}
</tbody>
@@ -94,4 +89,4 @@
</div>
</body>
</html>
</html>
-97
View File
@@ -1,97 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>王者荣耀 — 仇人列表</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: #f1f2f6;
margin: 0;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
text-align: center; /* ★ 让容器内标题更自然居中 */
}
h1 {
font-size: 32px;
margin-bottom: 25px;
color: #222;
font-weight: 800;
text-align: center; /* ★ 强制居中 */
}
table {
width: 100%;
border-collapse: collapse;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
margin-top: 10px;
text-align: left; /* 不影响表格内容 */
}
thead {
background: #d93939;
color: white;
font-size: 18px;
}
th, td {
padding: 14px 12px;
border-bottom: 1px solid #eee;
font-size: 16px;
}
tbody tr:hover {
background: #fff2f2;
}
.text-col {
max-width: 360px;
line-height: 1.5em;
word-break: break-word;
font-weight: 600;
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h1>仇人列表</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>描述</th>
<th>时间</th>
<th>记录者</th>
</tr>
</thead>
<tbody>
{% for m in lists %}
<tr>
<td>{{ m.id }}</td>
<td>{{ m.name }}</td>
<td class="text-col">{{ m.text }}</td>
<td>{{ m.time }}</td>
<td>{{ m.user }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
-14
View File
@@ -1,14 +0,0 @@
import http.client
conn = http.client.HTTPSConnection("api.t1qq.com")
payload = ''
headers = {
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Accept': '*/*',
'Host': 'api.t1qq.com',
'Connection': 'keep-alive'
}
conn.request("GET", "/api/tool/wzrr/ydtp?key=vBpEzoiC9z5A9c9Nn83IhLn6M9&id=489048724", payload, headers)
res = conn.getresponse()
data = res.read()
print(data)