增加战地六战绩全套构建
This commit is contained in:
parent
92f6a8bfb4
commit
9ee37493fe
43
.env
43
.env
@ -1,15 +1,14 @@
|
||||
DEBUG=true
|
||||
HOST=0.0.0.0 # 配置 NoneBot 监听的 IP / 主机名
|
||||
PORT=43001 # 配置 NoneBot 监听的端口
|
||||
COMMAND_START=["","/"] # 配置命令起始字符
|
||||
COMMAND_SEP=["."] # 配置命令分割字符
|
||||
HOST=0.0.0.0
|
||||
PORT=43001
|
||||
COMMAND_START=["","/"]
|
||||
COMMAND_SEP=["."]
|
||||
DRIVER=~fastapi+~httpx
|
||||
LOG_LEVEL=DEBUG
|
||||
|
||||
SUPERUSERS=[]
|
||||
|
||||
SUPERUSERS=[] # 超级管理员
|
||||
|
||||
NICKNAME=[] # 机器人昵称
|
||||
NICKNAME=[]
|
||||
|
||||
# QQ官方机器人配置文件
|
||||
# QQ_IS_SANDBOX=true
|
||||
@ -21,18 +20,30 @@ QQ_BOTS='[{
|
||||
"c2c_group_at_messages": true
|
||||
},
|
||||
"use_websocket": false
|
||||
}]'
|
||||
}
|
||||
]'
|
||||
|
||||
APSCHEDULER_CONFIG={"apscheduler.timezone": "Asia/Shanghai"}
|
||||
|
||||
|
||||
# .env.prod
|
||||
savedata = data/ram_data # 保存路径,相对路径,此处为保存至运行目录下的 "Yuni/savedata/" 下,默认为 ""
|
||||
ram_policy = 0 # 授权策略 0 为根据可用功能 1 为根据服务级别,默认为 0
|
||||
ram_cmd = ram # 指令名,或者叫触发词,默认为 ram
|
||||
ram_add = -a # 启用功能(根据可用功能),默认为 -a
|
||||
ram_rm = -r # 禁用功能(根据可用功能),默认为 -r
|
||||
ram_show = -s # 展示群功能状态(根据可用功能),默认为 -s
|
||||
ram_available = -v # 展示全局可用功能(根据可用功能),默认为 -v
|
||||
# 保存路径,相对路径,此处为保存至运行目录下的 "Yuni/savedata/" 下,默认为 ""
|
||||
|
||||
savedata=data/ram_data
|
||||
# 授权策略 0 为根据可用功能 1 为根据服务级别,默认为 0
|
||||
ram_policy=0
|
||||
|
||||
# 指令名,或者叫触发词,默认为 ram
|
||||
ram_cmd=ram
|
||||
|
||||
# 启用功能(根据可用功能),默认为 -a
|
||||
ram_add=-a
|
||||
|
||||
# 禁用功能(根据可用功能),默认为 -r
|
||||
ram_rm=-r
|
||||
|
||||
# 展示群功能状态(根据可用功能),默认为 -s
|
||||
ram_show=-s
|
||||
|
||||
# 展示全局可用功能(根据可用功能),默认为 -v
|
||||
ram_available=-v
|
||||
|
||||
|
||||
3
bot.py
3
bot.py
@ -1,5 +1,6 @@
|
||||
import nonebot
|
||||
from nonebot.adapters.qq import Adapter as QQ
|
||||
from nonebot.adapters.onebot.v11 import Adapter as Onebot
|
||||
from nonebot.log import logger
|
||||
|
||||
# 初始化 NoneBot 以及 数据库
|
||||
@ -9,7 +10,7 @@ app = nonebot.get_asgi()
|
||||
# 注册适配器
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(QQ)
|
||||
|
||||
driver.register_adapter(Onebot)
|
||||
|
||||
# 加载自定义插件
|
||||
nonebot.load_plugins("src/plugins") # 加载bot自定义插件
|
||||
|
||||
@ -37,7 +37,9 @@ nonebug-saa = { git = "https://github.com/MountainDash/nonebug-saa.git" }
|
||||
|
||||
[tool.nonebot]
|
||||
adapters = [
|
||||
{ name = "QQ", module_name = "nonebot.adapter.qq" }
|
||||
{ name = "QQ", module_name = "nonebot.adapter.qq" },
|
||||
{ name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" },
|
||||
{ name = "OneBot V12", module_name = "nonebot.adapters.onebot.v12" },
|
||||
]
|
||||
|
||||
plugins = [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import time
|
||||
|
||||
from nonebot import on_command, require
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.adapters import Message, Event, Bot
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.plugin import PluginMetadata
|
||||
@ -9,6 +9,8 @@ from nonebot.rule import to_me
|
||||
from nonebot.log import logger
|
||||
import json
|
||||
|
||||
from .user_data.data_utils import UserManager
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
from .data import *
|
||||
@ -19,14 +21,26 @@ from .text_utils import *
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="BF查询",
|
||||
description="战地3,4,1,5,2042,6",
|
||||
usage="",
|
||||
usage="""
|
||||
bf3: /bf3 EAID (查询BF3数据)
|
||||
bf4: /bf4 EAID (查询BF4数据)
|
||||
bf1: /bf1 EAID (查询BF5数据)
|
||||
bfv: /bfv EAID (查询BF6数据)
|
||||
bf2042: /bf2042 EAID (查询BF2042数据)
|
||||
bf6: /bf6 EAID (查询BF6数据)
|
||||
绑定: /绑定 EAID (绑定你的QQ与EAID)
|
||||
解绑: /解绑 (解除你的QQ与当前绑定的EAID)
|
||||
修改绑定: /修改绑定 EAID (修改你的QQ与当前绑定的EAID)
|
||||
""".strip(),
|
||||
extra={
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
query = on_command("bft", rule=to_me(), aliases={"bf3", "bf4", "bfv", "bf1", "bf2042", "bf6"}, block=True)
|
||||
bind = on_command("bind", rule=to_me(), aliases={"绑定"}, block=True)
|
||||
bind = on_command("bfbind", rule=to_me(), aliases={"绑定"}, block=True)
|
||||
unbind = on_command("unbind", rule=to_me(), aliases={"解绑"}, block=True)
|
||||
update_bind = on_command("update_bind", rule=to_me(), aliases={"修改绑定"}, block=True)
|
||||
|
||||
bf_dict = {
|
||||
"bf3": "战地3",
|
||||
@ -39,54 +53,73 @@ bf_dict = {
|
||||
|
||||
|
||||
@query.handle()
|
||||
async def handle_function(matcher: Matcher, msg: Message = CommandArg()):
|
||||
async def handle_function(event: Event, matcher: Matcher, msg: Message = CommandArg()):
|
||||
start_time = time.time()
|
||||
usermanager = UserManager()
|
||||
cmd = matcher.state["_prefix"]["command"][0]
|
||||
game = cmd
|
||||
content = msg.extract_plain_text()
|
||||
play_stat = ""
|
||||
if cmd == "bf3":
|
||||
play_stat = await get_data_bf3(content, "pc")
|
||||
elif cmd == "bf4":
|
||||
play_stat = await get_data_bf4(content, "pc")
|
||||
elif cmd == "bf1":
|
||||
play_stat = await get_data_bf1(content, "pc")
|
||||
elif cmd == "bfv":
|
||||
play_stat = await get_data_bfv(content, "pc")
|
||||
elif cmd == "bf2042":
|
||||
play_stat = await get_data_bfv(content, "pc")
|
||||
elif cmd == "bf6":
|
||||
flag, play_stat = await get_data_bf6(content, 0)
|
||||
if flag == 0:
|
||||
msg = '检测到多个同名用户\n' + '\n'.join(
|
||||
f'用户名:{info["name"]}-等级:{info["rank"]}-UID:{info["uid"]}' for info in play_stat)
|
||||
await UniMessage.text(msg).finish()
|
||||
elif flag == 2:
|
||||
msg = "未找到该玩家名"
|
||||
await UniMessage.text(msg).finish()
|
||||
input_text = msg.extract_plain_text().strip()
|
||||
logger.info(f"{type(input_text)}, {repr(input_text)}")
|
||||
if input_text is None or input_text == "":
|
||||
content = usermanager.get_user_by_qq(event.get_user_id())
|
||||
if not content:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text(
|
||||
"未检测到id,也未检测到绑定记录,请使用 绑定 EAID 进行绑定操作").send()
|
||||
return
|
||||
player_id = content['ea_player_id']
|
||||
user_id = content['ea_user_id']
|
||||
player = content['ea_player_name']
|
||||
platform = "pc"
|
||||
else:
|
||||
await UniMessage.text("指令异常").finish()
|
||||
content = await get_player_info_by_name(input_text, "pc")
|
||||
if 'errors' in content:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text("未找到玩家,请检查是否拼写正确").send()
|
||||
return
|
||||
player_id = content['personaId']
|
||||
user_id = content['userId']
|
||||
player = content['personaName'] if 'personaName' in content else content['name']
|
||||
platform = "pc"
|
||||
await UniMessage.text(f"正在查询 {player} 的 {bf_dict[cmd]} 数据,请耐心等待").send()
|
||||
play_stat = ""
|
||||
try:
|
||||
if cmd == "bf3":
|
||||
play_stat = await get_data_bf3(player_id, user_id, platform)
|
||||
elif cmd == "bf4":
|
||||
play_stat = await get_data_bf4(player_id, user_id, platform)
|
||||
elif cmd == "bf1":
|
||||
play_stat = await get_data_bf1(player_id, user_id, platform)
|
||||
elif cmd == "bfv":
|
||||
play_stat = await get_data_bfv(player_id, user_id, platform)
|
||||
elif cmd == "bf2042":
|
||||
await UniMessage.at(event.get_user_id()).text("正在开发中,敬请期待!").send()
|
||||
# play_stat = await get_data_bfv(player_id, user_id, platform)
|
||||
elif cmd == "bf6":
|
||||
play_stat = await get_data_bf6(player_id, user_id, platform)
|
||||
else:
|
||||
await UniMessage.at(event.get_user_id()).text("指令异常").send()
|
||||
return
|
||||
# logger.info(f"{json.dumps(play_stat, ensure_ascii=False, indent=2)}")
|
||||
if "errors" in play_stat:
|
||||
logger.warning(play_stat['errors'][0])
|
||||
msg = play_stat['errors'][0]
|
||||
else:
|
||||
if cmd == "bf6":
|
||||
img = build_bf6_simple_card(play_stat)
|
||||
else:
|
||||
# logger.info(f"结果{play_stat}")
|
||||
# player = play_stat['userName']
|
||||
weapon, vehicle = await get_best_weapon_and_best_vehicle(play_stat)
|
||||
player = play_stat['userName']
|
||||
pid = play_stat['userId']
|
||||
kd = play_stat['killDeath']
|
||||
kpm = play_stat['killsPerMinute']
|
||||
spm = play_stat['scorePerMinute']
|
||||
acc = play_stat['accuracy']
|
||||
# 战地3 特化
|
||||
if cmd == 'bf3':
|
||||
head_shots = play_stat['headShots']
|
||||
else:
|
||||
head_shots = play_stat['headshots']
|
||||
|
||||
rank = play_stat['rank']
|
||||
time_play = convert_to_hours(play_stat['timePlayed'])
|
||||
logger.info(f"游玩时长监测:{time_play}")
|
||||
if time_play == 0:
|
||||
await UniMessage.at(event.get_user_id()).text("查询接口异常,请等待一段时间后查询").send()
|
||||
return
|
||||
kills = int(play_stat['kills'])
|
||||
kill_assists = int(play_stat['killAssists'])
|
||||
revives = int(play_stat['revives'])
|
||||
@ -95,21 +128,141 @@ async def handle_function(matcher: Matcher, msg: Message = CommandArg()):
|
||||
best_weapon = weapon
|
||||
best_vehicle = vehicle
|
||||
best_class = play_stat['bestClass']
|
||||
destroyed = await get_vehicle_destroyed(play_stat["vehicles"])
|
||||
if cmd == 'bf6':
|
||||
rank = await get_bf6_rank(player)
|
||||
logger.info(f"等级:{rank}")
|
||||
captured = play_stat['objective']['captured']
|
||||
score = sum(item.get("score", 0) for item in play_stat['classes'])
|
||||
# 计算4兵种游玩时长
|
||||
seconds = sum(item.get("secondsPlayed", 0) for item in play_stat['classes'])
|
||||
kpm = round(kills / (int(seconds) / 60), 1)
|
||||
logger.info(f"击杀数:{kills},游玩分钟数:{seconds / 60},计算kpm值:{kpm}")
|
||||
spm = int(score / (int(seconds) / 60))
|
||||
logger.info(f"计算spm值:{spm}")
|
||||
repairs = play_stat['repairs']
|
||||
img = await build_bf6_stats_card(game='bf6',
|
||||
qq_id=event.get_user_id(),
|
||||
player=player,
|
||||
pid=pid,
|
||||
rank=rank,
|
||||
kd=kd,
|
||||
kpm=kpm,
|
||||
spm=spm,
|
||||
acc=acc,
|
||||
head_shots=head_shots,
|
||||
time_play=time_play,
|
||||
kills=kills,
|
||||
kill_assists=kill_assists,
|
||||
revives=revives,
|
||||
repairs=repairs,
|
||||
captured=captured,
|
||||
score=score,
|
||||
wins=wins,
|
||||
loses=loses,
|
||||
destroyed=destroyed,
|
||||
best_weapon=best_weapon,
|
||||
best_vehicle=best_vehicle,
|
||||
best_class=best_class)
|
||||
else:
|
||||
spm = play_stat['scorePerMinute']
|
||||
rank = play_stat['rank']
|
||||
longest_head_shot = play_stat['longestHeadShot']
|
||||
highest_ill_streak = play_stat['highestKillStreak']
|
||||
|
||||
# await stats_calculator(play_stat)
|
||||
|
||||
stat_data, level_designation = await stats_calculator(play_stat, cmd)
|
||||
destroyed = await get_vehicle_destroyed(play_stat["vehicles"])
|
||||
img = await build_stats_card(game, player, pid, kd, kpm, spm, acc, head_shots, rank, time_play, kills,
|
||||
kill_assists, revives, wins, loses, destroyed, best_weapon, best_vehicle,
|
||||
best_class,
|
||||
longest_head_shot, highest_ill_streak, stat_data, level_designation)
|
||||
await UniMessage.image(raw=img.getvalue()).finish()
|
||||
await UniMessage.text(f"\n玩家【{content}】的【{bf_dict[cmd]}】数据\n{msg}").send()
|
||||
end_time = time.time()
|
||||
duration = round(end_time - start_time, 3)
|
||||
await UniMessage.image(raw=img.getvalue()).at(user_id=event.get_user_id()).text(
|
||||
f"本次查询耗时:{duration}s").send()
|
||||
except Exception as e:
|
||||
logger.exception(f"异常:{e}")
|
||||
|
||||
|
||||
@bind.handle()
|
||||
async def bind_user(matcher: Matcher, msg: Message = CommandArg()):
|
||||
matcher.state.get()
|
||||
async def bind_user(event: Event, matcher: Matcher, msg: Message = CommandArg()):
|
||||
user_manager = UserManager()
|
||||
player_name = msg.extract_plain_text()
|
||||
qq_id = event.get_user_id()
|
||||
|
||||
# 执行已有数据查询操作
|
||||
user_record = user_manager.get_user_by_qq(qq_id)
|
||||
if user_record:
|
||||
user_info = await get_player_info_by_ea_id(user_record['ea_player_id'], user_record['ea_user_id'], 'pc')
|
||||
await (UniMessage.at(user_id=event.get_user_id())
|
||||
.text(f"您已经将账号绑定到: {user_info['personaName']},如果你需要更改绑定请发送 修改绑定 EAID ").send())
|
||||
return
|
||||
else:
|
||||
user_info_json = await get_player_info_by_name(player_name, "pc")
|
||||
if 'errors' in user_info_json:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text("未找到玩家,请检查是否拼写正确").send()
|
||||
player_id = user_info_json['personaId']
|
||||
user_id = user_info_json['userId']
|
||||
player_name = user_info_json['personaName'] if 'personaName' in user_info_json else user_info_json['name']
|
||||
avatar_url = user_info_json['avatar']
|
||||
logger.info(f"QQ号:{qq_id},EA双ID:{player_id}-{user_id}")
|
||||
|
||||
# 检测ea账号 是否被绑定至其他qq
|
||||
bind_record = user_manager.get_user_by_ea_id(user_id)
|
||||
if bind_record:
|
||||
await (UniMessage.at(user_id=event.get_user_id())
|
||||
.text(
|
||||
f"您的EA账号 {bind_record['ea_player_name']} 已经绑定到: {bind_record['qq_id']},请您确认绑定的EAID").send())
|
||||
return
|
||||
else:
|
||||
# 执行绑定操作
|
||||
record = user_manager.add_user(qq_id, player_name, player_id, user_id, "[]", "[]")
|
||||
logger.info(f"插入数据返回结果:{record}")
|
||||
if record > 0:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text(f"您已经成功绑定至: {player_name}").send()
|
||||
|
||||
|
||||
@unbind.handle()
|
||||
async def bind_user(event: Event, matcher: Matcher, msg: Message = CommandArg()):
|
||||
user_manager = UserManager()
|
||||
qq_id = event.get_user_id()
|
||||
|
||||
# 执行已有数据查询操作
|
||||
user_record = user_manager.get_user_by_qq(qq_id)
|
||||
if user_record:
|
||||
user_info = await get_player_info_by_ea_id(user_record['ea_player_id'], user_record['ea_user_id'], 'pc')
|
||||
rows = user_manager.delete_user_by_qq(qq_id)
|
||||
if rows > 0:
|
||||
await (UniMessage.at(user_id=event.get_user_id())
|
||||
.text(
|
||||
f"您已经与: {user_info['personaName']} 成功解除绑定,如果你需要重新绑定请发送 修改绑定 EAID ").send())
|
||||
return
|
||||
else:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text(f"未查询到绑定记录").send()
|
||||
|
||||
|
||||
@update_bind.handle()
|
||||
async def bind_user(event: Event, matcher: Matcher, msg: Message = CommandArg()):
|
||||
user_manager = UserManager()
|
||||
player_name = msg.extract_plain_text()
|
||||
qq_id = event.get_user_id()
|
||||
|
||||
# 执行已有数据查询操作
|
||||
user_record = user_manager.get_user_by_qq(qq_id)
|
||||
if user_record:
|
||||
user_info_json = await get_player_info_by_name(player_name, "pc")
|
||||
if 'errors' in user_info_json:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text("未找到玩家,请检查是否拼写正确").send()
|
||||
player_id = user_info_json['personaId']
|
||||
user_id = user_info_json['userId']
|
||||
player_name = user_info_json['personaName'] if 'personaName' in user_info_json else user_info_json['name']
|
||||
avatar_url = user_info_json['avatar']
|
||||
logger.info(f"QQ号:{qq_id},EA双ID:{player_id}-{user_id}")
|
||||
|
||||
# 执行修改绑定操作
|
||||
record = user_manager.update_user(qq_id, player_name, player_id, user_id, "[]", "[]")
|
||||
logger.info(f"修改数据返回结果:{record}")
|
||||
if record > 0 and record == 1:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text(
|
||||
f"您已经成功从:{user_record['ea_player_name']}修改绑定至: {player_name}").send()
|
||||
else:
|
||||
await UniMessage.at(user_id=event.get_user_id()).text(
|
||||
f"未查询到绑定记录: {player_name},如果需要绑定,请@机器人并发送 绑定 {player_name} ").send()
|
||||
|
||||
@ -6,11 +6,6 @@ from nonebot import logger
|
||||
from curl_cffi import AsyncSession, CurlError
|
||||
import random
|
||||
|
||||
try:
|
||||
import browser_cookie3
|
||||
except ImportError:
|
||||
browser_cookie3 = None # 可选,如果没安装则 fallback 到 cookies.txt
|
||||
|
||||
# ---------- 配置 ----------
|
||||
file_path = os.path.dirname(__file__).replace("\\", "/")
|
||||
exported_cookie_path = Path(f"{file_path}/cookies/tracker.txt") # 你导出的 cookies.txt
|
||||
@ -59,15 +54,6 @@ def load_cookies_from_txt(path: Path) -> List[Dict[str, str]]:
|
||||
return cookies
|
||||
|
||||
|
||||
def load_browser_cookies(domain="tracker.gg") -> List[Dict[str, str]]:
|
||||
if not browser_cookie3:
|
||||
return []
|
||||
try:
|
||||
return [{"name": c.name, "value": c.value} for c in browser_cookie3.chrome(domain_name=domain)]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def build_cookie_header(cookies: List[Dict[str, str]]) -> str:
|
||||
return "; ".join(f"{c['name']}={c['value']}" for c in cookies)
|
||||
|
||||
@ -130,10 +116,7 @@ def is_challenge_response(resp) -> bool:
|
||||
|
||||
|
||||
async def search_user_with_fallback(url: str):
|
||||
# 1️⃣ 优先尝试浏览器 cookie
|
||||
cookies = load_browser_cookies()
|
||||
# 2️⃣ 如果浏览器 cookie 不存在,再 fallback 到 cookies.txt
|
||||
if not cookies:
|
||||
# cookies.txt
|
||||
cookies = load_cookies_from_txt(exported_cookie_path)
|
||||
|
||||
headers = build_headers(RAW_BROWSER_HEADERS, cookies=cookies, ua_override=CUSTOM_UA)
|
||||
@ -142,12 +125,12 @@ async def search_user_with_fallback(url: str):
|
||||
|
||||
resp = await fetch_with_cookies(url, headers)
|
||||
if is_challenge_response(resp):
|
||||
logger.warning("⚠️ Cloudflare 拦截或 cookies 失效。")
|
||||
logger.warning("Cloudflare 拦截或 cookies 失效。")
|
||||
if isinstance(resp, dict):
|
||||
return resp
|
||||
return {"status": getattr(resp, "status_code", None), "preview": getattr(resp, "text", "")[:200]}
|
||||
else:
|
||||
logger.info("✅ 请求成功。")
|
||||
logger.info("请求成功。")
|
||||
return getattr(resp, "json", lambda: resp)() if hasattr(resp, "json") else resp
|
||||
|
||||
|
||||
|
||||
@ -2,24 +2,30 @@
|
||||
# https://curl.haxx.se/rfc/cookie_spec.html
|
||||
# This is a generated file! Do not edit.
|
||||
|
||||
tracker.gg FALSE / FALSE 1763712553 _lr_env_src_ats false
|
||||
tracker.gg FALSE / FALSE 1766200813 nitro-uid %7B%22TDID%22%3A%2238381d61-f87b-42c2-9a52-7764b7e111ac%22%2C%22TDID_LOOKUP%22%3A%22TRUE%22%2C%22TDID_CREATED_AT%22%3A%222025-09-21T03%3A20%3A13%22%7D
|
||||
tracker.gg FALSE / FALSE 1766200813 nitro-uid_cst znv0HA%3D%3D
|
||||
.tracker.gg TRUE / TRUE 1794816553 _scor_uid d3fe4e72704a49548e3d944283183d16
|
||||
.tracker.gg TRUE / TRUE 1795483904 _scor_uid d3fe4e72704a49548e3d944283183d16
|
||||
.tracker.gg TRUE / FALSE 1792552814 ncmp.domain tracker.gg
|
||||
.tracker.gg TRUE / TRUE 1792657486 __stripe_mid f3195526-098b-451a-a803-afe861f3546981d4b7
|
||||
.tracker.gg TRUE / FALSE 1795681481 _ga GA1.1.1090555935.1761016835
|
||||
.tracker.gg TRUE / TRUE 1797905122 __stripe_mid f3195526-098b-451a-a803-afe861f3546981d4b7
|
||||
.tracker.gg TRUE / FALSE 1800929047 _ga GA1.1.1090555935.1761016835
|
||||
.tracker.gg TRUE / FALSE 1795576862 _ga_4115T4MP2X GS2.1.s1761016839$o1$g1$t1761016862$j37$l0$h0
|
||||
tracker.gg FALSE / FALSE 1766305486 pbjs-unifiedid %7B%22TDID%22%3A%2238381d61-f87b-42c2-9a52-7764b7e111ac%22%2C%22TDID_LOOKUP%22%3A%22TRUE%22%2C%22TDID_CREATED_AT%22%3A%222025-09-21T03%3A21%3A09%22%7D
|
||||
tracker.gg FALSE / FALSE 1766305486 pbjs-unifiedid_cst YiwPLDosoA%3D%3D
|
||||
.tracker.gg TRUE / FALSE 1784448555 _cc_id d7583525ab694e187a68c7c9adac9679
|
||||
tracker.gg FALSE / FALSE 1761621671 _lr_sampling_rate 100
|
||||
.tracker.gg TRUE / FALSE 1794757153 cto_bidid ZYK0w18xUFhlUWZoQ2ttd2Vlc0lnVjV1azlmSXI5ZlBVRUZ3QjVFazJUOHAlMkZnYWlFc0l2endrZGVDb1Z6dXdJdXA4V3Y1OVlEVGI4VUlNV1QxejgwWmxwJTJGRjByeGJFbWhFRTBQQnExU1luNTlvd1klM0Q
|
||||
.tracker.gg TRUE / TRUE 1761122345 __cf_bm 0U2HKncz5gYebQMBxL_qtaeuqoAdy.5lnue9xCP_Lls-1761120544-1.0.1.1-vFt.KkK9agrVBKJiVR6KstByxEN86BEvyVVQxg5VhtRT40Oq6Bmaan1yzLbW9V0ixAlOLPpYcflX6CqpCjl0D9Lg.73D54Jnm3KLQ5tGxnEZhnr0ORp6W5HYsfpsNiRc
|
||||
tracker.gg FALSE / FALSE 1761124153 _lr_retry_request true
|
||||
.tracker.gg TRUE / FALSE 1761206955 panoramaId_expiry 1761206955341
|
||||
.tracker.gg TRUE / TRUE 1761123286 __stripe_sid 99b864d5-840b-4cce-9c31-b4699e594ec16de245
|
||||
.tracker.gg TRUE / FALSE 1795681481 _ga_HWSV72GK8X GS2.1.s1761120545$o13$g1$t1761121480$j60$l0$h0
|
||||
tracker.gg FALSE / FALSE 1766305486 pbjs-unifiedid_last Wed%2C%2022%20Oct%202025%2008%3A24%3A46%20GMT
|
||||
.tracker.gg TRUE / FALSE 1794817546 cto_bundle dv27kl9RN0JGSHVyZE9za1E5a1ZNZEo4R21rQVkxVUFtYSUyRjQxd0JaWDJtWGhOVmZnV2hxUEVzemZ2TE9XSVB0UXNMVkZySUtXaW0lMkZ4WTRnN3FhSmNFOE4lMkI2WXg4cW42UFpKd2I3QmViY1JuTkZYMG9FVUZ1STJESHc2NDUzMHBEMyUyQm92bUtEMzJlajF0bXMyZ3M1Z0YwJTJGRkR3JTNEJTNE
|
||||
.tracker.gg TRUE / TRUE 1792657481 cf_clearance betnOuJnsRXlDgs9rKx_2GJXze5bdRFK3eVFFB9k610-1761121481-1.2.1.1-RDnj4R4HqOjhE1Sn5Dp5wKysllw6cVSYRPbbL4y_STOAnBlQEVRv_7xC1h9TleDg9Ecyy2neJCW1Xk6BNd51K4htSHptKFlQF8tv_JqMlDyFg9DxShAveOMXu4o9vkivWZmFvJRKv9ERqm33dfsYAQD5lkyYFtsurraCOPzu3TILLbsnjoi2v_kCILeFLPdEbeiABRHQUrsZdi1jhwKM9cDJ_XHs0bmPZrDOfVaf444
|
||||
tracker.gg FALSE / FALSE 1771553052 pbjs-unifiedid_cst YiwPLDosoA%3D%3D
|
||||
.tracker.gg TRUE / FALSE 1789697050 _cc_id d7583525ab694e187a68c7c9adac9679
|
||||
.tracker.gg TRUE / FALSE 1792826487 _sharedID bed2ec05800bb7f9e68b74ba7bdcd4d2
|
||||
.tracker.gg TRUE / FALSE 1797905048 _pubcid a08276dc-b9f1-4665-af99-d3e92418fa0d
|
||||
tracker.gg FALSE / FALSE 1771553049 nitro-uid_cst V0fMHQ%3D%3D
|
||||
.tracker.gg TRUE / FALSE 1797905048 _pubcid_cst znv0HA%3D%3D
|
||||
tracker.gg FALSE / FALSE 1768961048 _lr_env_src_ats false
|
||||
.tracker.gg TRUE / FALSE 1797905049 _nitroID eac08417a7dae49a9a17a3077a566d71
|
||||
tracker.gg FALSE / FALSE 1771553049 nitro-uid %7B%22TDID%22%3A%2238381d61-f87b-42c2-9a52-7764b7e111ac%22%2C%22TDID_LOOKUP%22%3A%22TRUE%22%2C%22TDID_CREATED_AT%22%3A%222025-11-22T02%3A04%3A10%22%7D
|
||||
.tracker.gg TRUE / FALSE 1800093848 cto_bidid 1MhyDl8xUFhlUWZoQ2ttd2Vlc0lnVjV1azlmSXI5ZlBVRUZ3QjVFazJUOHAlMkZnYWlFc0l2endrZGVDb1Z6dXdJdXA4V3Y1OVlEVGI4VUlNV1QxejgwWmxwJTJGRiUyQjlkcXVKbW14OFo2dDZoUWFHMjcwMCUzRA
|
||||
.tracker.gg TRUE / FALSE 1766455452 panoramaId_expiry 1766455451737
|
||||
.tracker.gg TRUE / FALSE 1766455452 panoramaId 06fa4e910365dd224dafc8b188a2a9fb927a5781cea050ef85eb9d2d9bd1d20a
|
||||
.tracker.gg TRUE / FALSE 1766455452 panoramaIdType panoDevice
|
||||
tracker.gg FALSE / FALSE 1771553052 pbjs-unifiedid %7B%22TDID%22%3A%2238381d61-f87b-42c2-9a52-7764b7e111ac%22%2C%22TDID_LOOKUP%22%3A%22TRUE%22%2C%22TDID_CREATED_AT%22%3A%222025-11-22T02%3A04%3A13%22%7D
|
||||
tracker.gg FALSE / FALSE 1771553052 pbjs-unifiedid_last Mon%2C%2022%20Dec%202025%2002%3A04%3A12%20GMT
|
||||
.tracker.gg TRUE / FALSE 1800065052 cto_bundle Lj73nF81bUZFQXRhbjgxQmE5SG1uZWMyaEcxdGZBYUsxY09Fb05TT2hpSVo2ckptaXA0clZkeDBIQnklMkJrcG56U21MWnRvYlZJaVJUdmolMkI4YnBMQkgxWjRqaDZpTmhJVnRkVWIlMkZGVzBYR0Q1dnJ0WWhEeVNwQVpCTXZHc2tHUnVKSHRTeSUyRnpnQWIlMkJOV3BwSGdYTU1mcktaY2t3JTNEJTNE
|
||||
tracker.gg FALSE / FALSE 1766973852 _lr_sampling_rate 100
|
||||
.tracker.gg TRUE / FALSE 1800947800 _ga_HWSV72GK8X GS2.1.s1766387800$o31$g0$t1766387800$j60$l0$h0
|
||||
.tracker.gg TRUE / TRUE 1800065052 __gads ID=ac9c2c8840aa7cc9:T=1766369052:RT=1766388112:S=ALNI_Mb6I0wyc1mq0xflsJiO_mg_p7NYyg
|
||||
.tracker.gg TRUE / TRUE 1800065052 __gpi UID=000011d0cd089cb2:T=1766369052:RT=1766388112:S=ALNI_MZYW_bpm3kBzbSJVap3HmFbUvCzkQ
|
||||
.tracker.gg TRUE / TRUE 1781921052 __eoi ID=161dfd629cbfa555:T=1766369052:RT=1766388112:S=AA-Afjb7PNr8G6MBMl83ajT_75cS
|
||||
.tracker.gg TRUE / TRUE 1797905047 cf_clearance yXkCKjvvWaL14EKripPAXHHkJsg_U80n6RCKMxweSPg-1766369048-1.2.1.1-KaeYOmcV609jWIn.Rrw.fhT5HXMcy4R1H.s67uhk5ewh5P8yuXU6VZGYdMtGF8zUBAu5dVsg9dyp0C1jMqMPKgZ3syEcCryrW5_sPsLS8EUlLb1fXq2ehVCRtEHZhfP_zZuQSGxil2vTq1Z9feznMkY1cMb1hIaSZ3JUajyF7w.YryqLr4n5lOS3dlQeaXYS__X_gbzxyJNOzMXDuYQtfqc50JQt5ymEACbbPIGsK20
|
||||
|
||||
@ -3,55 +3,123 @@ import json
|
||||
import requests
|
||||
|
||||
from .get_bf6_data import *
|
||||
from .tracker_data import *
|
||||
|
||||
|
||||
async def get_data_bf3(user_name, platform):
|
||||
url = f"https://api.gametools.network/bf3/all/?format_values=true&name={user_name}&platform={platform}"
|
||||
async def get_data_bf3(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bf3/all/?format_values=true&playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"请求URL:{url}")
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
return data_json
|
||||
|
||||
|
||||
async def get_data_bf4(user_name, platform):
|
||||
url = f"https://api.gametools.network/bf4/all/?format_values=true&name={user_name}&platform={platform}"
|
||||
async def get_data_bf4(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bf4/all/?format_values=true&playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"请求URL:{url}")
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
return data_json
|
||||
|
||||
|
||||
async def get_data_bf1(user_name, platform):
|
||||
url = f"https://api.gametools.network/bf1/all/?format_values=true&name={user_name}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
async def get_data_bf1(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bf1/all/?format_values=true&playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"请求URL:{url}")
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
return data_json
|
||||
|
||||
|
||||
async def get_data_bfv(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bfv/all/?format_values=true&playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"请求URL:{url}")
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
return data_json
|
||||
|
||||
|
||||
async def get_data_bfv(user_name, platform):
|
||||
url = f"https://api.gametools.network/bfv/all/?format_values=true&name={user_name}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
async def get_data_bf6(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bf6/all/?format_values=true&playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false&lang=en-us"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"请求URL:{url}")
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
return data_json
|
||||
|
||||
|
||||
async def get_data_bf6(user_name, search_type):
|
||||
flag, data_json = await get_info(user_name, search_type)
|
||||
return flag, data_json
|
||||
async def get_player_info_by_name(user_name, platform):
|
||||
user_info_url = f"https://api.gametools.network/bfglobal/player/?name={user_name}&platform={platform}&skip_battlelog=false"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
logger.info(f"查询用户请求url:{user_info_url}")
|
||||
response = requests.request("GET", user_info_url, headers=headers, data=payload)
|
||||
|
||||
user_info_json = json.loads(response.text)
|
||||
return user_info_json
|
||||
|
||||
|
||||
async def get_player_info_by_ea_id(player_id, user_id, platform):
|
||||
url = f"https://api.gametools.network/bfglobal/player/?playerid={player_id}&oid={user_id}&platform={platform}&skip_battlelog=false"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
|
||||
user_info_json = json.loads(response.text)
|
||||
return user_info_json
|
||||
|
||||
|
||||
async def get_bf6_rank(player):
|
||||
url = f"https://api.tracker.gg/api/v2/bf6/standard/search?platform=origin&query={player}&autocomplete=true"
|
||||
result = await fetch_tracker_search(url, exported_cookie_path)
|
||||
# 无结果再使用steam搜索
|
||||
if "errors" in result:
|
||||
return 0
|
||||
user_info = {}
|
||||
if "status" in result or "errors" in result:
|
||||
return 0
|
||||
for res in result['data']:
|
||||
if res['status'] is not None and res['platformSlug'] == 'origin':
|
||||
name = res['platformUserHandle']
|
||||
uid = res['titleUserId']
|
||||
status = res['status'].strip().split('•', 1)[0].replace("Rank", "")
|
||||
user = {
|
||||
'name': name,
|
||||
'rank': status,
|
||||
'uid': uid,
|
||||
}
|
||||
user_info = user
|
||||
|
||||
logger.info(f"单用户{user_info}")
|
||||
logger.info(f"查询结果: {json.dumps(user_info, ensure_ascii=False, indent=2)}")
|
||||
if 'rank' not in user_info:
|
||||
return 0
|
||||
return user_info['rank']
|
||||
|
||||
@ -3,6 +3,8 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
from nonebot import logger
|
||||
from functools import reduce
|
||||
|
||||
# from img_utils import png_resize
|
||||
# from param import round_data, interval_table
|
||||
from .img_utils import png_resize
|
||||
from .param import round_data, interval_table
|
||||
|
||||
|
||||
4
src/plugins/bf_bot/database_op.py
Normal file
4
src/plugins/bf_bot/database_op.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
def bind_user(user_info):
|
||||
user_info['']
|
||||
60
src/plugins/bf_bot/gametools_bf6.py
Normal file
60
src/plugins/bf_bot/gametools_bf6.py
Normal file
@ -0,0 +1,60 @@
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
async def get_data_bf6(player_id, user_id, platform):
|
||||
url = "https://api.gametools.network/bf6/multiple/?raw=false&format_values=true"
|
||||
payload = json.dumps([
|
||||
{
|
||||
"player_id": player_id,
|
||||
"user_id": user_id,
|
||||
"platform": platform
|
||||
}
|
||||
])
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
info = json.loads(response.text)
|
||||
return info
|
||||
|
||||
|
||||
async def get_data_user(user_name, platform):
|
||||
url = f"https://api.gametools.network/bfglobal/player/?name={user_name}&platform={platform}&skip_battlelog=false"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
data_json = json.loads(response.text)
|
||||
|
||||
player_id = data_json["personaId"]
|
||||
user_id = data_json["userId"]
|
||||
platform = data_json["platform"]
|
||||
user_name = data_json["personaName"]
|
||||
|
||||
if "errors" in data_json:
|
||||
return False, data_json
|
||||
|
||||
user_info = {
|
||||
"player_id": player_id,
|
||||
"user_id": user_id,
|
||||
"platform": platform,
|
||||
"user_name": user_name
|
||||
}
|
||||
|
||||
return True, user_info
|
||||
|
||||
|
||||
async def get_player_game_info(user_name, platform):
|
||||
flag, user_info = await get_data_user(user_name, platform)
|
||||
info = await get_data_bf6(user_info['player_id'], user_info['user_id'], user_info['platform'])
|
||||
info['userName'] = user_name
|
||||
info_format = json.dumps(info, ensure_ascii=False, indent=2)
|
||||
print(info_format)
|
||||
@ -5,9 +5,10 @@ from typing import List, Dict, Optional
|
||||
from nonebot import logger
|
||||
from curl_cffi import AsyncSession, CurlError
|
||||
from .bf6_data import *
|
||||
# from bf6_data import *
|
||||
import requests
|
||||
import json
|
||||
|
||||
url_search = "https://api.tracker.gg/api/v2/bf6/standard/search?platform={platform}&query={name}"
|
||||
url_search = "https://api.tracker.gg/api/v2/bf6/standard/search?platform={platform}&query={name}&autocomplete=true"
|
||||
url_overview = "https://api.tracker.gg/api/v2/bf6/standard/profile/ign/{param}"
|
||||
|
||||
|
||||
@ -65,8 +66,8 @@ async def get_info(player, search_type):
|
||||
url = url_search.format(platform="steam", name=player)
|
||||
result = await search_user_with_fallback(url)
|
||||
title_id_list = []
|
||||
logger.info(f"查询结果: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
if "errors" in result:
|
||||
logger.info(f"查询结果: \n{json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
if "status" in result or "errors" in result:
|
||||
return 3, '查询异常'
|
||||
for res in result['data']:
|
||||
if res['status'] is not None:
|
||||
@ -245,3 +246,26 @@ async def get_vehicles(platform_info, overview, weapons, vehicles, gamemodes, ga
|
||||
'最佳载具': best_vehicle,
|
||||
}
|
||||
return player_info
|
||||
|
||||
|
||||
async def get_bf6_data(player_id, user_id, platform):
|
||||
|
||||
url = "https://api.gametools.network/bf6/multiple/?raw=false&format_values=true"
|
||||
|
||||
payload = json.dumps([
|
||||
{
|
||||
"player_id": player_id,
|
||||
"user_id": user_id,
|
||||
"platform": platform
|
||||
}
|
||||
])
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
|
||||
player_stat_json = json.loads(response.text)
|
||||
|
||||
return player_stat_json
|
||||
|
||||
@ -12,18 +12,39 @@ from .img_utils import *
|
||||
from .data_utils import *
|
||||
from .param import *
|
||||
|
||||
# from img_utils import *
|
||||
# from data_utils import *
|
||||
# from param import *
|
||||
|
||||
filepath = os.path.dirname(__file__).replace("\\", "/")
|
||||
|
||||
# 字体
|
||||
font_XXL = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 72)
|
||||
font_XL = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 64)
|
||||
font_L = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 48)
|
||||
font_M = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 36)
|
||||
font_MS = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 32)
|
||||
font_S = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 28)
|
||||
font_XS = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 24)
|
||||
font_XXS = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 18)
|
||||
font_XXXS = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 16)
|
||||
font_XXL = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 72)
|
||||
font_XL = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 64)
|
||||
font_L = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 48)
|
||||
font_ML = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 42)
|
||||
font_M = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 36)
|
||||
font_MS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 32)
|
||||
font_S = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 28)
|
||||
font_XS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 24)
|
||||
font_XXS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 18)
|
||||
font_XXXS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 16)
|
||||
|
||||
|
||||
font_XXL_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 72)
|
||||
font_XL_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 64)
|
||||
font_L_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 48)
|
||||
font_ML_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 42)
|
||||
font_M_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 36)
|
||||
font_MS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 32)
|
||||
font_S_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 28)
|
||||
font_XS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 24)
|
||||
font_XXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 18)
|
||||
font_XXXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 16)
|
||||
font_S_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 28)
|
||||
font_XS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 20)
|
||||
font_XXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 18)
|
||||
font_XXXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 16)
|
||||
|
||||
|
||||
async def build_stats_card(game="bfv", player="Player001", pid=1145141919, kd=1.25, kpm=2.3, spm=500, acc=25.6,
|
||||
@ -75,12 +96,12 @@ async def build_stats_card(game="bfv", player="Player001", pid=1145141919, kd=1.
|
||||
draw = ImageDraw.Draw(pil_img)
|
||||
|
||||
# 玩家信息
|
||||
draw.text((665, 65), f"{player}", fill="white", font=font_L)
|
||||
draw.text((745, 133), f"{pid}", fill="white", font=font_M)
|
||||
draw.text((665, 65), f"{player}", fill="white", font=font_L_CH)
|
||||
draw.text((745, 133), f"{pid}", fill="white", font=font_M_CH)
|
||||
|
||||
# 等级游玩时长
|
||||
draw.text((740, 212), f"{rank}", fill="white", font=font_M)
|
||||
draw.text((985, 212), f"{time_play}H", fill="white", font=font_M)
|
||||
draw.text((740, 212), f"{rank}", fill="white", font=font_M_CH)
|
||||
draw.text((985, 212), f"{time_play}H", fill="white", font=font_M_CH)
|
||||
# draw_centered_text(draw=draw, text=rank, x_left=730, x_right=905, y=210, font=font_M, fill="white", )
|
||||
# draw_centered_text(draw=draw, text=f"{time_play}H", x_left=965, x_right=1120, y=210, font=font_M, fill="white", )
|
||||
# draw.text((730, 195), f"{rank}", fill="white", font=font_M)
|
||||
@ -90,11 +111,172 @@ async def build_stats_card(game="bfv", player="Player001", pid=1145141919, kd=1.
|
||||
|
||||
# 场次
|
||||
draw_centered_text(draw=draw, text=(wins + loses), x_left=45, x_right=265, y=350,
|
||||
font=font_L,
|
||||
font=font_L_CH,
|
||||
fill="white")
|
||||
|
||||
# 命中率
|
||||
draw_centered_text(draw=draw, text=round(float(acc.replace("%", "")), 1), x_left=415, x_right=495, y=343,
|
||||
font=font_M_CH,
|
||||
fill="white")
|
||||
# 爆头率
|
||||
if head_shots:
|
||||
head_shots = head_shots.replace("%", "")
|
||||
else:
|
||||
head_shots = 0
|
||||
draw_centered_text(draw=draw, text=round(float(head_shots), 1), x_left=395, x_right=470, y=420, font=font_M_CH,
|
||||
fill="white")
|
||||
# kd
|
||||
draw_centered_text(draw=draw, text=kd, x_left=585, x_right=785, y=340, font=font_XL_CH, fill="white")
|
||||
# kpm
|
||||
draw_centered_text(draw=draw, text=round(kpm, 1), x_left=825, x_right=1025, y=340, font=font_XL_CH, fill="white")
|
||||
# spm
|
||||
draw_centered_text(draw=draw, text=round(spm, 1), x_left=1065, x_right=1265, y=340, font=font_XL_CH, fill="white")
|
||||
|
||||
# 击杀
|
||||
draw_centered_text(draw=draw, text=kills, x_left=870, x_right=1190, y=595, font=font_L_CH, fill="white")
|
||||
|
||||
# 助攻
|
||||
draw_centered_text(draw=draw, text=kill_assists, x_left=820, x_right=1145, y=770, font=font_L_CH, fill="white")
|
||||
|
||||
# 急救
|
||||
draw_centered_text(draw=draw, text=revives, x_left=770, x_right=1090, y=960, font=font_L_CH, fill="white")
|
||||
|
||||
# 摧毁
|
||||
draw_centered_text(draw=draw, text=destroyed, x_left=720, x_right=1040, y=1140, font=font_L_CH, fill="white")
|
||||
|
||||
# 底部栏
|
||||
|
||||
# 评级
|
||||
if game == 'bf6':
|
||||
logger.info("bf6不计算称号")
|
||||
else:
|
||||
level = level_designation['level']
|
||||
des = level_designation['designation']
|
||||
color = level_designation['color']
|
||||
draw_centered_text(draw=draw, text=level, x_left=70, x_right=200, y=1120, font=font_XL_CH, fill=color)
|
||||
draw_centered_text(draw=draw, text=des, x_left=200, x_right=550, y=1120, font=font_XL_CH, fill="white")
|
||||
|
||||
# 最佳兵种
|
||||
|
||||
draw_centered_text(draw=draw, text=classes[best_class], x_left=140, x_right=310, y=1290, font=font_L_CH, fill="white")
|
||||
|
||||
# 最远击杀
|
||||
draw_centered_text(draw=draw, text=f"{longest_head_shot}m", x_left=455, x_right=625, y=1290, font=font_L_CH,
|
||||
fill="white")
|
||||
|
||||
# 最高连杀
|
||||
draw_centered_text(draw=draw, text=highest_ill_streak, x_left=765, x_right=935, y=1290, font=font_L_CH, fill="white")
|
||||
|
||||
# 最佳⭐
|
||||
pil_img = build_best(draw, pil_img, best_weapon, best_vehicle, game)
|
||||
|
||||
# 生成时间
|
||||
draw.text((1870, 1420), f"{formatted_time}", fill=(154, 132, 149), font=font_XXXS_CH)
|
||||
|
||||
# ---------------- 转回 OpenCV ----------------
|
||||
img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# ---------------- 雷达图 ----------------
|
||||
if game != "bf6":
|
||||
center = (311, 796)
|
||||
r = 185
|
||||
scale_min = 0.25 if min(stat_data) < 0.25 else min(stat_data)
|
||||
img = draw_radar(img, stat_data, center=center, r=r,
|
||||
buffer=0.1, scale_min=scale_min, scale_max=1.10)
|
||||
# 保存
|
||||
success, buffer = cv2.imencode(".png", img)
|
||||
if success:
|
||||
b_io = io.BytesIO(buffer)
|
||||
return b_io # 就可以直接 return 二进制流
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
async def build_bf6_stats_card(game="bfv",
|
||||
qq_id=2931,
|
||||
player="Player001",
|
||||
pid=1145141919,
|
||||
rank=114,
|
||||
kd=1.25,
|
||||
kpm=2.3,
|
||||
spm=500,
|
||||
acc=25.6,
|
||||
head_shots=10,
|
||||
time_play=100,
|
||||
kills=100,
|
||||
kill_assists=150,
|
||||
revives=114,
|
||||
repairs=1000,
|
||||
captured=300,
|
||||
score=145674,
|
||||
wins=10,
|
||||
loses=10,
|
||||
destroyed=514,
|
||||
best_weapon="M1907 SF",
|
||||
best_vehicle="坦克",
|
||||
best_class="Support", ):
|
||||
# 获取当前事件并且格式化
|
||||
now = datetime.datetime.now()
|
||||
formatted_time = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# ---------------- OpenCV 部分 ----------------
|
||||
# 载入模板
|
||||
img = cv2.imread(f"{filepath}/template/{game}.png", cv2.IMREAD_COLOR)
|
||||
|
||||
# 载入头像并缩放
|
||||
avatar = cv2.imread(f"{filepath}/avatar_cache/0.png", cv2.IMREAD_UNCHANGED)
|
||||
avatar = cv2.resize(avatar, (134, 134), interpolation=cv2.INTER_AREA)
|
||||
|
||||
# 粘贴头像(保持原有坐标)
|
||||
y, x = 65, 105
|
||||
h, w = avatar.shape[:2]
|
||||
img[y:y + h, x:x + w] = avatar
|
||||
|
||||
# 公告栏
|
||||
img = notice_paste_bf6(img)
|
||||
img = designation_paste(player, img,
|
||||
user_id=qq_id,
|
||||
start_x=104,
|
||||
start_y=215,
|
||||
target_height=30,
|
||||
spacing=10)
|
||||
|
||||
# ---------------- PIL 部分 ----------------
|
||||
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
|
||||
draw = ImageDraw.Draw(pil_img)
|
||||
|
||||
# 玩家信息 105+160
|
||||
draw.text((265, 80), f"{player}", fill="white", font=font_XL)
|
||||
draw.text((265, 140), f"{pid}", fill="#CCCCCC", font=font_L)
|
||||
|
||||
# 等级
|
||||
pil_img = paste_rank_icon(int(rank), pil_img)
|
||||
draw_centered_text(draw=draw, text=f"{rank}", x_left=950, x_right=1075, y=205,
|
||||
font=font_M,
|
||||
fill="white")
|
||||
# draw.text((740, 212), f"{rank}", fill="white", font=font_M)
|
||||
|
||||
# 游玩时长
|
||||
draw.text((2300, 350), f"{time_play}H", fill="white", font=font_L)
|
||||
# 场次
|
||||
draw.text((2300, 450), f"{wins + loses}", fill="white", font=font_L)
|
||||
# draw_centered_text(draw=draw, text=(wins + loses), x_left=45, x_right=265, y=350,
|
||||
# font=font_L,
|
||||
# fill="white")
|
||||
|
||||
# 生涯总览
|
||||
# 胜率
|
||||
|
||||
win_rate = round((wins / (wins + loses)) * 100, 1)
|
||||
logger.info(f"胜场{wins},负场{loses},胜率{win_rate}")
|
||||
|
||||
draw_centered_text(draw=draw, text=f"{win_rate}%", x_left=2090, x_right=2200, y=375,
|
||||
font=font_XL,
|
||||
fill="white")
|
||||
|
||||
# 命中率
|
||||
acc_format = round(float(acc.replace("%", "")), 1)
|
||||
draw_centered_text(draw=draw, text=f"{acc_format}%", x_left=1210, x_right=1303, y=363,
|
||||
font=font_M,
|
||||
fill="white")
|
||||
# 爆头率
|
||||
@ -102,62 +284,83 @@ async def build_stats_card(game="bfv", player="Player001", pid=1145141919, kd=1.
|
||||
head_shots = head_shots.replace("%", "")
|
||||
else:
|
||||
head_shots = 0
|
||||
draw_centered_text(draw=draw, text=round(float(head_shots), 1), x_left=395, x_right=470, y=420, font=font_M,
|
||||
draw_centered_text(draw=draw, text=f"{round(float(head_shots), 1)}%", x_left=1210, x_right=1303, y=458, font=font_M,
|
||||
fill="white")
|
||||
# kd
|
||||
draw_centered_text(draw=draw, text=kd, x_left=585, x_right=785, y=340, font=font_XL, fill="white")
|
||||
# draw.text(text=kd, xy=(1350, 355), font=font_XL, fill="white")
|
||||
draw_centered_text(draw=draw, text=kd, x_left=1350, x_right=1500, y=355, font=font_XL, fill="white")
|
||||
# kpm
|
||||
draw_centered_text(draw=draw, text=round(kpm, 1), x_left=825, x_right=1025, y=340, font=font_XL, fill="white")
|
||||
draw_centered_text(draw=draw, text=round(kpm, 1), x_left=1610, x_right=1760, y=355, font=font_XL, fill="white")
|
||||
# spm
|
||||
draw_centered_text(draw=draw, text=round(spm, 1), x_left=1065, x_right=1265, y=340, font=font_XL, fill="white")
|
||||
draw_centered_text(draw=draw, text=round(spm, 1), x_left=1870, x_right=2010, y=355, font=font_XL, fill="white")
|
||||
|
||||
# 击杀
|
||||
draw_centered_text(draw=draw, text=kills, x_left=870, x_right=1190, y=595, font=font_L, fill="white")
|
||||
|
||||
# 助攻
|
||||
draw_centered_text(draw=draw, text=kill_assists, x_left=820, x_right=1145, y=770, font=font_L, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=805,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(kills)), x_left=1165, x_right=1455, y=805,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 急救
|
||||
draw_centered_text(draw=draw, text=revives, x_left=770, x_right=1090, y=960, font=font_L, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=1080,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(revives)), x_left=1165, x_right=1455, y=1080,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 摧毁
|
||||
draw_centered_text(draw=draw, text=destroyed, x_left=720, x_right=1040, y=1140, font=font_L, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=1350,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(destroyed)), x_left=1165, x_right=1455, y=1350,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 右侧
|
||||
# 占领
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=745,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(captured)), x_left=1485, x_right=1780, y=745,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 得分
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=955,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(score)), x_left=1485, x_right=1780, y=955,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 助攻
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=1160,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(kill_assists)), x_left=1485, x_right=1780, y=1160,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 修理
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=1370,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(repairs)), x_left=1485, x_right=1780, y=1370,
|
||||
font=font_L, fill="white")
|
||||
|
||||
# 底部栏
|
||||
|
||||
# 评级
|
||||
level = level_designation['level']
|
||||
des = level_designation['designation']
|
||||
color = level_designation['color']
|
||||
draw_centered_text(draw=draw, text=level, x_left=70, x_right=200, y=1120, font=font_XL, fill=color)
|
||||
draw_centered_text(draw=draw, text=des, x_left=200, x_right=550, y=1120, font=font_XL, fill="white")
|
||||
|
||||
# 最佳兵种
|
||||
|
||||
draw_centered_text(draw=draw, text=classes[best_class], x_left=140, x_right=310, y=1290, font=font_L, fill="white")
|
||||
|
||||
# 最远击杀
|
||||
draw_centered_text(draw=draw, text=f"{longest_head_shot}m", x_left=455, x_right=625, y=1290, font=font_L,
|
||||
fill="white")
|
||||
|
||||
# 最高连杀
|
||||
draw_centered_text(draw=draw, text=highest_ill_streak, x_left=765, x_right=935, y=1290, font=font_L, fill="white")
|
||||
# # 最佳兵种
|
||||
# logger.info(f"最佳兵种:{best_class}")
|
||||
# draw_centered_text(draw=draw, text=classes[best_class], x_left=140, x_right=310, y=1290, font=font_L, fill="white")
|
||||
|
||||
# 最佳⭐
|
||||
pil_img = build_best(draw, pil_img, best_weapon, best_vehicle, game)
|
||||
pil_img = build_best_bf6(draw, pil_img, best_weapon, best_vehicle, best_class, game)
|
||||
|
||||
# 生成时间
|
||||
draw.text((1870, 1420), f"{formatted_time}", fill=(154, 132, 149), font=font_XXXS)
|
||||
draw.text((1750, 10), f"{formatted_time}/战地中文社区/维护 SANSENHOSHI", fill=(154, 132, 149), font=font_XS_CH)
|
||||
|
||||
# ---------------- 转回 OpenCV ----------------
|
||||
img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# ---------------- 雷达图 ----------------
|
||||
center = (311, 796)
|
||||
r = 185
|
||||
scale_min = 0.25 if min(stat_data) < 0.25 else min(stat_data)
|
||||
img = draw_radar(img, stat_data, center=center, r=r,
|
||||
buffer=0.1, scale_min=scale_min, scale_max=1.10)
|
||||
|
||||
# 保存
|
||||
success, buffer = cv2.imencode(".png", img)
|
||||
if success:
|
||||
@ -184,18 +387,18 @@ def build_best(draw: ImageDraw, img: Image, weapons, vehicle, game: str):
|
||||
# 绘制最佳⭐
|
||||
# 武器
|
||||
draw_centered_text(draw=draw, text=bf_item[game][weapon_name].upper(), x_left=1240, x_right=1750, y=1355,
|
||||
font=font_MS, fill="white")
|
||||
draw_centered_text(draw=draw, text=w_kills, x_left=1190, x_right=1390, y=1260, font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=w_kpm, x_left=1510, x_right=1635, y=1260, font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=acc, x_left=1735, x_right=1803, y=1030, font=font_M, fill="white")
|
||||
draw_centered_text(draw=draw, text=headshots, x_left=1695, x_right=1760, y=1200, font=font_M, fill="white")
|
||||
font=font_MS_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=w_kills, x_left=1190, x_right=1390, y=1260, font=font_L_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=w_kpm, x_left=1510, x_right=1635, y=1260, font=font_L_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=acc, x_left=1735, x_right=1803, y=1030, font=font_M_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=headshots, x_left=1695, x_right=1760, y=1200, font=font_M_CH, fill="white")
|
||||
|
||||
# 载具
|
||||
draw_centered_text(draw=draw, text=bf_item[game][vehicle_name].upper(), x_left=1990, x_right=2440, y=1355,
|
||||
font=font_MS, fill="white")
|
||||
draw_centered_text(draw=draw, text=v_kills, x_left=1940, x_right=2150, y=1260, font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=v_kpm, x_left=2295, x_right=2465, y=1055, font=font_XL, fill="white")
|
||||
draw_centered_text(draw=draw, text=destroyed, x_left=2265, x_right=2440, y=1260, font=font_L, fill="white")
|
||||
font=font_MS_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=v_kills, x_left=1940, x_right=2150, y=1260, font=font_L_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=v_kpm, x_left=2295, x_right=2465, y=1055, font=font_XL_CH, fill="white")
|
||||
draw_centered_text(draw=draw, text=destroyed, x_left=2265, x_right=2440, y=1260, font=font_L_CH, fill="white")
|
||||
|
||||
# 图片
|
||||
weapon_url = weapons["image"]
|
||||
@ -208,6 +411,64 @@ def build_best(draw: ImageDraw, img: Image, weapons, vehicle, game: str):
|
||||
return img
|
||||
|
||||
|
||||
def build_best_bf6(draw: ImageDraw, img: Image, weapons, vehicle, classes, game: str):
|
||||
# 最佳武器
|
||||
weapon_name = weapons["weaponName"]
|
||||
w_kills = weapons["kills"]
|
||||
w_kpm = weapons["killsPerMinute"]
|
||||
acc = round(float(weapons["accuracy"].replace("%", "")), 1)
|
||||
headshots = round(float(weapons["headshots"].replace("%", "")), 1)
|
||||
|
||||
# 最佳载具
|
||||
vehicle_name = vehicle["vehicleName"]
|
||||
v_kills = vehicle["kills"]
|
||||
v_kpm = vehicle["killsPerMinute"]
|
||||
destroyed = vehicle["destroyed"]
|
||||
|
||||
# 绘制最佳⭐
|
||||
# 武器
|
||||
# draw_centered_text(draw=draw, text=bf_item[game][weapon_name].upper(), x_left=1240, x_right=1750, y=1355,
|
||||
# font=font_MS, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 7), x_left=95, x_right=313, y=665, font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(w_kills)), x_left=95, x_right=313, y=665,
|
||||
font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=w_kpm, x_left=335, x_right=447, y=665, font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=f"{acc}%", x_left=470, x_right=580, y=670, font=font_M, fill="white")
|
||||
draw_centered_text(draw=draw, text=f"{headshots}%", x_left=610, x_right=717, y=670, font=font_M, fill="white")
|
||||
|
||||
# 载具
|
||||
# draw_centered_text(draw=draw, text=bf_item[game][vehicle_name].upper(), x_left=1990, x_right=2440, y=1355,
|
||||
# font=font_MS, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 7), x_left=95, x_right=313, y=1087,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas(str(v_kills)), x_left=95, x_right=313, y=1087,
|
||||
font=font_L, fill="white")
|
||||
draw_centered_text(draw=draw, text=v_kpm, x_left=351, x_right=463, y=1087, font=font_L, fill="white")
|
||||
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 7), x_left=477, x_right=688, y=1087,
|
||||
font=font_L,
|
||||
fill="#333333")
|
||||
draw_right_aligned_text(draw=draw, text=destroyed, x_left=477, x_right=688, y=1087, font=font_L, fill="white")
|
||||
|
||||
# 图片
|
||||
weapon_url = weapons["image"]
|
||||
vehicle_url = vehicle["image"]
|
||||
|
||||
wp_icon = get_save_icon(game, weapon_name, "weapon", weapon_url)
|
||||
vc_icon = get_save_icon(game, vehicle_name, "vehicles", vehicle_url)
|
||||
classes_icon = get_icon_from_cache(classes, 'bf6', 'classes')
|
||||
wp_icon_new = png_resize(wp_icon, new_width=725 - 88)
|
||||
vc_icon_new = png_resize(vc_icon, new_width=725 - 88)
|
||||
classes_icon_new = png_resize(classes_icon, new_height=1072 - 325)
|
||||
|
||||
img = image_paste(wp_icon_new, img, (88, 325))
|
||||
img = image_paste(vc_icon_new, img, (88, 750))
|
||||
img = image_paste(classes_icon_new, img, (753, 325))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def normalize_for_radar(data, buffer=0.1, scale_min=0.0, scale_max=1.0):
|
||||
"""
|
||||
将数据动态归一化到指定区间 [scale_min, scale_max],
|
||||
@ -237,6 +498,24 @@ def normalize_for_radar(data, buffer=0.1, scale_min=0.0, scale_max=1.0):
|
||||
return norm.tolist()
|
||||
|
||||
|
||||
def add_commas(text):
|
||||
return re.sub(r'(?<!^)(?=(\d{3})+$)', ',', text)
|
||||
|
||||
|
||||
def add_commas_with_padding(value, total_digits):
|
||||
"""
|
||||
固定总位数补零后,再添加千分位分隔符
|
||||
:param value: 输入数字(int / str)
|
||||
:param total_digits: 固定总位数(如 7)
|
||||
:return: 格式化后的字符串,如 '1,234,567'
|
||||
"""
|
||||
# 转字符串并补零
|
||||
text = str(value).zfill(total_digits)
|
||||
|
||||
# 添加千分位
|
||||
return re.sub(r'(?<!^)(?=(\d{3})+$)', ',', text)
|
||||
|
||||
|
||||
# ---------------- 雷达图示例 ----------------
|
||||
def draw_radar(img, stat_data, center=(311, 796), r=185,
|
||||
buffer=0.1, scale_min=0.1, scale_max=1.05):
|
||||
@ -284,7 +563,7 @@ def build_bf6_simple_card(player_info):
|
||||
draw.text((50, 560), f"击杀总计:{player_info['击杀总计']}", fill='black', font=font_M)
|
||||
draw.text((550, 560), f"真人击杀:{player_info['真人击杀']}", fill='black', font=font_M)
|
||||
|
||||
draw.text((50,660), f"游戏助攻:{player_info['游戏助攻']}", fill='black', font=font_M)
|
||||
draw.text((50, 660), f"游戏助攻:{player_info['游戏助攻']}", fill='black', font=font_M)
|
||||
draw.text((550, 660), f"目标占领:{player_info['目标占领']}", fill='black', font=font_M)
|
||||
|
||||
draw.text((50, 760), f"救援数量:{player_info['救援数量']}", fill='black', font=font_M)
|
||||
|
||||
@ -5,6 +5,7 @@ import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import List, Tuple
|
||||
from .param import rank_pic
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@ -15,6 +16,8 @@ import requests
|
||||
import requests.exceptions
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from src.plugins.bf_bot.user_data.data_utils import UserManager, DesignationManager
|
||||
|
||||
filepath = os.path.dirname(__file__).replace("\\", "/")
|
||||
|
||||
|
||||
@ -59,41 +62,72 @@ def circle_corner(img, radii):
|
||||
return img
|
||||
|
||||
|
||||
# PNG重绘大小
|
||||
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=''):
|
||||
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=""):
|
||||
"""
|
||||
PNG缩放透明度处理
|
||||
:param source_file: 源文件(Image.open())
|
||||
:param new_width: 设置的宽度
|
||||
:param new_height: 设置的高度
|
||||
:param resample: 抗锯齿
|
||||
:param ref_file: 参考文件
|
||||
:return:
|
||||
PNG 等比缩放(保持 Alpha 透明度)
|
||||
:param source_file: Image.open() 得到的 Image 对象
|
||||
:param new_width: 目标宽度(可选)
|
||||
:param new_height: 目标高度(可选)
|
||||
:param resample: NEAREST / BILINEAR / BICUBIC / LANCZOS
|
||||
:param ref_file: 参考图片路径(等比适配到参考尺寸)
|
||||
:return: PIL.Image (RGBA)
|
||||
"""
|
||||
img = source_file
|
||||
img = img.convert("RGBA")
|
||||
width, height = img.size
|
||||
|
||||
if ref_file != '':
|
||||
imgRef = Image.open(ref_file)
|
||||
new_width, new_height = imgRef.size
|
||||
img = source_file.convert("RGBA")
|
||||
src_w, src_h = img.size
|
||||
|
||||
# -------------------------
|
||||
# 计算目标尺寸(等比)
|
||||
# -------------------------
|
||||
if ref_file:
|
||||
ref_img = Image.open(ref_file)
|
||||
ref_w, ref_h = ref_img.size
|
||||
|
||||
scale = min(ref_w / src_w, ref_h / src_h)
|
||||
target_w = int(src_w * scale)
|
||||
target_h = int(src_h * scale)
|
||||
|
||||
else:
|
||||
if new_height == 0:
|
||||
new_height = new_width * width / height
|
||||
if new_width > 0 and new_height > 0:
|
||||
scale = min(new_width / src_w, new_height / src_h)
|
||||
target_w = int(src_w * scale)
|
||||
target_h = int(src_h * scale)
|
||||
|
||||
bands = img.split()
|
||||
elif new_width > 0:
|
||||
scale = new_width / src_w
|
||||
target_w = new_width
|
||||
target_h = int(src_h * scale)
|
||||
|
||||
elif new_height > 0:
|
||||
scale = new_height / src_h
|
||||
target_h = new_height
|
||||
target_w = int(src_w * scale)
|
||||
|
||||
else:
|
||||
# 未指定任何尺寸,直接返回
|
||||
return img
|
||||
|
||||
# -------------------------
|
||||
# 重采样方式
|
||||
# -------------------------
|
||||
resample_map = {
|
||||
"NEAREST": Image.NEAREST,
|
||||
"BILINEAR": Image.BILINEAR,
|
||||
"BICUBIC": Image.BICUBIC,
|
||||
"LANCZOS": Image.LANCZOS
|
||||
}
|
||||
resample_method = resample_map.get(resample, Image.LANCZOS) # 默认使用 LANCZOS
|
||||
resample_method = resample_map.get(resample, Image.LANCZOS)
|
||||
|
||||
bands = [b.resize((new_width, new_height), resample=resample_method) for b in bands]
|
||||
resized_file = Image.merge('RGBA', bands)
|
||||
# -------------------------
|
||||
# Alpha 安全 resize(逐通道)
|
||||
# -------------------------
|
||||
bands = img.split()
|
||||
resized_bands = [
|
||||
band.resize((target_w, target_h), resample=resample_method)
|
||||
for band in bands
|
||||
]
|
||||
|
||||
return resized_file
|
||||
return Image.merge("RGBA", resized_bands)
|
||||
|
||||
|
||||
# 图片粘贴
|
||||
@ -200,8 +234,36 @@ def draw_centered_text(draw, text, x_left, x_right, y, font, fill):
|
||||
draw.text((x_center, y), text, font=font, fill=fill)
|
||||
|
||||
|
||||
def draw_right_aligned_text(draw, text, x_left, x_right, y, font, fill):
|
||||
"""
|
||||
在指定区域内从右往左绘制文本(右对齐)
|
||||
:param draw: ImageDraw 对象
|
||||
:param text: 要绘制的文本
|
||||
:param x_left: 区域左边界 x
|
||||
:param x_right: 区域右边界 x(文本右侧贴齐这里)
|
||||
:param y: 文本基线 y 坐标
|
||||
:param font: ImageFont 对象
|
||||
:param fill: 文本颜色
|
||||
"""
|
||||
text = str(text)
|
||||
text_width = font.getlength(text)
|
||||
|
||||
# 右对齐:文本右边贴到 x_right
|
||||
x_start = x_right - text_width
|
||||
|
||||
# 可选:防止越界(超出左边界时截断)
|
||||
if x_start < x_left:
|
||||
x_start = x_left
|
||||
|
||||
draw.text((x_start, y), text, font=font, fill=fill)
|
||||
|
||||
|
||||
def get_save_icon(game, name, icon_type, url):
|
||||
if game == 'bf6':
|
||||
name = name.replace("/", "").upper()
|
||||
else:
|
||||
name = name.replace("/", "")
|
||||
logger.info(f"查询的物品: {name},图标类型:{icon_type},游戏:{game}")
|
||||
icon = get_icon_from_cache(name, game, icon_type)
|
||||
if not icon:
|
||||
if game == "bf1":
|
||||
@ -257,8 +319,10 @@ def get_icon_from_url(url):
|
||||
|
||||
def get_icon_from_cache(icon_name, game, icon_type):
|
||||
path = f"{filepath}/img/{game}/{icon_type}"
|
||||
logger.info(f"查询物品:{icon_name}物品路径:{path}")
|
||||
try:
|
||||
icon_list = os.listdir(path)
|
||||
# logger.info(f"所有物品:{icon_list}")
|
||||
if icon_name in str(icon_list):
|
||||
logger.info(f"本地存在{icon_name}物品")
|
||||
img = Image.open(f"{path}/{icon_name}.png").convert('RGBA')
|
||||
@ -365,3 +429,129 @@ def notice_paste(cv2_bg):
|
||||
front_img = cutout_region(front_img, mask, alpha=0)
|
||||
mix_bg = paste_image_cv2(front_img, cv2_bg, pos)
|
||||
return mix_bg
|
||||
|
||||
|
||||
def notice_paste_bf6(cv2_bg):
|
||||
x, y = 0, 0
|
||||
|
||||
notice_path = f"{filepath}/notice/notice_bf6.png"
|
||||
front_img = cv2.imread(notice_path, cv2.IMREAD_UNCHANGED)
|
||||
|
||||
if front_img is None:
|
||||
raise RuntimeError(f"Failed to load image: {notice_path}")
|
||||
|
||||
h, w = front_img.shape[:2]
|
||||
|
||||
# ROI 边界裁剪
|
||||
bg_h, bg_w = cv2_bg.shape[:2]
|
||||
w = min(w, bg_w - x)
|
||||
h = min(h, bg_h - y)
|
||||
|
||||
roi = cv2_bg[y:y + h, x:x + w]
|
||||
|
||||
# 有 Alpha 通道 → Alpha 混合
|
||||
if front_img.shape[2] == 4:
|
||||
alpha = front_img[:h, :w, 3] / 255.0
|
||||
alpha = alpha[:, :, None]
|
||||
|
||||
cv2_bg[y:y + h, x:x + w] = (
|
||||
alpha * front_img[:h, :w, :3] +
|
||||
(1 - alpha) * roi
|
||||
).astype(np.uint8)
|
||||
|
||||
# 无 Alpha → 直接覆盖
|
||||
else:
|
||||
cv2_bg[y:y + h, x:x + w] = front_img[:h, :w]
|
||||
|
||||
return cv2_bg
|
||||
|
||||
|
||||
def designation_paste(player,
|
||||
cv2_bg,
|
||||
user_id,
|
||||
start_x=10,
|
||||
start_y=10,
|
||||
target_height=40,
|
||||
spacing=6
|
||||
):
|
||||
designation_list_path = get_designation_list_by_user_id(user_id, player)
|
||||
if not designation_list_path:
|
||||
return cv2_bg
|
||||
|
||||
icons = []
|
||||
total_width = 0
|
||||
|
||||
for path in designation_list_path:
|
||||
icon = cv2.imread(path, cv2.IMREAD_UNCHANGED)
|
||||
if icon is None:
|
||||
continue
|
||||
|
||||
h, w = icon.shape[:2]
|
||||
scale = target_height / h
|
||||
new_w = int(w * scale)
|
||||
|
||||
icon = cv2.resize(icon, (new_w, target_height))
|
||||
icons.append(icon)
|
||||
total_width += new_w
|
||||
|
||||
total_width += spacing * (len(icons) - 1)
|
||||
|
||||
x = start_x
|
||||
y = start_y # 关键变化点:直接使用 top-left
|
||||
|
||||
bg_h, bg_w = cv2_bg.shape[:2]
|
||||
|
||||
for icon in icons:
|
||||
ih, iw = icon.shape[:2]
|
||||
|
||||
if x + iw > bg_w or y + ih > bg_h:
|
||||
break
|
||||
|
||||
roi = cv2_bg[y:y + ih, x:x + iw]
|
||||
|
||||
if icon.shape[2] == 4:
|
||||
alpha = icon[:, :, 3] / 255.0
|
||||
for c in range(3):
|
||||
roi[:, :, c] = roi[:, :, c] * (1 - alpha) + icon[:, :, c] * alpha
|
||||
else:
|
||||
roi[:] = icon
|
||||
|
||||
cv2_bg[y:y + ih, x:x + iw] = roi
|
||||
x += iw + spacing
|
||||
|
||||
return cv2_bg
|
||||
|
||||
|
||||
def get_designation_list_by_user_id(user_id, player):
|
||||
usermanager = UserManager()
|
||||
designationmanager = DesignationManager()
|
||||
user_info = usermanager.get_user_by_qq(user_id)
|
||||
icon_no_list = []
|
||||
if user_info is not None and user_info['ea_player_name'].upper() == player.upper():
|
||||
logger.info(f"玩家标签列表:{user_info}")
|
||||
icon_no_list = user_info['designation']
|
||||
icon_list = designationmanager.get_designation_by_id_list(icon_no_list)
|
||||
icon_path_list = []
|
||||
for icon in icon_list:
|
||||
icon_file_name = icon['icon_path']
|
||||
icon_path_list.append(f"{filepath}/img/icon/designation/{icon_file_name}")
|
||||
return icon_path_list
|
||||
|
||||
|
||||
def paste_rank_icon(rank, img):
|
||||
rank_pic_path = get_rank_pic(rank)
|
||||
rank_icon_path = f"{filepath}/img/icon/rank/{rank_pic_path}"
|
||||
rank_icon = Image.open(rank_icon_path)
|
||||
rank_icon = png_resize(rank_icon, new_width=132, new_height=192)
|
||||
fm_img = image_paste(rank_icon, img, (946, 61))
|
||||
return fm_img
|
||||
|
||||
|
||||
def get_rank_pic(rank: int) -> str:
|
||||
"""
|
||||
根据 rank 返回对应的 rank_pic 路径
|
||||
"""
|
||||
for (start, end), info in rank_pic.items():
|
||||
if start <= rank <= end:
|
||||
return info["path"]
|
||||
return "t_ui_rankswatch_1-9 1.png"
|
||||
|
||||
@ -60,6 +60,28 @@ classes = {
|
||||
"Tanker": "坦克"
|
||||
}
|
||||
|
||||
mods = {
|
||||
|
||||
}
|
||||
|
||||
rank_pic = {
|
||||
(0, 9): {"path": "t_ui_rankswatch_1-9 1.png"},
|
||||
(10, 24): {"path": "t_ui_rankswatch_10-24 1.png"},
|
||||
(25, 44): {"path": "t_ui_rankswatch_25-44 1.png"},
|
||||
(45, 49): {"path": "t_ui_rankswatch_45-49 1.png"},
|
||||
(50, 99): {"path": "t_ui_rankswatch_50-99 1.png"},
|
||||
(100, 149): {"path": "t_ui_rankswatch_100-149 1.png"},
|
||||
(150, 199): {"path": "t_ui_rankswatch_150-190 1.png"},
|
||||
(200, 249): {"path": "t_ui_rankswatch_200-240 1.png"},
|
||||
(250, 299): {"path": "t_ui_rankswatch_250-290 1.png"},
|
||||
(300, 349): {"path": "t_ui_rankswatch_300-340 1.png"},
|
||||
(350, 399): {"path": "t_ui_rankswatch_350-390 1.png"},
|
||||
(400, 449): {"path": "t_ui_rankswatch_400-450 1.png"},
|
||||
(450, 499): {"path": "t_ui_rankswatch_450-490 1.png"},
|
||||
(500, 2999): {"path": "t_ui_rankswatch_500-3000 1.png"},
|
||||
(3000, 5000): {"path": "t_ui_rankswatch_3000-5000 1.png"},
|
||||
}
|
||||
|
||||
bf_item = {
|
||||
"bf1": {
|
||||
"Wex": "韦克斯火焰喷射器",
|
||||
@ -943,7 +965,19 @@ bf_item = {
|
||||
"AH-6J-LITTLE-BIRD1": "AH-6J-LITTLE-BIRD1",
|
||||
"T-90A1": "T-90A1",
|
||||
"HT-95-LEVKOV": "HT-95-LEVKOV"
|
||||
}
|
||||
},
|
||||
"bf6": {'L110': 'L110', 'PW5A3': 'PW5A3', 'M433': 'M433', 'RPKM': 'RPKM', 'M87A1': 'M87A1', 'P18': 'P18',
|
||||
'M277': 'M277', 'B36A4': 'B36A4', 'L85A3': 'L85A3', 'M2010 ESR': 'M2010 ESR', 'M1014': 'M1014',
|
||||
'AK-205': 'AK-205', 'SVK-8.6': 'SVK-8.6', 'SGX': 'SGX', 'LMR27': 'LMR27', 'QBZ-192': 'QBZ-192',
|
||||
'M417 A2': 'M417 A2', 'DRS-IAR': 'DRS-IAR', 'KORD 6P67': 'KORD 6P67', 'USG-90': 'USG-90', 'M4A1': 'M4A1',
|
||||
'KTS100 MK8': 'KTS100 MK8', 'M45A1': 'M45A1', 'KV9': 'KV9', 'SOR-556 Mk2': 'SOR-556 MK2', 'PW7A2': 'PW7A2',
|
||||
'M123K': 'M123K', 'M44': 'M44', 'M250': 'M250', 'UMG-40': 'UMG-40', 'TR-7': 'TR-7', 'SL9': 'SL9',
|
||||
'M240L': 'M240L', 'SCW-10': 'SCW-10', 'NVO-228E': 'NVO-228E', 'M/60': 'M/60', 'PSR': 'PSR', 'SVDM': 'SVDM',
|
||||
'AK4D': 'AK4D', 'SOR-300SC': 'SOR-300SC', 'SG 553R': 'SG 553R', 'GRT-BC': 'GRT-BC', '18.5KS-K': '18.5KS-K',
|
||||
'ES 5.7': 'ES 5.7', 'M39 EMR': 'M39 EMR', 'SV-98': 'SV-98', 'Panthera KHT': 'PANTHERA KHT',
|
||||
'M77E Falchion': 'M77E FALCHION', 'Leo A4': 'LEO A4', 'Strf 09 A4': 'STRF 09 A4',
|
||||
'M1A2 SEPv3': 'M1A2 SEPV3', 'Cheetah 1A2': 'CHEETAH 1A2', 'Glider 96': 'GLIDER 96',
|
||||
'M3A3 Bradley': 'M3A3 BRADLEY', 'Su-57': 'SU-57', 'F-61V': 'F-61V', 'F-39E': 'F-39E', 'VECTOR': 'VECTOR'}
|
||||
}
|
||||
|
||||
color_select = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
110
src/plugins/bf_bot/tracker_data.py
Normal file
110
src/plugins/bf_bot/tracker_data.py
Normal file
@ -0,0 +1,110 @@
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from nonebot import logger
|
||||
from playwright.async_api import async_playwright, Response
|
||||
|
||||
|
||||
async def fetch_tracker_search(
|
||||
search_url: str,
|
||||
cookies_txt: str,
|
||||
wait_seconds: int = 5,
|
||||
) -> Optional[Dict]:
|
||||
"""
|
||||
使用 Playwright + cookies 访问 tracker.gg API 并截获响应数据
|
||||
"""
|
||||
cookies = parse_netscape_cookies(cookies_txt)
|
||||
if not cookies:
|
||||
logger.error("cookies.txt 解析失败或为空")
|
||||
return None
|
||||
|
||||
result: Optional[Dict] = None
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(
|
||||
executable_path="C:/Program Files/Google/Chrome/Application/chrome.exe",
|
||||
headless=True, # False 可以看到浏览器操作
|
||||
args=[
|
||||
"--autoplay-policy=no-user-gesture-required",
|
||||
"--disable-features=AutoplayDisableSuppression",
|
||||
"--use-fake-ui-for-media-stream",
|
||||
]
|
||||
)
|
||||
|
||||
context = await browser.new_context(
|
||||
viewport={"width": 640, "height": 320},
|
||||
user_agent=(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/143.0.0.0 Safari/537.36"
|
||||
),
|
||||
)
|
||||
|
||||
await context.add_cookies(cookies)
|
||||
page = await context.new_page()
|
||||
|
||||
# 响应监听
|
||||
async def handle_response(response: Response):
|
||||
nonlocal result
|
||||
try:
|
||||
if search_url in response.url:
|
||||
# 尝试解析 JSON
|
||||
data = await response.json()
|
||||
if data and "data" in data:
|
||||
result = data
|
||||
logger.info(f"成功截获响应数据: {data}")
|
||||
except Exception as e:
|
||||
logger.warning(f"解析响应失败: {e}")
|
||||
|
||||
page.on("response", handle_response)
|
||||
|
||||
try:
|
||||
# 直接访问 API 链接
|
||||
await page.goto(search_url, wait_until="domcontentloaded", timeout=60000)
|
||||
# 等待响应截获
|
||||
await page.wait_for_timeout(wait_seconds * 1000)
|
||||
except Exception as e:
|
||||
logger.error(f"访问页面出错: {e}")
|
||||
finally:
|
||||
await browser.close()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_netscape_cookies(cookies_txt_path: str) -> List[Dict]:
|
||||
"""
|
||||
解析 Netscape HTTP Cookie File -> Playwright cookies
|
||||
"""
|
||||
cookies: List[Dict] = []
|
||||
|
||||
if not os.path.exists(cookies_txt_path):
|
||||
return cookies
|
||||
|
||||
with open(cookies_txt_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
parts = line.split("\t")
|
||||
if len(parts) != 7:
|
||||
continue
|
||||
|
||||
domain, include_sub, path, secure, expiry, name, value = parts
|
||||
|
||||
cookie = {
|
||||
"name": name,
|
||||
"value": value,
|
||||
"domain": domain,
|
||||
"path": path,
|
||||
"secure": secure.upper() == "TRUE",
|
||||
"httpOnly": False,
|
||||
}
|
||||
|
||||
if expiry.isdigit() and int(expiry) > 0:
|
||||
cookie["expires"] = int(expiry)
|
||||
|
||||
cookies.append(cookie)
|
||||
|
||||
return cookies
|
||||
@ -1,10 +1,12 @@
|
||||
import sqlite3
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
class TableManager:
|
||||
def __init__(self, db_path: str = 'user_data.db'):
|
||||
self.db_path = db_path
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
self.db_path = os.path.join(base_dir, 'user_data.db')
|
||||
|
||||
def _execute(self, sql: str, params: tuple = ()) -> sqlite3.Cursor:
|
||||
"""执行SQL语句的通用方法"""
|
||||
@ -17,76 +19,115 @@ class TableManager:
|
||||
|
||||
|
||||
class UserManager(TableManager):
|
||||
def add_user(self, qq_id: str, ea_id: str, dog_tag_list: str) -> int:
|
||||
"""添加用户记录"""
|
||||
def add_user(self, qq_id: str, ea_player_name: str, ea_player_id: str, ea_user_id: str, designation: str,
|
||||
medals: str) -> int:
|
||||
"""添加用户"""
|
||||
cursor = self._execute(
|
||||
"INSERT INTO users (qq_id, ea_id, dog_tag_list) VALUES (?, ?, ?)",
|
||||
(qq_id, ea_id, dog_tag_list)
|
||||
"INSERT INTO users (qq_id, ea_player_name,ea_player_id,ea_user_id, designation,medals) VALUES (?, ?, ?, ?, ?,?)",
|
||||
(qq_id, ea_player_name, ea_player_id, ea_user_id, designation, medals)
|
||||
)
|
||||
return cursor.lastrowid
|
||||
|
||||
def update_user(self, qq_id: str, ea_player_name: str, ea_player_id: str, ea_user_id: str, designation: str,
|
||||
medals: str) -> int:
|
||||
"""修改用户"""
|
||||
cursor = self._execute(
|
||||
"UPDATE users SET ea_player_name = ?, ea_player_id = ?,ea_user_id = ? WHERE qq_id = ?",
|
||||
(ea_player_name, ea_player_id, ea_user_id, qq_id)
|
||||
)
|
||||
return cursor.rowcount
|
||||
|
||||
def get_user_by_qq(self, qq_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""通过QQ号查询用户"""
|
||||
cursor = self._execute(
|
||||
"SELECT * FROM users WHERE qq_id = ?",
|
||||
(qq_id,)
|
||||
)
|
||||
return dict(cursor.fetchone()) if cursor.fetchone() else None
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def update_dog_tags(self, user_id: int, new_tags: str) -> bool:
|
||||
def get_user_by_ea_id(self, ea_user_id: str) -> Optional[Dict[str, Any]]:
|
||||
cursor = self._execute(
|
||||
"SELECT * FROM users WHERE ea_user_id = ?",
|
||||
(ea_user_id,)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def delete_user_by_qq(self, qq_id: str) -> Optional[Dict[str, Any]]:
|
||||
cursor = self._execute(
|
||||
"DELETE FROM users WHERE qq_id = ?",
|
||||
(qq_id,)
|
||||
)
|
||||
return cursor.rowcount
|
||||
|
||||
def update_dog_tags(self, qq_id: int, medals: str) -> bool:
|
||||
"""更新用户的狗牌列表"""
|
||||
self._execute(
|
||||
"UPDATE users SET dog_tag_list = ? WHERE id = ?",
|
||||
(new_tags, user_id)
|
||||
"UPDATE users SET medals = ? WHERE id = ?",
|
||||
(medals, qq_id)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class DogTagManager(TableManager):
|
||||
def create_tag(self, name: str) -> int:
|
||||
"""创建新狗牌"""
|
||||
class DesignationManager(TableManager):
|
||||
def create_designation(self, name: str, icon_path: str) -> int:
|
||||
"""创建新标签"""
|
||||
cursor = self._execute(
|
||||
"INSERT INTO dog_tag (name) VALUES (?)",
|
||||
(name,)
|
||||
"INSERT INTO designation (name,icon_path) VALUES (?,?)",
|
||||
(name, icon_path)
|
||||
)
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_all_tags(self) -> List[Dict[str, Any]]:
|
||||
"""获取所有狗牌"""
|
||||
cursor = self._execute("SELECT * FROM dog_tag")
|
||||
def get_all_designation(self) -> List[Dict[str, Any]]:
|
||||
"""获取所有标签"""
|
||||
cursor = self._execute("SELECT * FROM designation")
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
def get_designation_by_id_list(self, ids: List[int]) -> List[Dict[str, Any]]:
|
||||
"""根据 ID 列表获取标签"""
|
||||
|
||||
if not ids:
|
||||
return []
|
||||
|
||||
placeholders = ",".join(["?"] * len(ids))
|
||||
sql = f"""
|
||||
SELECT *
|
||||
FROM designation
|
||||
WHERE id IN ({placeholders})
|
||||
"""
|
||||
|
||||
cursor = self._execute(sql, ids)
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
|
||||
class QueryRecordManager(TableManager):
|
||||
def log_query(self, user_id: str, target_id: str, status: str) -> int:
|
||||
"""记录查询操作"""
|
||||
cursor = self._execute(
|
||||
"""INSERT INTO query_record
|
||||
(user_id, target_id, status)
|
||||
VALUES (?, ?, ?)""",
|
||||
(user_id, target_id, status)
|
||||
)
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_user_history(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
"""获取用户查询历史"""
|
||||
cursor = self._execute(
|
||||
"SELECT * FROM query_record WHERE user_id = ?",
|
||||
(user_id,)
|
||||
)
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
# class QueryRecordManager(TableManager):
|
||||
# def log_query(self, user_id: str, target_id: str, status: str) -> int:
|
||||
# """记录查询操作"""
|
||||
# cursor = self._execute(
|
||||
# """INSERT INTO query_record
|
||||
# (user_id, target_id, status)
|
||||
# VALUES (?, ?, ?)""",
|
||||
# (user_id, target_id, status)
|
||||
# )
|
||||
# return cursor.lastrowid
|
||||
#
|
||||
# def get_user_history(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
# """获取用户查询历史"""
|
||||
# cursor = self._execute(
|
||||
# "SELECT * FROM query_record WHERE user_id = ?",
|
||||
# (user_id,)
|
||||
# )
|
||||
# return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 使用示例
|
||||
user_db = UserManager()
|
||||
user_id = user_db.add_user("123456", "EA_001", "tag1,tag2")
|
||||
print(f"Created user with ID: {user_id}")
|
||||
tag_db = DesignationManager()
|
||||
# tag_id = tag_db.create_designation("DEV","DEV.png")
|
||||
# tag_id = tag_db.create_designation("DICE", "DICE.png")
|
||||
# tag_id = tag_db.create_designation("IC", "IC.png")
|
||||
# tag_id = tag_db.create_designation("OWNER", "OWNER.png")
|
||||
# print(f"Created tag with ID: {tag_id}")
|
||||
|
||||
tag_db = DogTagManager()
|
||||
tag_id = tag_db.create_tag("新狗牌")
|
||||
print(f"Created tag with ID: {tag_id}")
|
||||
record_id = tag_db.get_designation_by_id_list([1, 2, 3])
|
||||
|
||||
record_db = QueryRecordManager()
|
||||
record_id = record_db.log_query("123", "456", "success")
|
||||
print(f"Logged query with ID: {record_id}")
|
||||
|
||||
@ -11,20 +11,32 @@ class DatabaseManager:
|
||||
'users': [
|
||||
('id', 'INTEGER PRIMARY KEY AUTOINCREMENT'),
|
||||
('qq_id', 'TEXT NOT NULL'),
|
||||
('ea_id', 'TEXT UNIQUE'),
|
||||
('dog_tag_list', 'TEXT NOT NULL'),
|
||||
('ea_player_name', 'TEXT UNIQUE'),
|
||||
('ea_player_id', 'TEXT UNIQUE'),
|
||||
('ea_user_id', 'TEXT UNIQUE'),
|
||||
('designation', 'TEXT NOT NULL'),
|
||||
('medals', 'TEXT NOT NULL'),
|
||||
('create_time', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP')
|
||||
],
|
||||
'dog_tag': [
|
||||
'designation': [
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('name', 'TEXT NOT NULL'),
|
||||
('icon_path', 'TEXT NOT NULL'),
|
||||
('create_time', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP')
|
||||
],
|
||||
'medals': [
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('name', 'TEXT NOT NULL'),
|
||||
('icon_path', 'TEXT NOT NULL'),
|
||||
('create_time', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP')
|
||||
]
|
||||
,
|
||||
'query_record': [
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('user_id', 'TEXT NOT NULL'),
|
||||
('target_id', 'TEXT NOT NULL'),
|
||||
('ea_player_name', 'TEXT NOT NULL'),
|
||||
('ea_player_id', 'TEXT NOT NULL'),
|
||||
('ea_user_id', 'TEXT NOT NULL'),
|
||||
('status', 'TEXT NOT NULL'),
|
||||
('create_time', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP')
|
||||
]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user