feat(help): 新增「活动」关键词获取服务器最新运营活动海报 (支持合并转发与无活动引导)

This commit is contained in:
2026-09-14 10:38:17 +08:00
parent c00e917d55
commit c32d0dfe83
4 changed files with 166 additions and 5 deletions

View File

@@ -141,7 +141,8 @@
系统的官方指引手册。 系统的官方指引手册。
- **主要命令**: `帮助``下载``正式版如何运行` 等。 - **主要命令**: `帮助``下载``活动`(别名:`最新活动``群活动``hd`)、`每日试用``正式版如何运行` 等。
- **活动海报推送**: 发送 `活动` 时自动请求服务端接口获取当前正在进行中的运营活动,并向群内发送对应的活动宣传海报及说明。
- **限制**: 仅在特定群 `621016172` 可用。 - **限制**: 仅在特定群 `621016172` 可用。
### 13. command_list - 指令列表 ### 13. command_list - 指令列表

View File

@@ -11,5 +11,6 @@
2. 正式版如何运行htr 2. 正式版如何运行htr
## 活动🔥🔥 ## 活动🔥🔥
1. 每日试用mrss - 永久活动单设备每日可试用1小时 1. 活动hd - 获取当前最新活动海报与详情
2. 每日试用mrss - 永久活动单设备每日可试用1小时

View File

@@ -16,7 +16,8 @@ class Config(BaseModel):
2. 正式版如何运行 2. 正式版如何运行
## 活动 ## 活动
1. 每日试用""" 1. 活动
2. 每日试用"""
DowndLoadStr:str = """\ DowndLoadStr:str = """\
公益版提供群文件下载一种方式; 公益版提供群文件下载一种方式;
@@ -43,3 +44,12 @@ class Config(BaseModel):
DailyTrialStr:str = """\ DailyTrialStr:str = """\
永久活动-每日试用单设备支持每日试用1小时的脚本时长 永久活动-每日试用单设备支持每日试用1小时的脚本时长
请在购买或支持蛋定助手前,一定要先试用,确保自己可以正常使用脚本!""" 请在购买或支持蛋定助手前,一定要先试用,确保自己可以正常使用脚本!"""
NoActivityStr: str = """\
📢 当前暂无进行中的限时运营活动哦~
🎁 常规福利:
1. 发送【每日试用】获取单设备每日1小时试用时长
2. 发送【签到】获取每日免费积分奖励!
📖 发送【帮助】查看更多功能菜单。"""
DDApi_Host: str = "https://api.danding.vip/DD/"

View File

@@ -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 nonebot.rule import Rule, fullmatch
from .config import Config from .config import Config
import os import os
import io
import aiohttp
from PIL import Image as PILImage
from nonebot_plugin_saa import Text, Image, MessageFactory 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 from nonebot.adapters.onebot.v11.event import GroupMessageEvent
import random import random
import asyncio import asyncio
@@ -95,3 +99,148 @@ daily_trial = on_command("每日试用", rule=_group_rule & fullmatch(('每日
async def _handle_daily_trial(): async def _handle_daily_trial():
await asyncio.sleep(random.uniform(2, 3)) await asyncio.sleep(random.uniform(2, 3))
await daily_trial.finish(plugin_config.DailyTrialStr) 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}")