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 插件模板
+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
from astrbot.api.star import Context, Star, register
from astrbot.api import logger
import json
import asyncio
from pathlib import Path
from datetime import datetime
@register("helloworld", "YourName", "一个简单的 Hello World 插件", "1.0.0")
class MyPlugin(Star):
def __init__(self, context: Context):
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.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)
#获取配置
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):
"""可选择实现异步的插件初始化方法,当实例化该插件类之后会自动调用该方法。"""
# 数据库配置
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):
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
await self.db.close_pool()
# 后台进程销毁
self.kf_task.cancel()
logger.info("jx3api插件已卸载/停用")
+5 -6
View File
@@ -1,6 +1,5 @@
name: helloworld # 插件唯一识别名,以 astrbot_plugin_ 前缀开头
display_name: helloworld # 用于展示的名字,可以是方便人类阅读的名字(需要版本 >= v4.5.0,低版本不会报错,请放心填写)
desc: AstrBot 插件示例。 # 插件简短描述
version: v1.3.0 # 插件版本号。格式:v1.1.1 或者 v1.1
author: Soulter # 作者
repo: https://github.com/Soulter/helloworld # 插件的仓库地址
name: jx3api # 这是你的插件唯一识别名
desc: 通过接口调用剑网三API接口获取游戏数据 # 插件简短描述
version: v1.0.0 # 插件版本号。格式:v1.1.1 或者 v1.1
author: fxdyz # 作者
repo: https://github.com/qsc20001102/astrbot_plugin_jx3api # 插件的仓库地址
+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>