perf👌: 广播与全局插件/被动管理

This commit is contained in:
HibiKier 2024-02-29 03:07:31 +08:00
parent 88bda9ce2c
commit a2d6c7f951
8 changed files with 433 additions and 138 deletions

View File

@ -1,6 +1,6 @@
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Arparma, Match
from nonebot_plugin_alconna import AlconnaQuery, Arparma, Match, Query
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
@ -21,14 +21,16 @@ __plugin_meta__ = PluginMetadata(
usage="""
普通管理员
格式:
开启/关闭[功能名称] : 开关功能
群被动状态 : 查看被动技能开关状态
醒来 : 结束休眠
休息吧 : 群组休眠, 不会再响应命令
开启/关闭[功能名称] : 开关功能
开启/关闭群被动[被动名称] : 群被动开关
群被动状态 : 查看被动技能开关状态
醒来 : 结束休眠
休息吧 : 群组休眠, 不会再响应命令
示例:
开启签到 : 开启签到
关闭签到 : 关闭签到
开启群被动早晚安 : 关闭被动任务早晚安
超级管理员额外命令
格式:
@ -61,26 +63,21 @@ __plugin_meta__ = PluginMetadata(
@_status_matcher.assign("$main")
async def _(bot: Bot, session: EventSession, arparma: Arparma):
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
task: Query[bool] = AlconnaQuery("task.value", False),
):
image = None
if session.id1 in bot.config.superusers:
if task.result:
image = await build_task(session.id3 or session.id2)
elif session.id1 in bot.config.superusers:
image = await build_plugin()
if image:
await Image(image.pic2bytes()).send(reply=True)
logger.info(
f"查看功能列表",
arparma.header_result,
session=session,
)
@_status_matcher.assign("task")
async def _(bot: Bot, session: EventSession, arparma: Arparma):
image = None
if image := await build_task(session.id3 or session.id2):
await Image(image.pic2bytes()).send(reply=True)
logger.info(
f"查看被动列表",
f"查看{'功能' if arparma.find('task') else '被动'}列表",
arparma.header_result,
session=session,
)
@ -93,21 +90,35 @@ async def _(
arparma: Arparma,
name: str,
group: Match[str],
task: Query[bool] = AlconnaQuery("task.value", False),
):
if gid := session.id3 or session.id2:
result = await PluginManage.block_group_plugin(name, gid)
if task.result:
result = await PluginManage.unblock_group_task(name, gid)
else:
result = await PluginManage.block_group_plugin(name, gid)
await Text(result).send(reply=True)
logger.info(f"开启功能 {name}", arparma.header_result, session=session)
elif session.id1 in bot.config.superusers:
group_id = group.result if group.available else None
result = await PluginManage.superuser_block(name, None, group_id)
await Text(result).send(reply=True)
logger.info(
f"超级用户开启功能 {name}",
arparma.header_result,
session=session,
target=group_id,
)
if task.result:
result = await PluginManage.superuser_task_handle(name, group_id, True)
await Text(result).send(reply=True)
logger.info(
f"超级用户开启被动技能 {name}",
arparma.header_result,
session=session,
target=group_id,
)
else:
result = await PluginManage.superuser_block(name, None, group_id)
await Text(result).send(reply=True)
logger.info(
f"超级用户开启功能 {name}",
arparma.header_result,
session=session,
target=group_id,
)
@_status_matcher.assign("close")
@ -118,27 +129,41 @@ async def _(
name: str,
block_type: Match[str],
group: Match[str],
task: Query[bool] = AlconnaQuery("task.value", False),
):
if gid := session.id3 or session.id2:
result = await PluginManage.unblock_group_plugin(name, gid)
if task.result:
result = await PluginManage.block_group_task(name, gid)
else:
result = await PluginManage.unblock_group_plugin(name, gid)
await Text(result).send(reply=True)
logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
elif session.id1 in bot.config.superusers:
group_id = group.result if group.available else None
_type = BlockType.ALL
if block_type.available:
if block_type.result in ["p", "private"]:
_type = BlockType.PRIVATE
elif block_type.result in ["g", "group"]:
_type = BlockType.GROUP
result = await PluginManage.superuser_block(name, _type, group_id)
await Text(result).send(reply=True)
logger.info(
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
arparma.header_result,
session=session,
target=group_id,
)
if task.result:
result = await PluginManage.superuser_task_handle(name, group_id, False)
await Text(result).send(reply=True)
logger.info(
f"超级用户关闭被动技能 {name}",
arparma.header_result,
session=session,
target=group_id,
)
else:
_type = BlockType.ALL
if block_type.available:
if block_type.result in ["p", "private"]:
_type = BlockType.PRIVATE
elif block_type.result in ["g", "group"]:
_type = BlockType.GROUP
result = await PluginManage.superuser_block(name, _type, group_id)
await Text(result).send(reply=True)
logger.info(
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
arparma.header_result,
session=session,
target=group_id,
)
@_group_status_matcher.handle()

View File

@ -104,7 +104,9 @@ async def build_task(group_id: str | None) -> BuildImage:
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
group = None
if group_id:
group = await GroupConsole.get_or_none(group_id=group_id)
group = await GroupConsole.get_or_none(
group_id=group_id, channel_id__isnull=True
)
if not group:
raise GroupInfoNotFound()
else:
@ -145,17 +147,23 @@ class PluginManage:
@classmethod
async def is_wake(cls, group_id: str) -> bool:
if c := await GroupConsole.get_or_none(group_id=group_id):
if c := await GroupConsole.get_or_none(
group_id=group_id, channel_id__isnull=True
):
return c.status
return False
@classmethod
async def sleep(cls, group_id: str):
await GroupConsole.filter(group_id=group_id).update(status=False)
await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
status=False
)
@classmethod
async def wake(cls, group_id: str):
await GroupConsole.filter(group_id=group_id).update(status=True)
await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
status=True
)
@classmethod
async def block(cls, module: str):
@ -178,6 +186,32 @@ class PluginManage:
"""
return await cls._change_group_plugin(plugin_name, group_id, True)
@classmethod
async def unblock_group_task(cls, task_name: str, group_id: str) -> str:
"""启用被动技能
参数:
task_name: 被动技能名称
group_id: 群组id
返回:
str: 返回信息
"""
return await cls._change_group_task(task_name, group_id, False)
@classmethod
async def block_group_task(cls, task_name: str, group_id: str) -> str:
"""禁用被动技能
参数:
task_name: 被动技能名称
group_id: 群组id
返回:
str: 返回信息
"""
return await cls._change_group_task(task_name, group_id, True)
@classmethod
async def unblock_group_plugin(cls, plugin_name: str, group_id: str) -> str:
"""启用群组插件
@ -191,6 +225,33 @@ class PluginManage:
"""
return await cls._change_group_plugin(plugin_name, group_id, False)
@classmethod
async def _change_group_task(
cls, task_name: str, group_id: str, status: bool
) -> str:
"""改变群组被动技能状态
参数:
task_name: 被动技能名称
group_id: 群组Id
status: 状态
返回:
str: 返回信息
"""
if task := await TaskInfo.get_or_none(name=task_name):
status_str = "关闭" if status else "开启"
group, _ = await GroupConsole.get_or_create(
group_id=group_id, channel_id__isnull=True
)
if status:
group.block_task += f"{task.module},"
else:
group.block_task = group.block_task.replace(f"{task.module},", "")
await group.save(update_fields=["block_task"])
return f"已成功{status_str} {task_name} 被动技能!"
return "没有找到这个被动技能喔..."
@classmethod
async def _change_group_plugin(
cls, plugin_name: str, group_id: str, status: bool
@ -212,7 +273,9 @@ class PluginManage:
plugin = await PluginInfo.get_or_none(name=plugin_name)
status_str = "开启" if status else "关闭"
if plugin:
group, _ = await GroupConsole.get_or_create(group_id=group_id)
group, _ = await GroupConsole.get_or_create(
group_id=group_id, channel_id__isnull=True
)
if status:
if plugin.module in group.block_plugin:
group.block_plugin = group.block_plugin.replace(
@ -228,11 +291,44 @@ class PluginManage:
return f"该功能已经{status_str}了喔,不要重复{status_str}..."
return "没有找到这个功能喔..."
@classmethod
async def superuser_task_handle(
cls, task_name: str, group_id: str | None, status: bool
) -> str:
"""超级用户禁用被动技能
参数:
task_name: 被动技能名称
group_id: 群组id
status: 状态
返回:
str: 返回信息
"""
if task := await TaskInfo.get_or_none(name=task_name):
status_str = "开启" if status else "关闭"
if group_id:
group, _ = await GroupConsole.get_or_create(
group_id=group_id, channel_id__isnull=True
)
if status:
group.block_task = group.block_task.replace(
f"super:{task.module},", ""
)
else:
group.block_task += f"super:{task.module},"
await group.save(update_fields=["block_task"])
else:
task.status = status
await task.save(update_fields=["status"])
return f"已成功将被动技能 {task_name} 全局{status_str}!"
return "没有找到这个功能喔..."
@classmethod
async def superuser_block(
cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
) -> str:
"""超级用户禁用
"""超级用户禁用插件
参数:
plugin_name: 插件名称
@ -248,7 +344,9 @@ class PluginManage:
plugin = await PluginInfo.get_or_none(name=plugin_name)
if plugin:
if group_id:
if group := await GroupConsole.get_or_none(group_id=group_id):
if group := await GroupConsole.get_or_none(
group_id=group_id, channel_id__isnull=True
):
if f"super:{plugin.module}," not in group.block_plugin:
group.block_plugin += f"super:{plugin.module},"
await group.save(update_fields=["block_plugin"])

View File

@ -63,6 +63,15 @@ _status_matcher.shortcut(
prefix=True,
)
_status_matcher.shortcut(
r"开启群被动(?P<name>.+)",
command="switch",
arguments=["open", "{name}", "--task"],
prefix=True,
)
_status_matcher.shortcut(
r"开启(?P<name>.+)",
command="switch",
@ -70,6 +79,13 @@ _status_matcher.shortcut(
prefix=True,
)
_status_matcher.shortcut(
r"关闭群被动(?P<name>.+)",
command="switch",
arguments=["close", "{name}", "--task"],
prefix=True,
)
_status_matcher.shortcut(
r"关闭(?P<name>.+)",
command="switch",
@ -77,6 +93,7 @@ _status_matcher.shortcut(
prefix=True,
)
_group_status_matcher.shortcut(
r"醒来",
command="group-status",

View File

@ -1,28 +1,75 @@
import nonebot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_saa import Image
# TODO: 消息发送
from zhenxun.configs.config import NICKNAME
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, Task
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.platform import broadcast_group
# # 早上好
# @scheduler.scheduled_job(
# "cron",
# hour=6,
# minute=1,
# )
# async def _():
# img = image(IMAGE_PATH / "zhenxun" / "zao.jpg")
# await broadcast_group("[[_task|zwa]]早上好" + img, log_cmd="被动早晚安")
# logger.info("每日早安发送...")
__plugin_meta__ = PluginMetadata(
name="早晚安被动技能",
description="早晚安被动技能",
usage="",
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
tasks=[
Task(module="group_welcome", name="进群欢迎"),
Task(module="refund_group_remind", name="退群提醒"),
],
).dict(),
)
driver = nonebot.get_driver()
@driver.on_startup
async def _():
if not await TaskInfo.exists(module="morning_goodnight"):
await TaskInfo.create(
module="morning_goodnight",
name="早晚安",
status=True,
)
async def check(group_id: str, channel_id: str | None) -> bool:
task = await TaskInfo.get_or_none(module="morning_goodnight")
if not task or not task.status:
return False
return await GroupConsole.is_block_task(group_id, "morning_goodnight")
# 早上好
@scheduler.scheduled_job(
"cron",
hour=6,
minute=1,
)
async def _():
img = Image(IMAGE_PATH / "zhenxun" / "zao.jpg")
await broadcast_group("早上好" + img, log_cmd="被动早晚安", check_func=check)
logger.info("每日早安发送...")
# # 睡觉了
# @scheduler.scheduled_job(
# "cron",
# hour=23,
# minute=59,
# )
# async def _():
# img = image(IMAGE_PATH / "zhenxun" / "sleep.jpg")
# await broadcast_group(
# f"[[_task|zwa]]{NICKNAME}要睡觉了,你们也要早点睡呀" + img, log_cmd="被动早晚安"
# )
# logger.info("每日晚安发送...")
@scheduler.scheduled_job(
"cron",
hour=23,
minute=59,
)
async def _():
img = Image(IMAGE_PATH / "zhenxun" / "sleep.jpg")
await broadcast_group(
f"{NICKNAME}要睡觉了,你们也要早点睡呀" + img,
log_cmd="被动早晚安",
check_func=check,
)
logger.info("每日晚安发送...")

View File

@ -5,6 +5,7 @@ from nonebot.adapters import Bot
from nonebot.params import Command
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Text as alcText
from nonebot_plugin_alconna import UniMsg
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
@ -51,8 +52,11 @@ async def _(
message: UniMsg,
command: Annotated[tuple[str, ...], Command()],
):
message[0].text = message[0].text.replace(command[0], "").strip()
# await Text("正在发送..请等一下哦!").send()
for msg in message:
if isinstance(msg, alcText) and msg.text.strip().startswith(command[0]):
msg.text = msg.text.replace(command[0], "", 1).strip()
break
await Text("正在发送..请等一下哦!").send()
count, error_count = await BroadcastManage.send(bot, message, session)
result = f"成功广播 {count} 个群组"
if error_count:

View File

@ -9,23 +9,13 @@ from nonebot_plugin_alconna import UniMsg
from nonebot_plugin_saa import (
Image,
MessageFactory,
TargetDoDoChannel,
TargetQQGroup,
Text,
)
from nonebot_plugin_session import EventSession
from pydantic import BaseModel
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
class GroupChannel(BaseModel):
group_id: str
"""群组id"""
channel_id: str | None = None
"""频道id"""
from zhenxun.utils.platform import PlatformManage
class BroadcastManage:
@ -50,24 +40,27 @@ class BroadcastManage:
message_list.append(Image(msg.url))
elif isinstance(msg, alc.Text):
message_list.append(Text(msg.text))
if group_list := await cls.__get_group_list(bot):
group_list, _ = await PlatformManage.get_group_list(bot)
if group_list:
error_count = 0
for group in group_list:
try:
if not await GroupConsole.is_block_task(
group.group_id, "broadcast", group.channel_id
):
if isinstance(bot, (v11Bot, v12Bot)):
target = TargetQQGroup(group_id=int(group.group_id))
elif isinstance(bot, DodoBot):
target = TargetDoDoChannel(channel_id=group.channel_id) # type: ignore
await MessageFactory(message_list).send_to(target, bot)
logger.debug(
"发送成功",
"广播",
session=session,
target=f"{group.group_id}:{group.channel_id}",
target = PlatformManage.get_target(
bot, group.group_id, group.channel_id
)
if target:
await MessageFactory(message_list).send_to(target, bot)
logger.debug(
"发送成功",
"广播",
session=session,
target=f"{group.group_id}:{group.channel_id}",
)
else:
logger.warning("target为空", "广播", session=session)
except Exception as e:
error_count += 1
logger.error(
@ -79,40 +72,3 @@ class BroadcastManage:
)
return len(group_list) - error_count, error_count
return 0, 0
@classmethod
async def __get_group_list(cls, bot: Bot) -> list[GroupChannel]:
"""获取群组id列表
参数:
bot: Bot
返回:
list[str]: 群组id列表
"""
if isinstance(bot, (v11Bot, v12Bot)):
group_list = await bot.get_group_list()
return [GroupChannel(group_id=str(g["group_id"])) for g in group_list]
if isinstance(bot, DodoBot):
island_list = await bot.get_island_list()
source_id_list = [
g.island_source_id for g in island_list if g.island_source_id
]
channel_id_list = []
for id in source_id_list:
channel_list = await bot.get_channel_list(island_source_id=id)
channel_id_list += [
GroupChannel(group_id=id, channel_id=c.channel_id)
for c in channel_list
]
return channel_id_list
if isinstance(bot, KaiheilaBot):
# TODO: kaiheila获取群组列表
pass
# group_list = await bot.guild_list()
# if group_list.guilds:
# return [g.open_id for g in group_list.guilds if g.open_id]
if isinstance(bot, DiscordBot):
# TODO: discord获取群组列表
pass
return []

View File

@ -108,6 +108,12 @@ class GroupConsole(Model):
返回:
bool: 是否禁用被动
"""
if not channel_id:
return await cls.exists(
group_id=group_id,
channel_id__isnull=True,
block_task__contains=f"{task},",
)
return await cls.exists(
group_id=group_id, channel_id=channel_id, block_task__contains=f"{task},"
)

View File

@ -1,9 +1,20 @@
from typing import Awaitable, Callable, Literal, Set
import nonebot
from nonebot.adapters import Bot
from nonebot.adapters.discord import Bot as DiscordBot
from nonebot.adapters.dodo import Bot as DodoBot
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot.utils import is_coroutine_callable
from nonebot_plugin_saa import (
MessageFactory,
TargetDoDoChannel,
TargetKaiheilaChannel,
TargetQQGroup,
Text,
)
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
@ -23,7 +34,7 @@ class PlatformManage:
int: 更新个数
"""
create_list = []
group_list, platform = await cls.__get_group_list(bot)
group_list, platform = await cls.get_group_list(bot)
if group_list:
exists_group_list = await GroupConsole.all().values_list(
"group_id", "channel_id"
@ -42,7 +53,25 @@ class PlatformManage:
return len(create_list)
@classmethod
async def __get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]:
def get_platform(cls, bot: Bot) -> str | None:
"""获取平台
参数:
bot: Bot
返回:
str | None: 平台
"""
if isinstance(bot, (v11Bot, v12Bot)):
return "qq"
if isinstance(bot, DodoBot):
return "dodo"
if isinstance(bot, KaiheilaBot):
return "kaiheila"
return None
@classmethod
async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]:
"""获取群组列表
参数:
@ -121,7 +150,7 @@ class PlatformManage:
int: 更新个数
"""
create_list = []
friend_list, platform = await cls.__get_friend_list(bot)
friend_list, platform = await cls.get_friend_list(bot)
if friend_list:
user_id_list = await FriendUser.all().values_list("user_id", flat=True)
for friend in friend_list:
@ -133,7 +162,7 @@ class PlatformManage:
return len(create_list)
@classmethod
async def __get_friend_list(cls, bot: Bot) -> tuple[list[FriendUser], str]:
async def get_friend_list(cls, bot: Bot) -> tuple[list[FriendUser], str]:
"""获取好友列表
参数:
@ -167,3 +196,116 @@ class PlatformManage:
# TODO: discord好友列表
pass
return [], ""
@classmethod
def get_target(cls, bot: Bot, group_id: str | None, channel_id: str | None):
"""获取发生Target
参数:
bot: Bot
group_id: 群组id
channel_id: 频道id
返回:
target: 对应平台Target
"""
target = None
if isinstance(bot, (v11Bot, v12Bot)):
if group_id:
target = TargetQQGroup(group_id=int(group_id))
if channel_id:
if isinstance(bot, DodoBot):
target = TargetDoDoChannel(channel_id=channel_id)
elif isinstance(bot, KaiheilaBot):
target = TargetKaiheilaChannel(channel_id=channel_id)
return target
async def broadcast_group(
message: str | MessageFactory,
bot: Bot | list[Bot] | None = None,
bot_id: str | Set[str] | None = None,
ignore_group: Set[int] | None = None,
check_func: Callable[[str, str | None], Awaitable] | None = None,
log_cmd: str | None = None,
platform: Literal["qq", "dodo", "kaiheila"] | None = None,
):
"""获取所有Bot或指定Bot对象广播群聊
Args:
message: 广播消息内容
bot: 指定bot对象.
bot_id: 指定bot id.
ignore_group: 忽略群聊列表.
check_func: 发送前对群聊检测方法判断是否发送.
log_cmd: 日志标记.
platform: 指定平台
"""
if platform and platform not in ["qq", "dodo", "kaiheila"]:
raise ValueError("指定平台不支持")
if not message:
raise ValueError("群聊广播消息不能为空")
bot_dict = nonebot.get_bots()
bot_list: list[Bot] = []
if bot:
if isinstance(bot, list):
bot_list = bot
else:
bot_list.append(bot)
elif bot_id:
_bot_id_list = bot_id
if isinstance(bot_id, str):
_bot_id_list = [bot_id]
for id_ in _bot_id_list:
if bot_id in bot_dict:
bot_list.append(bot_dict[bot_id])
else:
logger.warning(f"Bot:{id_} 对象未连接或不存在")
else:
bot_list = list(bot_dict.values())
_used_group = []
for _bot in bot_list:
try:
if platform and platform != PlatformManage.get_platform(_bot):
continue
group_list, _ = await PlatformManage.get_group_list(_bot)
if group_list:
for group in group_list:
key = f"{group.group_id}:{group.channel_id}"
try:
if (
ignore_group
and (
group.group_id in ignore_group
or group.channel_id in ignore_group
)
) or key in _used_group:
continue
is_continue = False
if check_func:
if is_coroutine_callable(check_func):
is_continue = not await check_func(
group.group_id, group.channel_id
)
else:
is_continue = not check_func(
group.group_id, group.channel_id
)
if is_continue:
continue
target = PlatformManage.get_target(
_bot, group.group_id, group.channel_id
)
if target:
_used_group.append(key)
message_list = message
if isinstance(message, str):
message_list = MessageFactory([Text(message)])
await MessageFactory(message_list).send_to(target, _bot)
logger.debug("发送成功", log_cmd, target=key)
else:
logger.warning("target为空", log_cmd, target=key)
except Exception as e:
logger.error("发送失败", log_cmd, target=key, e=e)
except Exception as e:
logger.error(f"Bot: {_bot.self_id} 获取群聊列表失败", command=log_cmd, e=e)