- 群分析: 新增 history_store 只读本地消息源(读 learning_chat 落库,含 uninfo 昵称补齐/去重/截断判定),适配器优先读本地库、失败回退 OneBot 分页;分页 锚点字段回退链修复 NapCat 传 message_seq 翻页断裂;新增「本地记录」开关 - core/message_utils: 新增 common_proc_reply 占位图通用回复(引用消息 + 处理中 动图,支持后台任务显式指定 target),群分析/战况/倒放改用 - web_hub + web: 日志页改固定窗口滚动 + 翻页锚定 + 自动换行,SSE 日志轮转发 reset 帧,入口 HTML no-cache,行数统计增量缓存,CPU 改非阻塞采样,登录信息缓存 - 插件内 CLAUDE.md / DESIGN.md 不入库(.gitignore),galgame_card 两份文档取消 跟踪(文件保留在磁盘) Co-Authored-By: Claude Code <noreply@anthropic.com>
131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
"""绝地潜兵小助手 —— 触发器(Trigger)层。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import re
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
import httpx
|
||
from nonebot import logger, on_command
|
||
from nonebot.adapters.onebot.v11 import MessageEvent, MessageSegment
|
||
from nonebot.internal.params import ArgPlainText
|
||
from nonebot.matcher import Matcher
|
||
from PIL import Image
|
||
|
||
from hexi.core import message_utils
|
||
from ..services.equipment import get_equipment_by_combination, get_random_equipment
|
||
from ..services.hd2_api import get_briefing_data
|
||
from ..utils import gen_ms_img, pic2b64
|
||
from ..utils.war_renderer import render_war_briefing
|
||
|
||
_BASE_DIR = Path(__file__).resolve().parents[1]
|
||
_IMG_DIR = _BASE_DIR / "res" / "img"
|
||
|
||
|
||
async def _send_war_card(matcher: Matcher, ev: MessageEvent, top_per_race: Optional[int]) -> None:
|
||
await message_utils.common_proc_reply(ev.message_id)
|
||
try:
|
||
data = await get_briefing_data()
|
||
png = await render_war_briefing(data, top_per_race)
|
||
except Exception as e:
|
||
logger.exception("获取战况失败")
|
||
await matcher.finish(
|
||
MessageSegment.reply(ev.message_id)
|
||
+ f"战况获取失败:{type(e).__name__}:{e}"
|
||
)
|
||
return
|
||
mes = (MessageSegment.reply(ev.message_id), gen_ms_img(png))
|
||
await matcher.send(mes)
|
||
|
||
|
||
war_situation = on_command("简报", aliases={"简报"})
|
||
war_detail = on_command("详报", aliases={"详报"})
|
||
|
||
|
||
@war_situation.handle()
|
||
async def get_war_info(ev: MessageEvent):
|
||
await _send_war_card(war_situation, ev, top_per_race=4)
|
||
|
||
|
||
@war_detail.handle()
|
||
async def get_war_detail(ev: MessageEvent):
|
||
await _send_war_card(war_detail, ev, top_per_race=None)
|
||
|
||
|
||
async def download_url(url: str) -> bytes | None:
|
||
async with httpx.AsyncClient() as client:
|
||
for i in range(3):
|
||
try:
|
||
resp = await client.get(url)
|
||
if resp.status_code != 200:
|
||
continue
|
||
return resp.content
|
||
except Exception as e:
|
||
logger.error(f"Error downloading {url}, retry {i}/3: {str(e)}")
|
||
return None
|
||
|
||
|
||
random_helldivers = on_command("随机战备", aliases={"随机战备"}, block=True)
|
||
|
||
board_path = str(_IMG_DIR / "board2.png")
|
||
PROMPT = MessageSegment.image(pic2b64(Image.open(board_path)))
|
||
|
||
error_count = 0
|
||
|
||
|
||
@random_helldivers.got("pick_type", prompt=PROMPT)
|
||
async def got_random_helldivers(event: MessageEvent, pick_type: str = ArgPlainText()):
|
||
global error_count
|
||
if error_count >= 2:
|
||
await random_helldivers.finish("已回归平民生活——")
|
||
logger.info("持续输错退出随机战备")
|
||
error_count = 0
|
||
return
|
||
logger.info(f"用户选择的战备类型: {pick_type}")
|
||
if not is_number(pick_type):
|
||
error_count += 1
|
||
await random_helldivers.reject(f"您输入的 {pick_type} 非数字,请重新输入1到11,或者输入0退出")
|
||
elif int(pick_type) not in range(12):
|
||
error_count += 1
|
||
await random_helldivers.reject(f"您输入的 {pick_type} 不在范围内,请重新输入1到11,或者输入0退出")
|
||
elif int(pick_type) == 0:
|
||
await random_helldivers.finish("已回归平民生活——")
|
||
logger.info("用户选择退出随机战备")
|
||
error_count = 0
|
||
return
|
||
|
||
mix_msg = (MessageSegment.reply(event.message_id),)
|
||
|
||
type_combinations = {
|
||
2: {"red": 2, "blue": 1, "green": 1},
|
||
3: {"green": 2, "red": 1, "blue": 1},
|
||
4: {"blue": 2, "green": 1, "red": 1},
|
||
5: {"red": 3, "blue": 1},
|
||
6: {"green": 3, "blue": 1},
|
||
7: {"red": 2, "blue": 2},
|
||
8: {"blue": 2, "green": 2},
|
||
9: {"green": 2, "red": 2},
|
||
10: {"red": 4},
|
||
11: {"green": 4},
|
||
}
|
||
|
||
if int(pick_type) == 1:
|
||
logger.info("用户选择纯随机战备")
|
||
result = await get_random_equipment(4)
|
||
else:
|
||
combination = type_combinations.get(int(pick_type))
|
||
logger.info(f"用户选择的战备组合: {combination}")
|
||
result = await get_equipment_by_combination(combination)
|
||
|
||
final_msg = MessageSegment.text("您的随机结果是:\n")
|
||
image_tuple = gen_ms_img(result)
|
||
mix_msg += (final_msg, image_tuple)
|
||
|
||
await random_helldivers.finish(mix_msg)
|
||
|
||
|
||
def is_number(s):
|
||
return bool(re.match(r"^[0-9]+$", s))
|