From c32d0dfe8381d3451373867734a8986fdd6cd411 Mon Sep 17 00:00:00 2001 From: "Mr.Xia" <1424473282@qq.com> Date: Mon, 14 Sep 2026 10:38:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(help):=20=E6=96=B0=E5=A2=9E=E3=80=8C?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E3=80=8D=E5=85=B3=E9=94=AE=E8=AF=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=9C=80=E6=96=B0=E8=BF=90?= =?UTF-8?q?=E8=90=A5=E6=B4=BB=E5=8A=A8=E6=B5=B7=E6=8A=A5=20(=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=90=88=E5=B9=B6=E8=BD=AC=E5=8F=91=E4=B8=8E=E6=97=A0?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E5=BC=95=E5=AF=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLUGINS.md | 3 +- .../plugins/danding_help/HelpCaiDan.md | 3 +- danding_bot/plugins/danding_help/config.py | 14 +- danding_bot/plugins/danding_help/help.py | 151 +++++++++++++++++- 4 files changed, 166 insertions(+), 5 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 2a48714..848117e 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -141,7 +141,8 @@ 系统的官方指引手册。 -- **主要命令**: `帮助`、`下载`、`正式版如何运行` 等。 +- **主要命令**: `帮助`、`下载`、`活动`(别名:`最新活动`、`群活动`、`hd`)、`每日试用`、`正式版如何运行` 等。 +- **活动海报推送**: 发送 `活动` 时自动请求服务端接口获取当前正在进行中的运营活动,并向群内发送对应的活动宣传海报及说明。 - **限制**: 仅在特定群 `621016172` 可用。 ### 13. command_list - 指令列表 diff --git a/danding_bot/plugins/danding_help/HelpCaiDan.md b/danding_bot/plugins/danding_help/HelpCaiDan.md index 013e6ec..41ee4d4 100644 --- a/danding_bot/plugins/danding_help/HelpCaiDan.md +++ b/danding_bot/plugins/danding_help/HelpCaiDan.md @@ -11,5 +11,6 @@ 2. 正式版如何运行(htr) ## 活动🔥🔥 -1. 每日试用(mrss) - 永久活动,单设备每日可试用1小时! +1. 活动(hd) - 获取当前最新活动海报与详情! +2. 每日试用(mrss) - 永久活动,单设备每日可试用1小时! diff --git a/danding_bot/plugins/danding_help/config.py b/danding_bot/plugins/danding_help/config.py index 2fcc6ef..26afdc7 100644 --- a/danding_bot/plugins/danding_help/config.py +++ b/danding_bot/plugins/danding_help/config.py @@ -16,7 +16,8 @@ class Config(BaseModel): 2. 正式版如何运行 ## 活动 -1. 每日试用""" +1. 活动 +2. 每日试用""" DowndLoadStr:str = """\ 公益版提供群文件下载一种方式; @@ -42,4 +43,13 @@ class Config(BaseModel): DailyTrialStr:str = """\ 永久活动-每日试用:单设备支持每日试用1小时的脚本时长! -请在购买或支持蛋定助手前,一定要先试用,确保自己可以正常使用脚本!""" \ No newline at end of file +请在购买或支持蛋定助手前,一定要先试用,确保自己可以正常使用脚本!""" + + NoActivityStr: str = """\ +📢 当前暂无进行中的限时运营活动哦~ +🎁 常规福利: +1. 发送【每日试用】获取单设备每日1小时试用时长! +2. 发送【签到】获取每日免费积分奖励! +📖 发送【帮助】查看更多功能菜单。""" + + DDApi_Host: str = "https://api.danding.vip/DD/" \ No newline at end of file diff --git a/danding_bot/plugins/danding_help/help.py b/danding_bot/plugins/danding_help/help.py index c70e41a..26d2aa9 100644 --- a/danding_bot/plugins/danding_help/help.py +++ b/danding_bot/plugins/danding_help/help.py @@ -1,8 +1,12 @@ -from nonebot import on_command, get_plugin_config,logger +from nonebot import on_command, get_plugin_config, get_driver, logger from nonebot.rule import Rule, fullmatch from .config import Config import os +import io +import aiohttp +from PIL import Image as PILImage from nonebot_plugin_saa import Text, Image, MessageFactory +from nonebot.adapters.onebot.v11 import Bot as V11Bot, Message as V11Message, MessageSegment as V11MessageSegment from nonebot.adapters.onebot.v11.event import GroupMessageEvent import random import asyncio @@ -95,3 +99,148 @@ daily_trial = on_command("每日试用", rule=_group_rule & fullmatch(('每日 async def _handle_daily_trial(): await asyncio.sleep(random.uniform(2, 3)) await daily_trial.finish(plugin_config.DailyTrialStr) + +activity = on_command("活动", rule=_group_rule & fullmatch(('活动', '最新活动', '群活动', 'hd')), aliases={"最新活动", "群活动", "hd"}, priority=1, block=True) +@activity.handle() +async def _handle_activity(bot: V11Bot, event: GroupMessageEvent): + await asyncio.sleep(random.uniform(1.5, 2.5)) + driver_config = get_driver().config + api_host = ( + getattr(driver_config, "danding_api_host", None) + or os.environ.get("DANDING_API_HOST") + or getattr(plugin_config, "DDApi_Host", "https://api.danding.vip/DD/") + ).rstrip("/") + target_url = f"{api_host}/get_active_activities" + + try: + async with aiohttp.ClientSession() as session: + async with session.get(target_url, timeout=aiohttp.ClientTimeout(total=10)) as resp: + if resp.status != 200: + logger.error(f"[Help] 获取活动列表状态码异常: {resp.status}") + await activity.finish("获取活动信息失败,请稍后再试") + res = await resp.json() + except Exception as e: + logger.error(f"[Help] 获取活动列表请求失败: {e}") + await activity.finish("活动服务暂时不可用,请稍后再试") + return + + items = res.get("data", []) + if not isinstance(items, list) or not items: + await activity.finish(plugin_config.NoActivityStr) + return + + poster_items = [it for it in items if it.get("poster_url") or it.get("poster_path")] + + # 无海报时回退为文字列表 + if not poster_items: + msg_lines = ["📢 当前运营活动:"] + for it in items: + name = it.get("活动名", "未知活动") + start = it.get("活动开始时间", "") + end = it.get("活动截止时间", "") + notice = it.get("活动公告", "") + msg_lines.append(f"\n【{name}】\n时间:{start} ~ {end}\n说明:{notice}") + await MessageFactory([Text("\n".join(msg_lines))]).send(at_sender=True, reply=True) + return + + # 单个活动直接以图文消息发送 + if len(poster_items) == 1: + it = poster_items[0] + poster_url = it.get("poster_url") + if not poster_url and it.get("poster_path"): + poster_url = f"{api_host}/activity-posters/{it['poster_path'].replace('activity-posters/', '')}" + + name = it.get("活动名", "运营活动") + start = it.get("活动开始时间", "") + end = it.get("活动截止时间", "") + announcement = it.get("活动公告", "") + + desc = f"🎉【{name}】\n⏰ 活动时间:{start} 至 {end}" + if announcement: + desc += f"\n📝 活动说明:{announcement}" + + try: + async with aiohttp.ClientSession() as session: + async with session.get(poster_url, timeout=aiohttp.ClientTimeout(total=15)) as img_resp: + if img_resp.status == 200: + img_bytes = await img_resp.read() + try: + with PILImage.open(io.BytesIO(img_bytes)) as pil_img: + out_io = io.BytesIO() + pil_img.convert("RGB").save(out_io, format="JPEG", quality=90) + img_bytes = out_io.getvalue() + except Exception as conv_err: + logger.warning(f"[Help] 海报图片格式转换跳过: {conv_err}") + + await MessageFactory([Text(desc), Image(img_bytes)]).send(at_sender=True, reply=True) + return + except Exception as e: + logger.error(f"[Help] 单活动海报发送异常: {e}") + await activity.finish("当前活动海报加载失败,请稍后重试") + return + + # 多个活动:打包为合并转发消息发送,避免多图刷屏 + nodes = [] + bot_info = await bot.get_login_info() + bot_id = bot_info.get("user_id", event.self_id) + bot_name = bot_info.get("nickname", "蛋定助手") + + # 首节点:概览提示 + summary_text = f"📢 当前共有 {len(poster_items)} 个运营活动进行中:\n" + "\n".join( + [f"{idx + 1}. {it.get('活动名', '未知活动')}" for idx, it in enumerate(poster_items)] + ) + nodes.append(V11MessageSegment.node_custom( + user_id=bot_id, + nickname=bot_name, + content=V11Message([V11MessageSegment.text(summary_text)]) + )) + + async with aiohttp.ClientSession() as session: + for it in poster_items: + poster_url = it.get("poster_url") + if not poster_url and it.get("poster_path"): + poster_url = f"{api_host}/activity-posters/{it['poster_path'].replace('activity-posters/', '')}" + + name = it.get("活动名", "运营活动") + start = it.get("活动开始时间", "") + end = it.get("活动截止时间", "") + announcement = it.get("活动公告", "") + + card_msg = V11Message() + card_desc = f"🎉【{name}】\n⏰ 活动时间:{start} 至 {end}" + if announcement: + card_desc += f"\n📝 活动说明:{announcement}" + card_msg.append(V11MessageSegment.text(card_desc)) + + try: + async with session.get(poster_url, timeout=aiohttp.ClientTimeout(total=15)) as img_resp: + if img_resp.status == 200: + img_bytes = await img_resp.read() + try: + with PILImage.open(io.BytesIO(img_bytes)) as pil_img: + out_io = io.BytesIO() + pil_img.convert("RGB").save(out_io, format="JPEG", quality=90) + img_bytes = out_io.getvalue() + except Exception as conv_err: + logger.warning(f"[Help] 海报图片转换跳过: {conv_err}") + card_msg.append(V11MessageSegment.image(img_bytes)) + except Exception as dl_err: + logger.warning(f"[Help] 多活动海报拉取失败({name}): {dl_err}") + + nodes.append(V11MessageSegment.node_custom( + user_id=bot_id, + nickname=bot_name, + content=card_msg + )) + + try: + await bot.send_group_forward_msg(group_id=event.group_id, messages=nodes) + except Exception as fwd_err: + logger.error(f"[Help] 发送合并转发失败: {fwd_err},尝试降级逐条发送") + # 降级处理 + for node in nodes[1:]: + try: + await bot.send_group_msg(group_id=event.group_id, message=node.data["content"]) + await asyncio.sleep(1) + except Exception as snd_err: + logger.error(f"[Help] 降级发送单条失败: {snd_err}")