This commit is contained in:
qsc
2025-12-03 10:18:46 +08:00
parent 520dd5b5b7
commit 3478aa977a
20 changed files with 2237 additions and 22 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# helloworld # JX3API
AstrBot 插件模板 AstrBot 插件模板
+36
View File
@@ -0,0 +1,36 @@
{
"jx3_kfjk_en": {
"description": "开服监控功能",
"type": "bool",
"default": true,
"hint": "是否启用剑网三开服监控功能。"
},
"jx3_kfjk_time": {
"description": "开服监控循环时间",
"type": "int",
"default": 5,
"hint": "请求服务器状态的循环时间,单位秒。"
},
"jx3_kfjk_server": {
"description": "开服监控服务器",
"type": "string",
"default": "梦江南",
"hint": "请求服务器状态的名称,要官方名称。"
},
"jx3_kfjk_list": {
"description": "开服监控推送列表",
"type": "list",
"hint": "可以填写多个会话唯一ID。",
"items": {
"type": "string",
"description": "会话唯一ID"
},
"default": ["default:GroupMessage:768280021","default:GroupMessage:690665795"]
},
"jx3_kfjk_test": {
"description": "开服监控功能测试",
"type": "bool",
"default": false,
"hint": "可以手动修改服务器的状态,进行推送测试。"
}
}
+149
View File
@@ -0,0 +1,149 @@
{
"jx3_richang":{
"url":"https://www.jx3api.com/data/active/calendar",
"method":"GET",
"description":"获取剑网三日常任务",
"params":{
"server": "眉间雪",
"num": 0
}
},
"jx3_shaohua":{
"url":"https://www.jx3api.com/data/saohua/random",
"method":"GET",
"description":"获取剑网三骚话",
"params":{
"name": "万花"
}
},
"jx3_jigai":{
"url":"https://www.jx3api.com/data/skills/records",
"method":"GET",
"description":"获取剑网三技能记录",
"params":{
}
},
"jx3_kaifu":{
"url":"https://www.jx3api.com/data/server/check",
"method":"GET",
"description":"剑网三开服查询",
"params":{
"server": "梦江南"
}
},
"aijx3_shapan":{
"url":"https://www.jianxiachaguan.cn/api2/aijx3-jxcg/game/get-sand-table-img",
"method":"POST",
"description":"获取剑网三沙盘状态",
"params":{
"serverName": "眉间雪"
}
},
"aijx3_jinjia":{
"url":"https://www.jianxiachaguan.cn/api2/aijx3-jxcg/game/get-gold",
"method":"POST",
"description":"获取剑网三某区服金价",
"params":{
"serverName": "眉间雪"
}
},
"aijx3_qiyu":{
"url":"https://www.jianxiachaguan.cn/api2/aijx3-jxcg/game/get-adventure-record",
"method":"POST",
"description":"获取剑网三某区服奇遇触发记录",
"params":{
"adventureName": "阴阳两界",
"serverName": "眉间雪"
}
},
"aijx3_SearchData":{
"url":"https://www.aijx3.cn/api2/aijx3-wblwg/basedata/getSearchData",
"method":"POST",
"description":"获取爱剑网三所有外观数据",
"params":{
}
},
"jx3box_exterior":{
"url":"https://node.jx3box.com/api/node/v2/exterior/list",
"method":"GET",
"description":"魔盒中模糊查找剑网三的外观数据",
"params":{
"keyword": "秃盒",
"tradable": "0",
"list_type": "0"
}
},
"aijx3_GoodsDetail":{
"url":"https://www.aijx3.cn/api2/aijx3-wj/goods/getGoodsDetail",
"method":"POST",
"description":"在爱剑网三中获取外观详细数据",
"params":{
"goodsName": "秃盒"
}
},
"aijx3_wblwg":{
"url":"https://www.aijx3.cn/api2/aijx3-wblwg/record/queryByCondition",
"method":"POST",
"description":"在爱剑网三中获取外观详细数据",
"params":{
"accoSeq": "",
"orderMode": 1,
"orderBy": "price_num",
"searchId": [48356],
"current": 1,
"size": 10,
"tradeStatus": ""
}
},
"jx3box_item":{
"url":"https://node.jx3box.com/api/node/item/search",
"method":"GET",
"description":"魔盒中模糊查找剑网三的物品数据",
"params":{
"keyword": "守缺式",
"page": "1",
"per": "50"
}
},
"jx3box_itemprice":{
"url":"https://next2.jx3box.com/api/item-price/list",
"method":"GET",
"description":"交易行中在售物品数据",
"params":{
"server": "眉间雪",
"itemIds": ["5_76650"]
}
},
"wbl_waiguan":{
"url":"https://trade-api.seasunwbl.com/api/buyer/goods/list",
"method":"GET",
"description":"万宝楼官方交易数据",
"params":{
"filter[state]": "0",
"filter[role_appearance]": "浮屠明音礼盒",
"page": "1",
"size": "10",
"goods_type": "3",
"sort[price]": "1"
}
},
"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"
}
}
}
+150
View File
@@ -0,0 +1,150 @@
# core/request.py
import aiohttp
import json
from aiohttp import ClientTimeout
from astrbot.api import logger
class APIClient:
"""
API客户端类,支持GET/POST请求,同时兼容JSON和二进制数据
"""
def __init__(self, base_timeout=10, ssl_verify=False):
self.base_timeout = base_timeout
self.ssl_verify = ssl_verify
async def _make_request(self, method, url, params_data=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':
async with session.get(url, params=params_data, ssl=self.ssl_verify) as response:
return await self._handle_response(response)
elif method.upper() == 'POST':
headers = {'Content-Type': 'application/json'}
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
except Exception as e:
logger.error(f"未知错误: {e}")
return None
async def _handle_response(self, response):
"""
处理响应:支持 JSON 和二进制数据
"""
try:
logger.debug(f"响应状态: {response.status}")
response.raise_for_status()
content_type = response.headers.get('Content-Type', '')
if 'image' in content_type or 'octet-stream' in content_type:
# 返回二进制数据
data = await response.read()
return data
# 尝试解析为 JSON
try:
data = await response.json(content_type=None)
except Exception:
text = await response.text()
logger.debug(f"原始文本响应: {text}")
try:
data = json.loads(text)
except json.JSONDecodeError:
logger.error("无法解析为 JSON 数据")
return None
logger.debug(f"响应数据: {data}")
return self._check_response_data(data)
except aiohttp.ClientError as e:
logger.error(f"HTTP错误: {e}")
return None
except Exception as e:
logger.error(f"未知错误: {e}")
return None
def _check_response_data(self, data):
"""
检查 API 返回 JSON 数据
"""
if isinstance(data, str):
try:
data = json.loads(data)
except json.JSONDecodeError:
logger.error("响应数据是无效的JSON字符串")
return None
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
elif not data:
logger.error("API返回空数据")
return None
return data
async def post(self, api_url, params_data=None, outdata=None):
data = await self._make_request('POST', api_url, params_data)
if data is None:
return None
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):
data = await self._make_request('GET', api_url, params_data)
if data is None:
return None
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):
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 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
current_page += 1
logger.info(f"已获取第 {current_page} 页数据")
return all_data
# 保持原接口兼容
async def api_data_post(api_url, params_data=None, outdata=None):
client = APIClient()
return await client.post(api_url, params_data, outdata)
async def api_data_get(api_url, params_data=None, outdata=None):
client = APIClient()
return await client.get(api_url, params_data, outdata)
+90
View File
@@ -0,0 +1,90 @@
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:
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:
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:
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:
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:
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()))
+536
View File
@@ -0,0 +1,536 @@
from datetime import datetime
from astrbot.api import logger
from .APIClient import APIClient
from .AsyncMySQL import AsyncMySQL
from .function_basic import load_template,extract_field,flatten_field,extract_fields,gold_to_string,plot_line_chart_base64
class JX3Function:
def __init__(self, api_config,db: AsyncMySQL ):
self.__api = APIClient()
self.__db = db
self.__api_config = api_config
async def richang(self,server: str = "眉间雪",num: int = 0):
"""
日常活动
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3_richang"]
#更新参数
api_config["params"]["server"] = server
api_config["params"]["num"] = num
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
result_msg = f"{server}-{data.get('date')}-星期{data.get('week')}\n"
result_msg += f"大战:{data.get('war')}\n"
result_msg += f"战场:{data.get('battle')}\n"
result_msg += f"阵营:{data.get('orecar')}\n"
result_msg += f"宗门:{data.get('school')}\n"
result_msg += f"驰援:{data.get('rescue')}\n"
result_msg += f"画像:{data.get('draw')}\n"
result_msg += f"宠物福缘:{data.get('luck')[0]},{data.get('luck')[1]},{data.get('luck')[2]}\n"
return_data["data"] = result_msg
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
async def shapan(self,server: str = "梦江南"):
"""
区服沙盘
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["aijx3_shapan"]
#更新参数
api_config["params"]["serverName"] = server
# 获取数据
data = await self.__api.post(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
return_data["data"] = data.get("picUrl")
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
async def kaifu(self,server: str = "梦江南"):
"""
开服状态查询
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3_kaifu"]
#更新参数
api_config["params"]["server"] = server
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
status = data.get("status")
status_time = datetime.fromtimestamp(data.get("time")).strftime("%Y-%m-%d %H:%M:%S")
if status == 1:
status_str = f"{server}服务器已开服,快冲,快冲!\n开服时间:{status_time}"
status_bool = True
else:
status_str = f"{server}服务器当前维护中,等会再来吧!\n维护时间:{status_time}"
status_bool = False
return_data["status"] = status_bool
return_data["data"] = status_str
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
async def shaohua(self):
"""
骚话
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3_shaohua"]
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
return_data["data"] = data.get("text")
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
async def jigai(self):
"""
技改记录
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3_jigai"]
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 处理返回数据
try:
result_msg = f"剑网三最近技改\n"
for i, item in enumerate(data[:1], 1):
result_msg += f"{i}. {item.get('title', '无标题')}\n"
result_msg += f"时间:{item.get('time', '未知时间')}\n"
result_msg += f"链接:{item.get('url', '无链接')}\n\n"
return_data["data"] = result_msg
return_data["code"] = 200
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "处理接口返回信息时出错"
return return_data
async def jinjia(self, server: str = "眉间雪"):
"""
区服金价
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["aijx3_jinjia"]
#更新参数
api_config["params"]["serverName"] = server
# 获取数据
data = await self.__api.post(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
chart_data = data
data = data[:15]
# 加载模板
try:
return_data["temp"] = load_template("jinjia.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
# 生成折线图
try:
chart_base64 = plot_line_chart_base64(chart_data, "date", "priceWanbaolou", "万宝楼金价走势",True)
except Exception as e:
logger.error(f"生成折线图失败: {e}")
return_data["msg"] = "系统错误:生成折线图失败"
return return_data
# 准备模板渲染数据
try:
return_data["data"] = {
"items": data,
"server": api_config["params"]["serverName"],
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"img": chart_base64
}
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "系统错误:模板渲染数据准备失败"
return return_data
return_data["code"] = 200
return return_data
async def qiyu(self, adventureName: str = "阴阳两界", serverName: str = "眉间雪"):
"""
区服奇遇
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["aijx3_qiyu"]
#更新参数
api_config["params"]["adventureName"] = adventureName
api_config["params"]["serverName"] = serverName
# 获取数据
data = await self.__api.post(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 格式化时间
for item in data:
if "time" in item:
item["time"] = datetime.fromtimestamp(item["time"]/1000).strftime("%Y-%m-%d %H:%M:%S")
# 加载模板
try:
return_data["temp"] = load_template("qiyuliebiao.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
# 准备模板渲染数据
try:
return_data["data"] = {
"items": data,
"server": serverName,
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
,"qiyuname": adventureName
}
except Exception as e:
logger.error(f"处理数据时出错: {e}")
return_data["msg"] = "系统错误:模板渲染数据准备失败"
return return_data
return_data["code"] = 200
return return_data
async def SearchData(self):
"""
外观数据插入数据库
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["aijx3_SearchData"]
# 获取数据
data = await self.__api.post(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取接口信息失败"
return return_data
# 提取数据
try:
extracted_data = flatten_field(data, "dataModels")
except FileNotFoundError as e:
logger.error(f"提取数据失败: {e}")
return_data["msg"] = "提取指定数据字段失败"
return return_data
# 准备SQL和批量数据
sql = """
INSERT INTO searchdata
(typeName, name, showName, picUrl, searchId, searchDescType)
VALUES (%s, %s, %s, %s, %s, %s)
"""
values_list = [
(
item['typeName'],
item['name'],
item['showName'],
item['picUrl'],
item['searchId'],
item['searchDescType']
)
for item in extracted_data
]
# 插入数据
try:
await self.__db.truncate_table("searchdata")
await self.__db.executemany(sql, values_list)
except FileNotFoundError as e:
logger.error(f"数据插入失败: {e}")
return_data["msg"] = "数据插入数据库失败"
return return_data
return_data["msg"] = f"成功批量插入 {len(extracted_data)} 条数据!"
return_data["code"] = 200
return return_data
async def wujia(self,Name: str = "秃盒"):
"""
物价查询
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3box_exterior"]
#更新参数
api_config["params"]["keyword"] = Name
# 获取查找信息
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if data["total"] == 0:
try:
sql = "SELECT showName, searchId FROM searchdata WHERE name = %s OR showName = %s"
sqldata = await self.__db.fetch_one(sql, (Name,Name))
searchId=sqldata["searchId"]
showName=sqldata["showName"]
return_data["data"]["searchId"]=searchId
return_data["data"]["showName"]=showName
except Exception as e:
logger.error(f"获取外观信息错误: {e}")
return_data["msg"] = "未找到该外观信息"
return return_data
else:
# 查询魔盒获取外观名称
try:
showName=data["list"][0]["name"]
return_data["data"]["showName"]=showName
except Exception as e:
logger.error(f"获取外观名称错误: {e}")
return_data["msg"] = "未找到该外观信息"
return return_data
# 查询爱剑三获取外观ID
try:
sql = "SELECT searchId FROM searchdata WHERE showName=%s"
sqldata = await self.__db.fetch_one(sql, (return_data["data"]["showName"],))
searchId=sqldata["searchId"]
return_data["data"]["searchId"]=searchId
except Exception as e:
logger.error(f"获取外观ID错误: {e}\n{return_data['data']['showName']}")
return_data["msg"] = "未找到该外观信息"
return return_data
#在配置文件中获取接口配置
api_config = self.__api_config["aijx3_GoodsDetail"]
#更新参数
api_config["params"]["goodsName"] = showName
# 获取数据外观详细数据
data = await self.__api.post(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "获取外观详细数据失败"
return return_data
# 提取外观详细数据
try:
imgs = data.get("imgs", [])
return_data["data"]["goodsDesc"]=data.get("goodsDesc", "无描述")
return_data["data"]["publishTime"]=data.get("publishTime", "无价格")
return_data["data"]["priceNum"]=data.get("priceNum", 0)
return_data["data"]["goodsId"]=data.get("goodsId", "无数据")
return_data["data"]["imgs"]=imgs[0] if imgs else ""
return_data["data"]["goodsAlias"]=data.get("goodsAlias", "无别名")
except Exception as e:
logger.error(f"提取外观详细数据失败: {e}")
return_data["msg"] = "提取外观详细数据失败"
return return_data
# 查询万宝楼数据(公示和在售)
wbl_data = await self.__get_wbl_data(showName)
if not wbl_data:
return_data["msg"] = "获取万宝楼数据失败"
return return_data
if wbl_data:
return_data["data"]["wblgs"]=wbl_data["wblgs"]
return_data["data"]["wblzs"]=wbl_data["wblzs"]
return_data["msg"] = "获取万宝楼数据完成"
# 加载模板
try:
return_data["temp"] = load_template("wujia.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
async def __get_wbl_data(self,showName):
"""获取万宝楼数据(公示和在售)"""
try:
#在配置文件中获取接口配置
#api_config = self.__api_config["aijx3_wblwg"]
api_config = self.__api_config["wbl_waiguan"]
#更新参数
api_config["params"]["filter[role_appearance]"] = showName
# 获取公示数据
api_config["params"]["filter[state]"] = "1"
datawblgs = await self.__api.get(api_config["url"], api_config["params"], "data")
# 获取在售数据
api_config["params"]["filter[state]"] = "2"
datawblzs = await self.__api.get(api_config["url"], api_config["params"], "data")
return {
"wblgs": await self.__process_wbl_records(datawblgs.get("list", [])),
"wblzs": await self.__process_wbl_records(datawblzs.get("list", []))
}
except Exception as e:
logger.error(f"获取万宝楼数据出错: {e}")
return None
async def __process_wbl_records(self,records):
"""处理万宝楼记录数据"""
processed = []
for record in records:
try:
# 转换时间戳为可读格式
timestamp = record.get("remaining_time", 0)
days = timestamp // 86400 # 每天有 86400 秒
hours = (timestamp % 86400) // 3600 # 每小时有 3600 秒
minutes = (timestamp % 3600) // 60 # 每分钟有 60 秒
result = ""
if days > 0:
result = (f"{days}")
if hours > 0:
result += (f"{hours}")
if minutes > 0:
result += (f"{minutes}分钟")
single_unit_price = record.get("single_unit_price", 0)
processed.append({
"priceNum": "{:.2f}".format( single_unit_price / 100),
"belongQf2": record.get("server_name", "无数据"),
"replyTime": result,
"discountRate": record.get("discountRate", 0.0),
})
except Exception as e:
logger.error(f"处理万宝楼记录出错: {e}")
continue
return processed
async def jiaoyihang(self,Name: str = "守缺式",server: str = "梦江南"):
"""
区服交易行
"""
return_data = {
"code": 0,
"msg": "功能函数未执行",
"data": {}
}
#在配置文件中获取接口配置
api_config = self.__api_config["jx3box_item"]
#更新参数
api_config["params"]["keyword"] = Name
# 获取多页查找信息
data = await self.__api.all_pages("GET",api_config["url"],api_config["params"],"data","data")
if not data:
logger.error(f"获取多页数据失败")
return_data["msg"] = "未找到该物品"
return return_data
# 提取指定字段
fields = ["IconID", "Name","id"]
result = extract_fields(data, fields)
if not data:
logger.error(f"提取字段失败")
return_data["msg"] = "未找到该物品"
return return_data
# 提取id列表
lists_id = extract_field(result, "id")
strlists_id = ",".join(str(x) for x in lists_id)
logger.info(f"{strlists_id}")
#在配置文件中获取接口配置
api_config = self.__api_config["jx3box_itemprice"]
#更新参数
api_config["params"]["itemIds"] = strlists_id
api_config["params"]["server"] = server
# 获取数据
data = await self.__api.get(api_config["url"],api_config["params"],"data")
if not data:
return_data["msg"] = "未找到在售物品"
return return_data
#提取需要的字段
fieldsjyh = ["ItemId", "SampleSize", "LowestPrice", "AvgPrice", "Date"]
resultjyh = [{f: v[f] for f in fieldsjyh} for v in data.values()]
#合并表格数据
# 先建立一个字典映射,加快查找
map_result = {item["id"]: {"IconID": item["IconID"], "Name": item["Name"]} for item in result}
# 遍历 resultjyh,合并字段
for item in resultjyh:
if item["ItemId"] in map_result:
item.update(map_result[item["ItemId"]])
#处理数据
for item in resultjyh:
item["LowestPrice"] = gold_to_string(item["LowestPrice"])
item["AvgPrice"] = gold_to_string(item["AvgPrice"])
item["IconID"] = f"https://icon.jx3box.com/icon/{item['IconID']}.png"
# 准备模板渲染数据
return_data["data"] = {
"items": resultjyh,
"server": server,
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# 加载模板
try:
return_data["temp"] = load_template("jiaoyihang.html")
except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在"
return return_data
return_data["code"] = 200
return return_data
+190
View File
@@ -0,0 +1,190 @@
from datetime import datetime
import base64
from astrbot.api import logger
from .APIClient import APIClient
from .AsyncMySQL import AsyncMySQL
from .function_basic import load_template,extract_field,flatten_field,extract_fields,gold_to_string,plot_line_chart_base64
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"处理数据时出错: {e}")
return_data["msg"] = "处理数据库信息时出错"
return return_data
# 加载模板
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["data"]["lists"] = sqlid
return_data["code"] = 200
return return_data
View File
+167
View File
@@ -0,0 +1,167 @@
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}"
+14
View File
@@ -0,0 +1,14 @@
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)
+391 -15
View File
@@ -1,24 +1,400 @@
from astrbot.api.event import filter, AstrMessageEvent, MessageEventResult import json
from astrbot.api.star import Context, Star, register import asyncio
from astrbot.api import logger from pathlib import Path
from datetime import datetime
@register("helloworld", "YourName", "一个简单的 Hello World 插件", "1.0.0") from astrbot.api.event import filter, AstrMessageEvent, MessageEventResult, MessageChain
class MyPlugin(Star): from astrbot.api.star import Context, Star, register, StarTools
def __init__(self, context: Context): from astrbot.api import logger
from astrbot.api import AstrBotConfig
from .core.AsyncMySQL import AsyncMySQL
from .core.JX3Function import JX3Function
from .core.WZRYFunction import WZRYFunction
@register("astrbot_plugin_jx3api",
"fxdyz",
"通过接口调用剑网三API接口获取游戏数据",
"1.0.0",
"https://github.com/qsc20001102/astrbot_plugin_jx3api.git"
)
class Jx3ApiPlugin(Star):
def __init__(self, context: Context, config: AstrBotConfig):
super().__init__(context) super().__init__(context)
#获取配置
self.conf = config
# 本地数据存储路径
self.local_data_dir = StarTools.get_data_dir("astrbot_plugin_jx3api")
# api数据文件
self.api_file_path = Path(__file__).parent / "api_config.json"
# 读取文件内容
with open(self.api_file_path, 'r', encoding='utf-8') as f:
self.api_config = json.load(f)
# 初始化数据
self.inidata()
self.kf_task = asyncio.create_task(self.cycle_kaifjiankong())
logger.info("jx3api插件初始化完成")
async def initialize(self): 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.jx3fun = JX3Function(self.api_config,self.db)
self.wzry = WZRYFunction(self.api_config,self.db)
# 周期函数调用
logger.info("jx3api插件创建实例完成")
def inidata(self):
"""数据初始化"""
self.test_server = False
logger.info("数据初始化完成")
async def cycle_kaifjiankong(self):
"""开服监控后台程序"""
# 获取配置信息
time_int = self.conf.get("jx3_kfjk_time", 10)
self.kfjk_en = self.conf.get("jx3_kfjk_en", True)
self.kfjk_umos = self.conf.get("jx3_kfjk_list", [])
self.kfjk_servername = self.conf.get("jx3_kfjk_server", "梦江南")
kfjk_test = self.conf.get("jx3_kfjk_test", False)
self.kfjk_server_state = True # 上一次查询的状态
self.kfjk_server_state_new = False # 最新查询的状态
if self.kfjk_en:
logger.info(f"开服监控功能开启")
while self.kfjk_en:
data = await self.jx3fun.kaifu(self.kfjk_servername)
if kfjk_test:
self.kfjk_server_state_new = self.test_server
else:
self.kfjk_server_state_new = data["status"]
# logger.info(f"开服监控功能循环中,上次询问服务器状态{self.kfjk_server_state},本次询问的服务器状态{self.kfjk_server_state_new}")
if self.kfjk_server_state != self.kfjk_server_state_new:
logger.info(f"开服监控功能循环中,上次询问服务器状态{self.kfjk_server_state},本次询问的服务器状态{self.kfjk_server_state_new}")
if self.kfjk_server_state and not self.kfjk_server_state_new:
message_chain = MessageChain().message(f"{self.kfjk_servername}服务器已关闭\n休息一会把,开服了喊你!")
if self.kfjk_server_state_new and not self.kfjk_server_state:
message_chain = MessageChain().message(f"{self.kfjk_servername}服务器已开启\n快冲!快冲!")
if self.kfjk_umos:
for umo in self.kfjk_umos:
await self.context.send_message(umo, message_chain)
self.kfjk_server_state = self.kfjk_server_state_new
await asyncio.sleep(time_int)
if not self.kfjk_en:
# 销毁进程
self.kf_task.cancel()
logger.info(f"开服监控功能关闭")
@filter.command_group("剑三")
def jx3(self):
pass
@jx3.command("日常")
async def jx3_richang(self, event: AstrMessageEvent,server: str = "梦江南",num: int = 0):
"""剑三 日常 服务器 天数"""
try:
data= await self.jx3fun.richang(server,num)
if data["code"] == 200:
yield event.plain_result(data["data"])
else:
yield event.plain_result("msg")
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@jx3.command("开服")
async def jx3_kaifu(self, event: AstrMessageEvent,server: str = "梦江南"):
"""剑三 日常 服务器 天数"""
try:
data= await self.jx3fun.kaifu(server)
if data["code"] == 200:
yield event.plain_result(data["data"])
else:
yield event.plain_result("msg")
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@jx3.command("沙盘")
async def jx3_shapan(self, event: AstrMessageEvent,server: str = "梦江南"):
"""剑三 沙盘 服务器"""
try:
data= await self.jx3fun.shapan(server)
if data["code"] == 200:
yield event.image_result(data["data"])
else:
yield event.plain_result(data["msg"])
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@jx3.command("骚话")
async def jx3_shaohua(self, event: AstrMessageEvent,):
"""剑三 骚话"""
try:
data= await self.jx3fun.shaohua()
if data["code"] == 200:
yield event.plain_result(data["data"])
else:
yield event.plain_result(data["msg"])
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@jx3.command("技改")
async def jx3_jigai(self, event: AstrMessageEvent,):
"""剑三 技改"""
try:
data= await self.jx3fun.jigai()
if data["code"] == 200:
yield event.plain_result(data["data"])
else:
yield event.plain_result(data["msg"])
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@jx3.command("金价")
async def jx3_jinjia(self, event: AstrMessageEvent,server: str = "梦江南"):
"""剑三 金价 服务器"""
try:
data= await self.jx3fun.jinjia(server)
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("猪脑过载,请稍后再试")
@jx3.command("奇遇")
async def jx3_qiyu(self, event: AstrMessageEvent,adventureName: str = "阴阳两界", serverName: str = "梦江南"):
"""剑三 奇遇 奇遇名称 服务器"""
try:
data= await self.jx3fun.qiyu(adventureName,serverName)
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("猪脑过载,请稍后再试")
@jx3.command("物价")
async def jx3_wujia(self, event: AstrMessageEvent,Name: str = "秃盒"):
"""剑三 外观名称"""
try:
data=await self.jx3fun.wujia(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("猪脑过载,请稍后再试")
@jx3.command("交易行")
async def jx3_jiaoyihang(self, event: AstrMessageEvent,Name: str = "守缺式",server: str = "梦江南"):
"""剑三 外观名称"""
try:
data=await self.jx3fun.jiaoyihang(Name,server)
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("猪脑过载,请稍后再试")
@jx3.command("开服监控")
async def jx3_kaifhujiank(self, event: AstrMessageEvent,en: str = "状态"):
"""剑三 开服监控"""
if en == "开启":
umo_set = set(self.kfjk_umos)
umo_set.add(event.unified_msg_origin)
self.kfjk_umos = list(umo_set)
yield event.plain_result(f"开服监控已开启")
elif en =="关闭":
try:
self.kfjk_umos.remove(event.unified_msg_origin)
yield event.plain_result(f"开服监控已关闭")
except ValueError as e:
yield event.plain_result(f"开服监控已关闭")
elif en == "状态":
yield event.plain_result(f"开服监控后台状态:{self.kfjk_en}\n监控服务器:{self.kfjk_servername}\n上次询问服务器状态{self.kfjk_server_state}\n本次询问的服务器状态{self.kfjk_server_state_new}\n推送会话列表:\n{self.kfjk_umos}")
else:
yield event.plain_result("开服监控指令错误,请输入 开启/关闭")
@filter.permission_type(filter.PermissionType.ADMIN)
@jx3.command("外观数据同步")
async def jx3_SearchData(self, event: AstrMessageEvent):
"""剑三 外观数据同步"""
try:
data=await self.jx3fun.SearchData()
if data["code"] == 200:
yield event.plain_result(data["msg"])
else:
yield event.plain_result(data["msg"])
return
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@filter.command("测试值")
async def jx3_testvar(self, event: AstrMessageEvent,var: str = "0"):
"""测试程序"""
if var == "1":
self.test_server = True
if var == "0":
self.test_server = False
yield event.plain_result(f"测试值:{self.test_server}")
@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)
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
@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("猪脑过载,请稍后再试")
@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("仇人列表")
async def wz_bilei(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
#logger.info(f"输出结果{event.get_sender_name()}\n{out}")
#yield event.plain_result(f"{out}")
except Exception as e:
logger.error(f"功能函数执行错误: {e}")
yield event.plain_result("猪脑过载,请稍后再试")
# 注册指令的装饰器。指令名为 helloworld。注册成功后,发送 `/helloworld` 就会触发这个指令,并回复 `你好, {user_name}!`
@filter.command("helloworld")
async def helloworld(self, event: AstrMessageEvent):
"""这是一个 hello world 指令""" # 这是 handler 的描述,将会被解析方便用户了解插件内容。建议填写。
user_name = event.get_sender_name()
message_str = event.message_str # 用户发的纯文本消息字符串
message_chain = event.get_messages() # 用户所发的消息的消息链 # from astrbot.api.message_components import *
logger.info(message_chain)
yield event.plain_result(f"Hello, {user_name}, 你发了 {message_str}!") # 发送一条纯文本消息
async def terminate(self): async def terminate(self):
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。""" """可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
await self.db.close_pool()
# 后台进程销毁
self.kf_task.cancel()
logger.info("jx3api插件已卸载/停用")
+5 -6
View File
@@ -1,6 +1,5 @@
name: helloworld # 插件唯一识别名,以 astrbot_plugin_ 前缀开头 name: jx3api # 这是你的插件唯一识别名
display_name: helloworld # 用于展示的名字,可以是方便人类阅读的名字(需要版本 >= v4.5.0,低版本不会报错,请放心填写) desc: 通过接口调用剑网三API接口获取游戏数据 # 插件简短描述
desc: AstrBot 插件示例。 # 插件简短描述 version: v1.0.0 # 插件版本号。格式:v1.1.1 或者 v1.1
version: v1.3.0 # 插件版本号。格式:v1.1.1 或者 v1.1 author: fxdyz # 作者
author: Soulter # 作者 repo: https://github.com/qsc20001102/astrbot_plugin_jx3api # 插件的仓库地址
repo: https://github.com/Soulter/helloworld # 插件的仓库地址
+2
View File
@@ -0,0 +1,2 @@
aiomysql
matplotlib
+30
View File
@@ -0,0 +1,30 @@
<div style="width: 1220px; margin: 0 auto;">
<h1 style="text-align: center;">剑网3交易行价格查询</h1>
<p style="text-align: center;">服务器: {{ server }}</p>
<div style="overflow-x: auto; overflow-y: hidden;">
<table style="width: 100%; border-collapse: collapse; margin: 0 auto;">
<thead>
<tr style="height: 50px; text-align: center;">
<th style="border:1px solid #000; padding:4px;">物品名称</th>
<th style="border:1px solid #000; padding:4px;">最低价</th>
<th style="border:1px solid #000; padding:4px;">总数量</th>
<th style="border:1px solid #000; padding:4px;">数据更新时间</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr style="height: 45px; text-align: center;">
<td style="border:1px solid #000; padding:4px; text-align: left;">
<img src="{{ item.IconID }}" alt="icon" style="width:20px; height:20px; vertical-align:middle; border-radius:4px; margin-right:6px;">
{{ item.Name }}
</td>
<td style="border:1px solid #000; padding:4px;">{{ item.AvgPrice }}</td>
<td style="border:1px solid #000; padding:4px;">{{ item.SampleSize }}</td>
<td style="border:1px solid #000; padding:4px;">{{ item.Date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
+33
View File
@@ -0,0 +1,33 @@
<div style="width: 1220px; margin: 0 auto;">
<h1 style="text-align: center" id="剑网三金价">剑网三金价</h1>
<div style="overflow-x: auto; overflow-y: hidden;">
<table style="width: 100%; border-collapse: collapse; margin: 0 auto;">
<thead>
<tr style="height: 60px; text-align: center;">
<th style="border:1px solid #000; padding:2px;">时间</th>
{% for item in items %}
<th style="border:1px solid #000; padding:2px;">{{ item.date }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr style="height: 50px; text-align: center;">
<td style="border:1px solid #000; padding:2px;">万宝楼</td>
{% for item in items %}
<td style="border:1px solid #000; padding:2px;">{{ item.priceWanbaolou }}</td>
{% endfor %}
</tr>
<tr style="height: 50px; text-align: center;">
<td style="border:1px solid #000; padding:2px;">贴吧</td>
{% for item in items %}
<td style="border:1px solid #000; padding:2px;">{{ item.priceTieba }}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
+34
View File
@@ -0,0 +1,34 @@
<div style="width: 800px; margin: 0 auto;">
<h1 style="text-align: center;" id="剑网三奇遇查询">剑网3奇遇查询</h1>
<p style="text-align: center;">奇遇名称:{{ qiyuname }} 服务器:{{ server }}</p>
<div style="overflow-x: auto; overflow-y: hidden;">
<table style="width: 100%; border-collapse: collapse; margin: 0 auto;">
<thead>
<tr style="height: 50px; text-align: center;">
<th style="border:1px solid #000; padding:4px;">奇遇名称</th>
<th style="border:1px solid #000; padding:4px;">服务器</th>
<th style="border:1px solid #000; padding:4px;">角色名称</th>
<th style="border:1px solid #000; padding:4px;">触发时间</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr style="height: 45px; text-align: center;">
<td style="border:1px solid #000; padding:4px;">{{ item.adventureName }}</td>
<td style="border:1px solid #000; padding:4px;">{{ server }}</td>
<td style="border:1px solid #000; padding:4px;">{{ item.gameName }}</td>
<td style="border:1px solid #000; padding:4px;">{{ item.time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 可在这里插入 Base64 图片 -->
<div style="text-align: center; margin-top: 20px;">
<img src="data:image/png;base64, {{ your_base64_string }}"
alt="奇遇趋势图"
style="max-width: 100%; height: auto;" />
</div>
</div>
+101
View File
@@ -0,0 +1,101 @@
<!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>
<th>时间</th>
<th>描述</th>
</tr>
</thead>
<tbody>
{% for m in lists %}
<tr>
<td>{{ m.id }}</td>
<td>{{ m.name }}</td>
<td>{{ m.hero }}</td>
<td>{{ m.fenglu }}</td>
<td>{{ m.user }}</td>
<td>{{ m.time }}</td>
<td class="text-col">{{ m.text }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
+240
View File
@@ -0,0 +1,240 @@
<!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: 900px;
margin: 0 auto;
}
h1 {
text-align: center;
font-size: 32px;
margin-bottom: 25px;
color: #222;
font-weight: 800;
}
/* 卡片基础样式 */
.match-card {
display: flex;
border-radius: 18px;
padding: 16px;
margin-bottom: 18px;
position: relative;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
/* 背景色 */
.win { background: #e5f4ff; }
.lose { background: #ffe9e9; }
.draw { background: #f1f2f3; }
.hero-box img {
width: 76px;
height: 76px;
border-radius: 10px;
object-fit: cover;
}
.hero-desc {
text-align: center;
margin-top: 8px;
font-size: 18px;
font-weight: bold;
color: #333;
}
.content {
flex: 1;
padding-left: 15px;
font-size: 16px;
color: #333;
position: relative;
}
/* 右上角标签区域 */
.tag-area {
position: absolute;
right: 16px;
top: 16px;
display: flex;
gap: 8px;
}
/* 胜负标签 */
.result-tag {
padding: 6px 12px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
color: white;
}
.win-tag { background: #4a90e2; }
.lose-tag { background: #d93939; }
.draw-tag { background: #777; }
/* 对局时间 + 对局类型 */
.info-line-time {
font-size: 18px;
color: #000;
margin-bottom: 6px;
}
/* 段位 + 星星 */
.rank-stars {
font-size: 16px;
color: #2ecc71;
font-weight: bold;
margin-bottom: 6px;
}
/* KDA 信息 */
.kda-info {
font-size: 16px;
font-weight: bold;
color: #555;
margin-bottom: 6px;
}
/* 巅峰赛积分 */
.master-score {
font-size: 16px;
font-weight: bold;
color: #056a3a;
margin-bottom: 6px;
}
/* 评分显示 */
.score-badge {
position: absolute;
right: 16px;
bottom: 16px;
background: #ffd700;
color: #222;
font-size: 20px;
font-weight: 900;
padding: 6px 14px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* 新增成就标签 */
.achievement-tags {
margin-top: 8px;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.achievement-tags span {
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-weight: bold;
padding: 6px 10px;
border-radius: 14px;
font-size: 14px;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<div class="container">
<h1>王者荣耀 — 对局列表</h1>
{% for m in data %}
<div class="match-card
{% if m.gameresult == 1 %}win
{% elif m.gameresult == 2 %}lose
{% else %}draw
{% endif %}">
<div class="hero-box">
<img src="{{ m.heroIcon }}">
<div class="hero-desc">{{ m.desc }}</div>
</div>
<div class="content">
<!-- 右上角标签(evaluate + MVP PNG -->
<div class="tag-area">
{% if m.evaluateUrlV3 %}
<img src="{{ m.evaluateUrlV3 }}"
style="height: 32px; border-radius: 6px;">
{% endif %}
{% if m.mvpUrlV3 %}
<img src="{{ m.mvpUrlV3 }}"
style="height: 32px; border-radius: 6px;">
{% endif %}
<div class="result-tag
{% if m.gameresult == 1 %}win-tag
{% elif m.gameresult == 2 %}lose-tag
{% else %}draw-tag
{% endif %}">
{% if m.gameresult == 1 %}胜利
{% elif m.gameresult == 2 %}失败
{% else %}平局
{% endif %}
</div>
</div>
<!-- 对局时间 + 对局类型 -->
<div class="info-line-time">
{{ m.gametime }} · {{ m.mapName }}
</div>
<!-- 段位 + 星星 -->
<div class="rank-stars">
{{ m.roleJobName }} · ⭐{{ m.stars }}
</div>
<!-- 巅峰赛积分(原 KDA 位置) -->
<div class="master-score">
巅峰赛积分 {{ m.oldMasterMatchScore }} → {{ m.newMasterMatchScore }}
</div>
<!-- KDA 信息(原巅峰赛位置) -->
<div class="kda-info">
击杀 {{ m.killcnt }} 死亡 {{ m.deadcnt }} 助攻 {{ m.assistcnt }} 时长 {{ m.time_str }}
</div>
<!-- 新增成就显示 -->
<div class="achievement-tags">
{% if m.godLikeCnt > 0 %}<span>超神 × {{ m.godLikeCnt }}</span>{% endif %}
{% if m.firstBlood > 0 %}<span>一血</span>{% endif %}
{% if m.hero1TripleKillCnt > 0 %}<span>三杀 × {{ m.hero1TripleKillCnt }}</span>{% endif %}
{% if m.hero1UltraKillCnt > 0 %}<span>四杀 × {{ m.hero1UltraKillCnt }}</span>{% endif %}
{% if m.hero1RampageCnt > 0 %}<span>五杀 × {{ m.hero1RampageCnt }}</span>{% endif %}
</div>
<!-- 评分放右下角 -->
<div class="score-badge">
评分 {{ m.gradeGame }}
</div>
</div>
</div>
{% endfor %}
</div>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
<div style="max-width: 800px; margin: 0 auto; font-family: 'Microsoft YaHei', sans-serif; color: #000; text-align: center;">
<!-- 商品基本信息 -->
<h1 style="margin-bottom: 5px;">{{ showName }} ({{ goodsAlias }})</h1>
<img src="{{ imgs }}" alt="{{ showName }}" style="width: 100%; max-width: 600px; margin-bottom: 8px;">
<p style="margin: 4px 0;">{{ goodsDesc }}</p>
<p style="margin: 4px 0;"><strong>发布时间:</strong> {{ publishTime.split('T')[0] if publishTime else '未知' }}</p>
<p style="margin: 4px 0;"><strong>参考价格:</strong> {{ priceNum }} 元</p>
<!-- 双列表格 -->
<div style="display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; justify-content: center;">
<!-- 万宝楼公示 -->
<div style="flex: 1; min-width: 300px; border:1px solid #000; overflow:hidden;">
<div style="padding:4px; font-weight:bold; text-align:center;">万宝楼公示</div>
<div style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; margin:0 auto; text-align:center; font-size:0.9em;">
<thead>
<tr>
<th style="border:1px solid #000; padding:2px;">剩余时间</th>
<th style="border:1px solid #000; padding:2px;">服务器</th>
<th style="border:1px solid #000; padding:2px;">价格</th>
</tr>
</thead>
<tbody>
{% for item in wblgs %}
<tr>
<td style="border:1px solid #000; padding:2px;">{{ item.replyTime }}</td>
<td style="border:1px solid #000; padding:2px;">{{ item.belongQf2 }}</td>
<td style="border:1px solid #000; padding:2px;">{{ item.priceNum }}元</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- 万宝楼在售 -->
<div style="flex: 1; min-width: 300px; border:1px solid #000; overflow:hidden;">
<div style="padding:4px; font-weight:bold; text-align:center;">万宝楼在售</div>
<div style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; margin:0 auto; text-align:center; font-size:0.9em;">
<thead>
<tr>
<th style="border:1px solid #000; padding:2px;">剩余时间</th>
<th style="border:1px solid #000; padding:2px;">服务器</th>
<th style="border:1px solid #000; padding:2px;">价格</th>
</tr>
</thead>
<tbody>
{% for item in wblzs %}
<tr>
<td style="border:1px solid #000; padding:2px;">{{ item.replyTime }}</td>
<td style="border:1px solid #000; padding:2px;">{{ item.belongQf2 }}</td>
<td style="border:1px solid #000; padding:2px;">{{ item.priceNum }}元</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
+3
View File
@@ -0,0 +1,3 @@
<div style="text-align:center; margin-bottom:20px;">
<img src="data:image/jpeg;base64,{{ img_base64 }}" style="max-width:100%; border-radius:10px;">
</div>