feat✨: 更新内置插件

This commit is contained in:
HibiKier
2024-02-25 03:18:34 +08:00
parent 131200a28e
commit eb0572ea77
83 changed files with 7588 additions and 450 deletions
+9
View File
@@ -1,3 +1,5 @@
import os
from nonebot import require
require("nonebot_plugin_apscheduler")
@@ -8,3 +10,10 @@ require("nonebot_plugin_saa")
from nonebot_plugin_saa import enable_auto_select_bot
enable_auto_select_bot()
from pathlib import Path
import nonebot
path = Path(__file__).parent / "platform"
for d in os.listdir(path):
nonebot.load_plugins(str((path / d).resolve()))
+165
View File
@@ -0,0 +1,165 @@
import nonebot
from arclet.alconna import Args, Option
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_alconna.matcher import AlconnaMatcher
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.exception import EmptyError
from zhenxun.utils.image_utils import (
BuildImage,
build_sort_image,
group_image,
text2image,
)
from zhenxun.utils.rules import admin_check, ensure_group
base_config = Config.get("admin_bot_manage")
__plugin_meta__ = PluginMetadata(
name="群组管理员帮助",
description="管理员帮助列表",
usage="""
管理员帮助
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.ADMIN,
admin_level=1,
).dict(),
)
_matcher = on_alconna(
Alconna("管理员帮助"),
rule=admin_check(1) & ensure_group,
priority=5,
block=True,
)
ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png"
if ADMIN_HELP_IMAGE.exists():
ADMIN_HELP_IMAGE.unlink()
async def build_help() -> BuildImage:
"""构造管理员帮助图片
异常:
EmptyError: 管理员帮助为空
返回:
BuildImage: 管理员帮助图片
"""
plugin_list = await PluginInfo.filter(
plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN]
).all()
data_list = []
for plugin in plugin_list:
if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path):
if _plugin.metadata:
data_list.append({"plugin": plugin, "metadata": _plugin.metadata})
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
image_list = []
for data in data_list:
plugin = data["plugin"]
metadata = data["metadata"]
try:
usage = None
description = None
if metadata.usage:
usage = await text2image(
metadata.usage,
padding=5,
color=(255, 255, 255),
font_color=(0, 0, 0),
)
if metadata.description:
description = await text2image(
metadata.description,
padding=5,
color=(255, 255, 255),
font_color=(0, 0, 0),
)
width = 0
height = 100
if usage:
width = usage.width
height += usage.height
if description and description.width > width:
width = description.width
height += description.height
font_width, font_height = BuildImage.get_text_size(
plugin.name + f"[{plugin.level}]", font
)
if font_width > width:
width = font_width
A = BuildImage(width + 30, height + 120, "#EAEDF2")
await A.text((15, 10), plugin.name + f"[{plugin.level}]")
await A.text((15, 70), "简介:")
if not description:
description = BuildImage(A.width - 30, 30, (255, 255, 255))
await description.circle_corner(10)
await A.paste(description, (15, 100))
if not usage:
usage = BuildImage(A.width - 30, 30, (255, 255, 255))
await usage.circle_corner(10)
await A.text((15, description.height + 115), "用法:")
await A.paste(usage, (15, description.height + 145))
await A.circle_corner(10)
image_list.append(A)
except Exception as e:
logger.warning(
f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...",
"管理员帮助",
e=e,
)
if task_list := await TaskInfo.all():
task_str = "\n".join([task.name for task in task_list])
task_str = "通过 开启/关闭 来控制群被动\n----------\n" + task_str
task_image = await text2image(task_str, padding=5, color=(255, 255, 255))
await task_image.circle_corner(10)
A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2")
await A.text((25, 10), "被动技能")
await A.paste(task_image, (25, 50))
await A.circle_corner(10)
image_list.append(A)
if not image_list:
raise EmptyError()
image_group, _ = group_image(image_list)
A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160)
text = await BuildImage.build_text_image(
"群管理员帮助",
size=40,
)
tip = await BuildImage.build_text_image(
"注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red"
)
await A.paste(text, (50, 30))
await A.paste(tip, (50, 90))
await A.save(ADMIN_HELP_IMAGE)
return BuildImage(1, 1)
@_matcher.handle()
async def _(
session: EventSession,
matcher: AlconnaMatcher,
arparma: Arparma,
):
if not ADMIN_HELP_IMAGE.exists():
try:
await build_help()
except EmptyError:
await Text("管理员帮助为空").finish(reply=True)
await Image(ADMIN_HELP_IMAGE).send()
logger.info("查看管理员帮助", arparma.header_result, session=session)
+11 -11
View File
@@ -9,11 +9,6 @@ from zhenxun.models.level_user import LevelUser
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
__zx_plugin_name__ = "群管理员变动监测 [Hidden]"
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_meta__ = PluginMetadata(
name="群管理员变动监测",
description="检测群管理员变动, 添加与删除管理员默认权限, 当配置项 ADMIN_DEFAULT_AUTH 为空时, 不会添加管理员权限",
@@ -40,20 +35,25 @@ async def _(event: GroupAdminNoticeEvent):
admin_default_auth = base_config.get("ADMIN_DEFAULT_AUTH")
if admin_default_auth is not None:
await LevelUser.set_level(
event.user_id,
event.group_id,
str(event.user_id),
str(event.group_id),
admin_default_auth,
)
logger.info(
f"成为管理员,添加权限: {admin_default_auth}",
"群管理员变动监测",
event.user_id,
event.group_id,
session=event.user_id,
group_id=event.group_id,
)
else:
logger.warning(
f"配置项 MODULE: [<u><y>admin_bot_manage</y></u>] | KEY: [<u><y>ADMIN_DEFAULT_AUTH</y></u>] 为空"
)
elif event.sub_type == "unset":
await LevelUser.delete_level(event.user_id, event.group_id)
logger.info("撤销群管理员, 取消权限等级", "群管理员变动监测", event.user_id, event.group_id)
await LevelUser.delete_level(str(event.user_id), str(event.group_id))
logger.info(
"撤销群管理员, 取消权限等级",
"群管理员变动监测",
session=event.user_id,
group_id=event.group_id,
)
@@ -0,0 +1,196 @@
from arclet.alconna import Args
from nonebot.adapters import Bot
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
Arparma,
At,
Match,
Option,
Subcommand,
on_alconna,
store_true,
)
from nonebot_plugin_saa import Image, Mention, MessageFactory, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.rules import admin_check
from ._data_source import BanManage
base_config = Config.get("ban")
__plugin_meta__ = PluginMetadata(
name="封禁用户/群组",
description="你被逮捕了!丢进小黑屋!封禁用户以及群组,屏蔽消息",
usage="""
.ban [at] ?[小时] ?[分钟]
.unban
示例:.ban @user
示例:.ban @user 6
示例:.ban @user 3 10
示例:.unban @user
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPER_AND_ADMIN,
admin_level=base_config.get("BAN_LEVEL", 5),
configs=[
RegisterConfig(
key="BAN_LEVEL",
value=5,
help="ban/unban所需要的管理员权限等级",
default_value=5,
type=int,
)
],
).dict(),
)
_matcher = on_alconna(
Alconna(
"ban-console",
Subcommand(
"ban",
Args["user?", [str, At]]["duration?", int],
Option("-g|--group", Args["group_id", str]),
),
Subcommand(
"unban",
Args["user?", [str, At]],
Option("-g|--group", Args["group_id", str]),
),
),
rule=admin_check("ban", "BAN_LEVEL"),
priority=5,
block=True,
)
_status_matcher = on_alconna(
Alconna(
"ban-status",
Option("-u|--user", Args["user_id", str]),
Option("-g|--group", Args["group_id", str]),
),
permission=SUPERUSER,
priority=1,
block=True,
)
# TODO: shortcut
@_status_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
user_id: Match[str],
group_id: Match[str],
):
_user_id = user_id.result if user_id.available else None
_group_id = group_id.result if group_id.available else None
if image := await BanManage.build_ban_image(_user_id, _group_id):
await Image(image.pic2bs4()).finish(reply=True)
else:
await Text("数据为空捏...").finish(reply=True)
@_matcher.assign("ban")
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
user: Match[str | At],
duration: Match[int],
group_id: Match[str],
):
user_id = None
if user.available:
if isinstance(user.result, At):
user_id = user.result.target
else:
user_id = user.result
_duration = duration.result * 60 if duration.available else -1
if gid := session.id3 or session.id2:
if group_id.available:
gid = group_id.result
await BanManage.ban(
user_id, gid, _duration, session, session.id1 in bot.config.superusers
)
logger.info(
f"管理员Ban",
arparma.header_result,
session=session,
target=f"{gid}:{user_id}",
)
await MessageFactory(
[
Text("对 "),
Mention(user_id), # type: ignore
Text(f" 狠狠惩戒了一番,一脚踢进了小黑屋!"),
]
).finish(reply=True)
elif session.id1 in bot.config.superusers:
_group_id = group_id.result if group_id.available else None
await BanManage.ban(user_id, _group_id, _duration, session, True)
logger.info(
f"超级用户Ban",
arparma.header_result,
session=session,
target=f"{_group_id}:{user_id}",
)
at_msg = user_id if user_id else f"群组:{_group_id}"
await Text(f"对 {at_msg} 狠狠惩戒了一番,一脚踢进了小黑屋!").finish(reply=True)
@_matcher.assign("unban")
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
user: Match[str | At],
group_id: Match[str],
):
user_id = None
if user.available:
if isinstance(user.result, At):
user_id = user.result.target
else:
user_id = user.result
if gid := session.id3 or session.id2:
if group_id.available:
gid = group_id.result
await BanManage.unban(
user_id, gid, session, session.id1 in bot.config.superusers
)
logger.info(
f"管理员UnBan",
arparma.header_result,
session=session,
target=f"{gid}:{user_id}",
)
await MessageFactory(
[
Text("将 "),
Mention(user_id), # type: ignore
Text(f" 从黑屋中拉了出来并急救了一下!"),
]
).finish(reply=True)
elif session.id1 in bot.config.superusers:
_group_id = group_id.result if group_id.available else None
await BanManage.unban(user_id, _group_id, session, True)
logger.info(
f"超级用户UnBan",
arparma.header_result,
session=session,
target=f"{_group_id}:{user_id}",
)
at_msg = user_id if user_id else f"群组:{_group_id}"
await Text(f"对 {at_msg} 从黑屋中拉了出来并急救了一下!").finish(reply=True)
@@ -0,0 +1,120 @@
import time
from nonebot_plugin_session import EventSession
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.level_user import LevelUser
from zhenxun.utils.image_utils import ImageTemplate
class BanManage:
@classmethod
async def build_ban_image(cls, user_id: str | None, group_id: str | None):
data_list = None
if not user_id and not group_id:
data_list = await BanConsole.all()
elif user_id:
if group_id:
data_list = await BanConsole.filter(
user_id=user_id, group_id=group_id
).all()
else:
data_list = await BanConsole.filter(
user_id=user_id, group_id__isnull=True
).all()
else:
if group_id:
data_list = await BanConsole.filter(
user_id__isnull=True, group_id=group_id
).all()
if not data_list:
return None
column_name = [
"ID",
"用户ID",
"群组ID",
"BAN LEVEL",
"剩余时长(分钟)",
"操作员ID",
]
row_data = []
for data in data_list:
duration = int((data.ban_time + data.duration - time.time()) / 60)
if duration < 0:
duration = 0
row_data.append(
[
data.id,
data.user_id,
data.group_id,
data.ban_level,
duration,
data.operator,
]
)
return await ImageTemplate.table_page(
"Ban / UnBan 列表", "在黑屋中狠狠调教!", column_name, row_data
)
@classmethod
async def is_ban(cls, user_id: str, group_id: str | None):
"""判断用户是否被ban
参数:
user_id: 用户id
返回:
bool: 是否被ban
"""
return await BanConsole.is_ban(user_id, group_id)
@classmethod
async def unban(
cls,
user_id: str | None,
group_id: str | None,
session: EventSession,
is_superuser: bool = False,
) -> bool:
"""ban掉目标用户
参数:
user_id: 用户id
group_id: 群组id
session: Session
is_superuser: 是否为超级用户操作
返回:
bool: 是否unban成功
"""
user_level = 9999
if not is_superuser and user_id and session.id1:
user_level = await LevelUser.get_user_level(session.id1, group_id)
if await BanConsole.check_ban_level(user_id, group_id, user_level):
await BanConsole.unban(user_id, group_id)
return True
return False
@classmethod
async def ban(
cls,
user_id: str | None,
group_id: str | None,
duration: int,
session: EventSession,
is_superuser: bool,
):
"""ban掉目标用户
参数:
user_id: 用户id
group_id: 群组id
duration: 时长,秒
session: Session
is_superuser: 是否为超级用户操作
"""
level = 9999
if not is_superuser and user_id and session.id1:
level = await LevelUser.get_user_level(session.id1, group_id)
await BanConsole.ban(user_id, group_id, level, duration, session.id1)
@@ -0,0 +1,63 @@
from nonebot import on_notice
from nonebot.adapters import Bot
from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.rules import admin_check, ensure_group
from ._data_source import MemberUpdateManage
__plugin_meta__ = PluginMetadata(
name="更新群组成员列表",
description="更新群组成员列表",
usage="""
更新群组成员的基本信息
指令:
更新群组成员信息
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPER_AND_ADMIN,
admin_level=1,
).dict(),
)
_matcher = on_alconna(
Alconna("更新群组成员信息"),
rule=admin_check(1) & ensure_group,
priority=5,
block=True,
)
@_matcher.handle()
async def _(bot: Bot, session: EventSession, arparma: Arparma):
if gid := session.id3 or session.id2:
logger.info("更新群组成员信息", arparma.header_result, session=session)
await MemberUpdateManage.update(bot, gid)
await Text("已经成功更新了群组成员信息!").finish(reply=True)
await Text("群组id为空...").send()
_notice = on_notice(priority=1, block=False)
@_notice.handle()
async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
# TODO: 其他适配器的加群自动更新群组成员信息
if str(event.user_id) == bot.self_id:
await MemberUpdateManage.update(bot, str(event.group_id))
logger.info(
"{NICKNAME}加入群聊更新群组信息",
"更新群组成员列表",
session=event.user_id,
group_id=event.group_id,
)
@@ -0,0 +1,177 @@
import time
from datetime import datetime, timedelta, timezone
from nonebot.adapters import Bot
from nonebot.adapters.discord import Bot as DiscordBot
from nonebot.adapters.dodo import Bot as DodoBot
from nonebot.adapters.dodo.models import MemberInfo
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 zhenxun.configs.config import Config
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.level_user import LevelUser
from zhenxun.services.log import logger
class MemberUpdateManage:
@classmethod
async def update(cls, bot: Bot, group_id: str):
if isinstance(bot, v11Bot):
await cls.v11(bot, group_id)
elif isinstance(bot, v12Bot):
await cls.v12(bot, group_id)
elif isinstance(bot, KaiheilaBot):
await cls.kaiheila(bot, group_id)
elif isinstance(bot, DodoBot):
await cls.dodo(bot, group_id)
elif isinstance(bot, DiscordBot):
await cls.discord(bot, group_id)
@classmethod
async def discord(cls, bot: DiscordBot, group_id: str):
# TODO: discord更新群组成员信息
pass
@classmethod
async def dodo(cls, bot: DodoBot, group_id: str):
page_size = 100
result_size = 100
max_id = 0
exist_member_list = []
group_member_list: list[MemberInfo] = []
while result_size == page_size:
group_member_data = await bot.get_member_list(
island_source_id=group_id, page_size=page_size
)
result_size = len(group_member_data.list)
group_member_list += group_member_data.list
max_id = group_member_data.max_id
if group_member_list:
for user in group_member_list:
exist_member_list.append(user.dodo_source_id)
await GroupInfoUser.update_or_create(
user_id=user.dodo_source_id,
group_id=group_id,
defaults={
"user_name": user.nick_name or user.personal_nick_name,
"user_join_time": user.join_time,
"platform": "dodo",
},
)
if delete_member_list := list(
set(exist_member_list).difference(
set(await GroupInfoUser.get_group_member_id_list(group_id))
)
):
await GroupInfoUser.filter(
user_id__in=delete_member_list, group_id=group_id
).delete()
logger.info(
f"删除已退群用户",
"更新群组成员信息",
group_id=group_id,
platform="dodo",
)
@classmethod
async def kaiheila(cls, bot: KaiheilaBot, group_id: str):
# TODO: kaiheila 更新群组成员信息
pass
@classmethod
async def v11(cls, bot: v11Bot, group_id: str):
exist_member_list = []
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
group_member_list = await bot.get_group_member_list(group_id=int(group_id))
for user_info in group_member_list:
user_id = user_info["user_id"]
nickname = user_info["card"] or user_info["nickname"]
role = user_info["role"]
if default_auth:
if role in ["owner", "admin"] and not LevelUser.is_group_flag(
str(user_id), group_id
):
await LevelUser.set_level(user_id, group_id, default_auth)
if str(user_id) in bot.config.superusers:
await LevelUser.set_level(str(user_id), group_id, 9)
join_time = datetime.strptime(
time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])
),
"%Y-%m-%d %H:%M:%S",
)
await GroupInfoUser.update_or_create(
user_id=str(user_id),
group_id=group_id,
defaults={
"user_name": nickname,
"user_join_time": join_time.replace(
tzinfo=timezone(timedelta(hours=8))
),
"platform": "qq",
},
)
exist_member_list.append(str(user_id))
logger.debug(
"更新成功", "更新群组成员信息", session=user_id, group_id=group_id
)
if delete_member_list := list(
set(exist_member_list).difference(
set(await GroupInfoUser.get_group_member_id_list(group_id))
)
):
await GroupInfoUser.filter(
user_id__in=delete_member_list, group_id=group_id
).delete()
logger.info(
f"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq"
)
@classmethod
async def v12(cls, bot: v12Bot, group_id: str):
# TODO: v12更新群组成员信息
pass
# exist_member_list = []
# default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
# group_member_list: list[GetGroupMemberInfoResp] = await bot.get_group_member_list(
# group_id=group_id
# )
# for user_info in group_member_list:
# user_id = user_info.user_id
# nickname = user_info.user_displayname or user_info.user_name
# role = user_info["role"]
# if default_auth:
# if role in ["owner", "admin"] and not LevelUser.is_group_flag(
# str(user_id), group_id
# ):
# await LevelUser.set_level(user_id, group_id, default_auth)
# if str(user_id) in bot.config.superusers:
# await LevelUser.set_level(str(user_id), group_id, 9)
# join_time = datetime.strptime(
# time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])),
# "%Y-%m-%d %H:%M:%S",
# )
# await GroupInfoUser.update_or_create(
# user_id=str(user_id),
# group_id=group_id,
# defaults={
# "user_name": nickname,
# "user_join_time": join_time.replace(
# tzinfo=timezone(timedelta(hours=8))
# ),
# },
# )
# exist_member_list.append(str(user_id))
# logger.debug("更新成功", "更新群组成员信息", session=user_id, group_id=group_id)
# if delete_member_list := list(
# set(exist_member_list).difference(
# set(await GroupInfoUser.get_group_member_id_list(group_id))
# )
# ):
# await GroupInfoUser.filter(
# user_id__in=delete_member_list, group_id=group_id
# ).delete()
# logger.info(f"删除已退群用户", "更新群组成员信息", group_id=group_id)
@@ -0,0 +1,162 @@
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
Args,
Arparma,
Match,
Option,
Subcommand,
on_alconna,
store_true,
)
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from requests import session
from zhenxun.configs.config import Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.rules import admin_check, ensure_group
from ._data_source import PluginManage, build_plugin, build_task
base_config = Config.get("admin_bot_manage")
__plugin_meta__ = PluginMetadata(
name="功能开关",
description="对群组内的功能限制,超级用户可以对群组以及全局的功能被动开关限制",
usage="""
开启/关闭[功能]
群被动状态
开启全部被动
关闭全部被动
醒来/休息吧
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPER_AND_ADMIN,
admin_level=base_config.get("CHANGE_GROUP_SWITCH_LEVEL", 2),
configs=[
RegisterConfig(
key="CHANGE_GROUP_SWITCH_LEVEL",
value=2,
help="开关群功能权限",
default_value=2,
type=int,
)
],
).dict(),
)
_status_matcher = on_alconna(
Alconna(
"switch",
Option("-t|--task", action=store_true, help_text="被动技能"),
Subcommand(
"open",
Args["name", str],
Option(
"-g|--group",
Args["group_id", str],
),
),
Subcommand(
"close",
Args["name", str],
Option(
"-t|--type",
Args["block_type", ["all", "a", "private", "p", "group", "g"]],
),
Option(
"-g|--group",
Args["group_id", str],
),
),
),
rule=admin_check("admin_bot_manage", "CHANGE_GROUP_SWITCH_LEVEL"),
priority=5,
block=True,
)
# TODO: shortcut
_group_status_matcher = on_alconna(
Alconna("group-status", Args["status", ["sleep", "wake"]]),
rule=admin_check("admin_bot_manage", "CHANGE_GROUP_SWITCH_LEVEL") & ensure_group,
priority=5,
block=True,
)
@_status_matcher.assign("$main")
async def _(bot: Bot, session: EventSession, arparma: Arparma):
image = None
if arparma.find("task"):
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.pic2bs4()).send(reply=True)
logger.info(
f"查看{'被动' if arparma.find('task') else '功能'}列表",
arparma.header_result,
session=session,
)
@_status_matcher.assign("open")
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
name: str,
group: Match[str],
):
if gid := session.id3 or session.id2:
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:
result = await PluginManage.superuser_block(name, None, group.result)
await Text(result).send(reply=True)
logger.info(
f"超级用户开启功能 {name}",
arparma.header_result,
session=session,
target=group.result,
)
@_status_matcher.assign("close")
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
name: str,
block_type: Match[str],
group: Match[str],
):
if gid := session.id3 or session.id2:
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:
_type = BlockType.ALL
if block_type.available:
if block_type.result in ["p", "private"]:
_type = BlockType.FRIEND
elif block_type.result in ["g", "group"]:
_type = BlockType.GROUP
result = await PluginManage.superuser_block(name, _type, group.result)
await Text(result).send(reply=True)
logger.info(
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
arparma.header_result,
session=session,
target=group.result,
)
@@ -0,0 +1,244 @@
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.exception import GroupInfoNotFound
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle
def plugin_row_style(column: str, text: str) -> RowStyle:
"""被动技能文本风格
参数:
column: 表头
text: 文本内容
返回:
RowStyle: RowStyle
"""
style = RowStyle()
if column == "全局状态":
if text == "开启":
style.font_color = "#67C23A"
else:
style.font_color = "#F56C6C"
if column == "加载状态":
if text == "SUCCESS":
style.font_color = "#67C23A"
else:
style.font_color = "#F56C6C"
return style
async def build_plugin() -> BuildImage:
column_name = [
"ID",
"模块",
"名称",
"全局状态",
"禁用类型",
"加载状态",
"菜单分类",
"作者",
"版本",
"金币花费",
]
plugin_list = await PluginInfo.filter(plugin_type__not=PluginType.HIDDEN).all()
column_data = []
for plugin in plugin_list:
column_data.append(
[
plugin.id,
plugin.module,
plugin.name,
"开启" if plugin.status else "关闭",
plugin.block_type,
"SUCCESS" if plugin.load_status else "ERROR",
plugin.menu_type,
plugin.author,
plugin.version,
plugin.cost_gold,
]
)
return await ImageTemplate.table_page(
"Plugin",
"插件状态",
column_name,
column_data,
text_style=plugin_row_style,
)
def task_row_style(column: str, text: str) -> RowStyle:
"""被动技能文本风格
参数:
column: 表头
text: 文本内容
返回:
RowStyle: RowStyle
"""
style = RowStyle()
if column in ["群组状态", "全局状态"]:
if text == "开启":
style.font_color = "#67C23A"
else:
style.font_color = "#F56C6C"
return style
async def build_task(group_id: str | None) -> BuildImage:
"""构造被动技能状态图片
参数:
group_id: 群组id
异常:
GroupInfoNotFound: 未找到群组
返回:
BuildImage: 被动技能状态图片
"""
task_list = await TaskInfo.all()
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
group = None
if group_id:
group = await GroupConsole.get_or_none(group_id=group_id)
if not group:
raise GroupInfoNotFound()
else:
column_name.remove("群组状态")
column_data = []
for task in task_list:
if group:
column_data.append(
[
task.id,
task.module,
task.name,
"开启" if task.module not in group.block_task else "关闭",
"开启" if task.status else "关闭",
task.run_time,
]
)
else:
column_data.append(
[
task.id,
task.module,
task.name,
"开启" if task.status else "关闭",
task.run_time,
]
)
return await ImageTemplate.table_page(
"Task",
"被动技能状态",
column_name,
column_data,
text_style=task_row_style,
)
class PluginManage:
@classmethod
async def block(cls, module: str):
await PluginInfo.filter(module=module).update(status=False)
@classmethod
async def unblock(cls, module: str):
await PluginInfo.filter(module=module).update(status=True)
@classmethod
async def block_group_plugin(cls, plugin_name: str, group_id: str) -> str:
"""禁用群组插件
参数:
plugin_name: 插件名称
group_id: 群组id
返回:
str: 返回信息
"""
return await cls._change_group_plugin(plugin_name, group_id, True)
@classmethod
async def unblock_group_plugin(cls, plugin_name: str, group_id: str) -> str:
"""启用群组插件
参数:
plugin_name: 插件名称
group_id: 群组id
返回:
str: 返回信息
"""
return await cls._change_group_plugin(plugin_name, group_id, False)
@classmethod
async def _change_group_plugin(
cls, plugin_name: str, group_id: str, status: bool
) -> str:
"""修改群组插件状态
参数:
plugin_name: 插件名称
group_id: 群组id
status: 插件状态
返回:
str: 返回信息
"""
status_str = "开启" if status else "关闭"
if plugin := await PluginInfo.get_or_none(name=plugin_name):
group, _ = await GroupConsole.get_or_create(group_id=group_id)
if status:
if plugin.module in group.block_plugin:
group.block_plugin = group.block_plugin.replace(
f"{plugin.module},", ""
)
await group.save(update_fields=["block_plugin"])
return f"已成功{status_str} {plugin_name} 功能!"
else:
if plugin.module not in group.block_plugin:
group.block_plugin += f"{plugin.module},"
await group.save(update_fields=["block_plugin"])
return f"已成功{status_str} {plugin_name} 功能!"
return f"该功能已经{status_str}了喔,不要重复{status_str}..."
return "没有找到这个功能喔..."
@classmethod
async def superuser_block(
cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
) -> str:
"""超级用户禁用
参数:
plugin_name: 插件名称
block_type: 禁用类型
group_id: 群组id
返回:
str: 返回信息
"""
if plugin := await PluginInfo.get_or_none(name=plugin_name):
if group_id:
if group := await GroupConsole.get_or_none(group_id=group_id):
if f"super:{plugin_name}," not in group.block_plugin:
group.block_plugin += f"super:{plugin_name},"
await group.save(update_fields=["block_plugin"])
return (
f"已成功关闭群组 {group.group_name} 的 {plugin_name} 功能!"
)
return "此群组该功能已被超级用户关闭,不要重复关闭..."
return "群组信息未更新,请先更新群组信息..."
plugin.block_type = block_type
plugin.status = not bool(block_type)
await plugin.save(update_fields=["status", "block_type"])
if not block_type:
return f"已成功将 {plugin_name} 全局启用!"
else:
return f"已成功将 {plugin_name} 全局关闭!"
return "没有找到这个功能喔..."
@@ -1,26 +1,22 @@
import os
import shutil
from typing import Dict
from typing import Annotated, Dict
import ujson as json
from arclet.alconna import Args, Option
from nonebot import on_command
from nonebot.params import Command
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
AlconnaMatch,
Arparma,
Match,
on_alconna,
store_true,
)
from nonebot_plugin_alconna.matcher import AlconnaMatcher
from nonebot_plugin_saa import Text
from nonebot_plugin_alconna import Image
from nonebot_plugin_alconna import Text as alcText
from nonebot_plugin_alconna import UniMsg
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.rules import admin_check, ensure_group
base_config = Config.get("admin_bot_manage")
@@ -48,18 +44,15 @@ __plugin_meta__ = PluginMetadata(
).dict(),
)
_matcher = on_alconna(
Alconna(
"设置欢迎消息",
Args["message", str],
Option("-at", action=store_true, help_text="是否at新入群用户"),
),
_matcher = on_command(
"设置欢迎消息",
rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL")
& ensure_group,
priority=5,
block=True,
)
BASE_PATH = DATA_PATH / "welcome_message"
BASE_PATH.mkdir(parents=True, exist_ok=True)
@@ -86,31 +79,43 @@ if old_file.exists():
@_matcher.handle()
async def _(
session: EventSession,
matcher: AlconnaMatcher,
arparma: Arparma,
message: str,
message: UniMsg,
command: Annotated[tuple[str, ...], Command()],
):
file = (
BASE_PATH
/ f"{session.platform or session.bot_type}"
/ f"{session.id2}"
/ "text.json"
)
path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}"
if session.id3:
file = (
path = (
BASE_PATH
/ f"{session.platform or session.bot_type}"
/ f"{session.id3}"
/ f"{session.id2}"
/ "text.json"
)
file = path / "text.json"
idx = 0
text = ""
for f in os.listdir(path):
(path / f).unlink()
message[0].text = message[0].text.replace(command[0], "").strip()
for msg in message:
if isinstance(msg, alcText):
text += msg.text
elif isinstance(msg, Image):
if msg.url:
text += f"[image:{idx}]"
await AsyncHttpx.download_file(msg.url, path / f"{idx}.png")
idx += 1
else:
logger.debug("图片 URL 为空...", command[0])
if not file.exists():
file.parent.mkdir(exist_ok=True, parents=True)
is_at = "-at" in message
text = text.replace("-at", "")
json.dump(
{"at": arparma.find("at"), "message": message},
{"at": is_at, "message": text},
file.open("w"),
ensure_ascii=False,
indent=4,
)
logger.info(f"设置群欢迎消息成功: {message}", arparma.header_result, session=session)
await Text(f"设置欢迎消息成功: \n{message}").send()
uni_msg = alcText("设置欢迎消息成功: \n") + message
await uni_msg.send()
logger.info(f"设置群欢迎消息成功: {text}", command[0], session=session)
@@ -0,0 +1,4 @@
import nonebot
from pathlib import Path
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
@@ -0,0 +1,83 @@
from nonebot import on_message
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import UniMsg
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.chat_history import ChatHistory
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
__plugin_meta__ = PluginMetadata(
name="消息存储",
description="消息存储,被动存储群消息",
usage="",
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
configs=[
RegisterConfig(
module="chat_history",
key="FLAG",
value=True,
help="是否开启消息自从存储",
default_value=True,
type=bool,
)
],
).dict(),
)
def rule(message: UniMsg) -> bool:
return bool(Config.get_config("chat_history", "FLAG") and message)
chat_history = on_message(rule=rule, priority=1, block=False)
TEMP_LIST = []
@chat_history.handle()
async def _(message: UniMsg, session: EventSession):
group_id = session.id3 or session.id2
TEMP_LIST.append(
ChatHistory(
user_id=session.id1,
group_id=group_id,
text=str(message),
plain_text=message.extract_plain_text(),
bot_id=session.bot_id,
platform=session.platform,
)
)
@scheduler.scheduled_job(
"interval",
minutes=1,
)
async def _():
try:
message_list = TEMP_LIST.copy()
TEMP_LIST.clear()
if message_list:
await ChatHistory.bulk_create(message_list)
logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务")
except Exception as e:
logger.error(f"定时批量添加聊天记录", "定时任务", e=e)
# @test.handle()
# async def _(event: MessageEvent):
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
# print(await ChatHistory.get_group_msg(event.group_id))
# print(await ChatHistory.get_group_msg_count(event.group_id))
@@ -0,0 +1,116 @@
from datetime import datetime, timedelta
import pytz
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
Args,
Arparma,
Match,
Option,
on_alconna,
store_true,
)
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.chat_history import ChatHistory
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import ImageTemplate
__plugin_meta__ = PluginMetadata(
name="消息统计查询",
description="消息统计查询",
usage="",
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.NORMAL,
menu_type="数据统计",
).dict(),
)
# TODO: shortcut
_matcher = on_alconna(
Alconna(
"消息排行",
Option("--des", default=False, action=store_true),
Args["type?", ["日", "周", "月", "年"]]["count?", int, 10],
),
priority=5,
block=True,
)
@_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
type: Match[str],
count: Match[int],
):
group_id = session.id3 or session.id2
if not group_id:
await Text("群组id为空...").finish()
time_now = datetime.now()
date_scope = None
zero_today = time_now - timedelta(
hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second
)
date = type.result if type.available else None
if date:
if date in ["日"]:
date_scope = (zero_today, time_now)
elif date in ["周"]:
date_scope = (time_now - timedelta(days=7), time_now)
elif date in ["月"]:
date_scope = (time_now - timedelta(days=30), time_now)
column_name = ["名次", "昵称", "发言次数"]
if rank_data := await ChatHistory.get_group_msg_rank(
group_id, count.result, "DES" if arparma.find("des") else "DESC", date_scope
):
idx = 1
data_list = []
for uid, num in rank_data:
if user := await GroupInfoUser.filter(
user_id=uid, group_id=group_id
).first():
user_name = user.user_name
else:
user_name = uid
data_list.append([idx, user_name, num])
idx += 1
if not date_scope:
if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id):
date_scope = date_scope.astimezone(
pytz.timezone("Asia/Shanghai")
).replace(microsecond=0)
else:
date_scope = time_now.replace(microsecond=0)
date_str = f"{date_scope} - 至今"
else:
date_str = f"{date_scope[0].replace(microsecond=0)} - {date_scope[1].replace(microsecond=0)}"
A = await ImageTemplate.table_page(
f"消息排行({count.result})", date_str, column_name, data_list
)
logger.info(
f"查看消息排行 数量={count.result}", arparma.header_result, session=session
)
await Image(A.pic2bs4()).finish(reply=True)
await Text("群组消息记录为空...").finish()
# # @test.handle()
# # async def _(event: MessageEvent):
# # print(await ChatHistory.get_user_msg(event.user_id, "private"))
# # print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
# # print(await ChatHistory.get_user_msg(event.user_id, "group"))
# # print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
# # print(await ChatHistory.get_group_msg(event.group_id))
# # print(await ChatHistory.get_group_msg_count(event.group_id))
+80
View File
@@ -0,0 +1,80 @@
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Args, Match, on_alconna
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from ._data_source import create_help_img, get_plugin_help
from ._utils import GROUP_HELP_PATH
__plugin_meta__ = PluginMetadata(
name="帮助",
description="帮助",
usage="",
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
configs=[
RegisterConfig(
key="type",
value="normal",
help="帮助图片样式 ['normal', 'HTML']",
default_value="normal",
)
],
).dict(),
)
SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png"
if SIMPLE_HELP_IMAGE.exists():
SIMPLE_HELP_IMAGE.unlink()
_matcher = on_alconna(
Alconna(
"功能",
Args["name?", str],
),
aliases={"help", "帮助"},
rule=to_me(),
priority=1,
block=True,
)
# TODO: 插件使用详情 图片形式的帮助回复
@_matcher.handle()
async def _(
name: Match[str],
session: EventSession,
):
if name.available:
if text := await get_plugin_help(name.result):
await Text(text).send(reply=True)
else:
await Text("没有此功能的帮助信息...").send()
logger.info(
f"查看帮助详情: {name.result}",
"帮助",
session=session,
)
else:
if gid := session.id3 or session.id2:
_image_path = GROUP_HELP_PATH / f"{gid}.png"
if not _image_path.exists():
await create_help_img(gid)
await Image(_image_path).finish()
else:
if not SIMPLE_HELP_IMAGE.exists():
if SIMPLE_HELP_IMAGE.exists():
SIMPLE_HELP_IMAGE.unlink()
await create_help_img(None)
await Image(SIMPLE_HELP_IMAGE).finish()
+13
View File
@@ -0,0 +1,13 @@
from pydantic import BaseModel
class Item(BaseModel):
plugin_name: str
sta: int
class PluginList(BaseModel):
plugin_type: str
icon: str
logo: str
items: list[Item]
@@ -0,0 +1,35 @@
import nonebot
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.utils.image_utils import BuildImage
from ._utils import HelpImageBuild
random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
background = IMAGE_PATH / "background" / "0.png"
async def create_help_img(group_id: int | None):
"""
说明:
生成帮助图片
参数:
:param group_id: 群号
"""
await HelpImageBuild().build_image(group_id)
async def get_plugin_help(name: str) -> str:
"""获取功能的帮助信息
参数:
name: 插件名称
"""
if plugin := await PluginInfo.get_or_none(name=name):
_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
if _plugin and _plugin.metadata:
return _plugin.metadata.usage
return "糟糕! 该功能没有帮助喔..."
return "没有查找到这个功能噢..."
+242
View File
@@ -0,0 +1,242 @@
import os
import random
from typing import Dict
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.image_utils import BuildImage, build_sort_image, group_image
from ._config import Item
GROUP_HELP_PATH = DATA_PATH / "group_help"
GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True)
for f in os.listdir(GROUP_HELP_PATH):
group_help_image = GROUP_HELP_PATH / f
group_help_image.unlink()
BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help"
LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo"
class HelpImageBuild:
def __init__(self):
self._data: list[PluginInfo] = []
self._sort_data: Dict[str, list[PluginInfo]] = {}
self._image_list = []
self.icon2str = {
"normal": "fa fa-cog",
"原神相关": "fa fa-circle-o",
"常规插件": "fa fa-cubes",
"联系管理员": "fa fa-envelope-o",
"抽卡相关": "fa fa-credit-card-alt",
"来点好康的": "fa fa-picture-o",
"数据统计": "fa fa-bar-chart",
"一些工具": "fa fa-shopping-cart",
"商店": "fa fa-shopping-cart",
"其它": "fa fa-tags",
"群内小游戏": "fa fa-gamepad",
}
async def sort_type(self):
"""
对插件按照菜单类型分类
"""
if not self._data:
self._data = await PluginInfo.filter(plugin_type=PluginType.NORMAL)
if not self._sort_data:
for plugin in self._data:
menu_type = plugin.menu_type or "normal"
if not self._sort_data.get(menu_type):
self._sort_data[menu_type] = []
self._sort_data[menu_type].append(plugin)
async def build_image(self, group_id: int | None):
if group_id:
help_image = GROUP_HELP_PATH / f"{group_id}.png"
else:
help_image = IMAGE_PATH / f"SIMPLE_HELP.png"
build_type = Config.get_config("help", "TYPE")
if build_type == "HTML":
byt = await self.build_html_image(group_id)
with open(help_image, "wb") as f:
f.write(byt)
else:
img = await self.build_pil_image(group_id)
await img.save(help_image)
async def build_html_image(self, group_id: int | None) -> bytes:
from nonebot_plugin_htmlrender import template_to_pic
await self.sort_type()
classify = {}
for menu in self._sort_data:
for plugin in self._sort_data[menu]:
sta = 0
if not plugin.status:
if group_id and plugin.block_type in [
BlockType.ALL,
BlockType.GROUP,
]:
sta = 2
if not group_id and plugin.block_type in [
BlockType.ALL,
BlockType.FRIEND,
]:
sta = 2
if group_id and (
group := await GroupConsole.get_or_none(group_id=group_id)
):
if f"{plugin.module}:super," in group.block_plugin:
sta = 2
if f"{plugin.module}," in group.block_plugin:
sta = 1
if classify.get(menu):
classify[menu].append(Item(plugin_name=plugin.name, sta=sta))
else:
classify[menu] = [Item(plugin_name=plugin.name, sta=sta)]
max_len = 0
flag_index = -1
max_data = None
plugin_list = []
for index, plu in enumerate(classify.keys()):
if plu in self.icon2str.keys():
icon = self.icon2str[plu]
else:
icon = "fa fa-pencil-square-o"
logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH))
data = {
"name": plu if plu != "normal" else "功能",
"items": classify[plu],
"icon": icon,
"logo": str(logo.absolute()),
}
if len(classify[plu]) > max_len:
max_len = len(classify[plu])
flag_index = index
max_data = data
plugin_list.append(data)
del plugin_list[flag_index]
plugin_list.insert(0, max_data)
pic = await template_to_pic(
template_path=str((TEMPLATE_PATH / "menu").absolute()),
template_name="zhenxun_menu.html",
templates={"plugin_list": plugin_list},
pages={
"viewport": {"width": 1903, "height": 975},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)
return pic
async def build_pil_image(self, group_id: int | None) -> BuildImage:
"""构造帮助图片
参数:
group_id: 群号
"""
self._image_list = []
await self.sort_type()
font_size = 24
build_type = Config.get_config("help", "TYPE")
_image = BuildImage.build_text_image("1", size=font_size)
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
for idx, menu_type in enumerate(self._sort_data.keys()):
plugin_list = self._sort_data[menu_type]
wh_list = [BuildImage.get_text_size(x.name, font) for x in plugin_list]
wh_list.append(BuildImage.get_text_size(menu_type, font))
# sum_height = sum([x[1] for x in wh_list])
if build_type == "VV":
sum_height = 50 * len(plugin_list) + 10
else:
sum_height = (font_size + 6) * len(plugin_list) + 10
max_width = max([x[0] for x in wh_list]) + 20
bk = BuildImage(
max_width + 40,
sum_height + 50,
font_size=30,
color="#a7d1fc",
font="CJGaoDeGuo.otf",
)
title_size = bk.getsize(menu_type)
max_width = max_width if max_width > title_size[0] else title_size[0]
B = BuildImage(
max_width + 40,
sum_height,
font_size=font_size,
color="white" if not idx % 2 else "black",
)
curr_h = 10
if group := await GroupConsole.get_or_none(group_id=group_id):
for i, plugin in enumerate(plugin_list):
text_color = (255, 255, 255) if idx % 2 else (0, 0, 0)
if f"{plugin.module}," in group.block_plugin:
text_color = (252, 75, 13)
pos = None
# 禁用状态划线
if (
plugin.block_type in [BlockType.ALL, BlockType.GROUP]
or f"{plugin.module}:super," in group.block_plugin
):
w = curr_h + int(B.getsize(plugin.name)[1] / 2) + 2
pos = (
7,
w,
B.getsize(plugin.name)[0] + 35,
w,
)
if build_type == "VV":
name_image = await self.build_name_image( # type: ignore
max_width,
plugin.name,
"black" if not idx % 2 else "white",
text_color,
pos,
)
await B.paste(name_image, (0, curr_h), center_type="width")
curr_h += name_image.h + 5
else:
await B.text((10, curr_h), f"{i + 1}.{plugin.name}", text_color)
if pos:
await B.line(pos, (236, 66, 7), 3)
curr_h += font_size + 5
if menu_type == "normal":
menu_type = "功能"
await bk.text((0, 14), menu_type, center_type="width")
await bk.paste(B, (0, 50))
await bk.transparent(2)
# await bk.acircle_corner(point_list=['lt', 'rt'])
self._image_list.append(bk)
image_group, h = group_image(self._image_list)
B = await build_sort_image(
image_group,
h,
background_path=BACKGROUND_PATH,
background_handle=lambda image: image.filter("GaussianBlur", 5),
)
w = 10
h = 10
for msg in [
"目前支持的功能列表:",
"可以通过 ‘帮助[功能名称]’ 来获取对应功能的使用方法",
]:
text = await BuildImage.build_text_image(msg, "HYWenHei-85W.ttf", 24)
await B.paste(text, (w, h))
h += 50
if msg == "目前支持的功能列表:":
w += 50
text = await BuildImage.build_text_image(
"注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
"HYWenHei-85W.ttf",
24,
(231, 74, 57),
)
await B.paste(
text,
(300, 10),
)
return B
+43
View File
@@ -0,0 +1,43 @@
from pathlib import Path
import nonebot
from zhenxun.configs.config import Config
Config.add_plugin_config(
"hook",
"CHECK_NOTICE_INFO_CD",
300,
help="群检测,个人权限检测等各种检测提示信息cd",
default_value=300,
type=int,
)
Config.add_plugin_config(
"hook",
"MALICIOUS_BAN_TIME",
30,
help="恶意命令触发检测触发后ban的时长(分钟)",
default_value=30,
type=int,
)
Config.add_plugin_config(
"hook",
"MALICIOUS_CHECK_TIME",
5,
help="恶意命令触发检测规定时间内(秒)",
default_value=5,
type=int,
)
Config.add_plugin_config(
"hook",
"MALICIOUS_BAN_COUNT",
6,
help="恶意命令触发检测最大触发次数",
default_value=6,
type=int,
)
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
+61
View File
@@ -0,0 +1,61 @@
from nonebot.adapters import Bot, Event
from nonebot.exception import IgnoredException
from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor
from nonebot.typing import T_State
from nonebot_plugin_saa import Mention, MessageFactory, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.models.ban_console import BanConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.utils import FreqLimiter
Config.add_plugin_config(
"hook",
"BAN_RESULT",
"才不会给你发消息.",
help="对被ban用户发送的消息",
)
_flmt = FreqLimiter(300)
# 检查是否被ban
@run_preprocessor
async def _(
matcher: Matcher, bot: Bot, event: Event, state: T_State, session: EventSession
):
if plugin := matcher.plugin:
if metadata := plugin.metadata:
extra = metadata.extra
if extra.get("plugin_type") == PluginType.HIDDEN:
return
user_id = session.id1
group_id = session.id3 or session.id2
if user_id:
ban_result = Config.get_config("hook", "BAN_RESULT")
if user_id in bot.config.superusers:
return
if await BanConsole.is_ban(user_id) or await BanConsole.is_ban(
user_id, group_id
):
time = await BanConsole.check_ban_time(user_id)
if time == -1:
time_str = "∞"
else:
time = abs(int(time))
if time < 60:
time_str = str(time) + " 秒"
else:
time_str = str(int(time / 60)) + " 分钟"
if ban_result and _flmt.check(user_id):
_flmt.start_cd(user_id)
await MessageFactory(
[
Mention(user_id),
Text(f"{ban_result}\n在..在 {time_str} 后才会理你喔"),
]
).send()
raise IgnoredException("用户处于黑名单中")
@@ -0,0 +1,104 @@
import time
from collections import defaultdict
from click import command
from nonebot.adapters.onebot.v11 import ActionFailed, Bot, GroupMessageEvent
from nonebot.exception import IgnoredException
from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor
from nonebot.typing import T_State
from nonebot_plugin_alconna import Arparma
from nonebot_plugin_saa import Mention, MessageFactory, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.models.ban_console import BanConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
malicious_check_time = Config.get_config("hook", "MALICIOUS_CHECK_TIME")
malicious_ban_count = Config.get_config("hook", "MALICIOUS_BAN_COUNT")
if not malicious_check_time:
raise ValueError("模块: [hook], 配置项: [MALICIOUS_CHECK_TIME] 为空或小于0")
if not malicious_ban_count:
raise ValueError("模块: [hook], 配置项: [MALICIOUS_BAN_COUNT] 为空或小于0")
class BanCheckLimiter:
"""
恶意命令触发检测
"""
def __init__(self, default_check_time: float = 5, default_count: int = 4):
self.mint = defaultdict(int)
self.mtime = defaultdict(float)
self.default_check_time = default_check_time
self.default_count = default_count
def add(self, key: str | int | float):
if self.mint[key] == 1:
self.mtime[key] = time.time()
self.mint[key] += 1
def check(self, key: str | int | float) -> bool:
if time.time() - self.mtime[key] > self.default_check_time:
self.mtime[key] = time.time()
self.mint[key] = 0
return False
if (
self.mint[key] >= self.default_count
and time.time() - self.mtime[key] < self.default_check_time
):
self.mtime[key] = time.time()
self.mint[key] = 0
return True
return False
_blmt = BanCheckLimiter(
malicious_check_time,
malicious_ban_count,
)
# 恶意触发命令检测
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State):
if plugin := matcher.plugin:
if metadata := plugin.metadata:
extra = metadata.extra
if extra.get("plugin_type") == PluginType.HIDDEN:
return
user_id = session.id1
group_id = session.id3 or session.id2
malicious_ban_time = Config.get_config("hook", "MALICIOUS_BAN_TIME")
if not malicious_ban_time:
raise ValueError("模块: [hook], 配置项: [MALICIOUS_BAN_TIME] 为空或小于0")
if user_id:
command = state["_prefix"]["raw_command"]
if state["_alc_result"]:
command = state["_alc_result"].source.command
if command:
if _blmt.check(f"{user_id}__{command}"):
await BanConsole.ban(
user_id, group_id, 9, malicious_ban_time * 60, bot.self_id
)
logger.info(
f"触发了恶意触发检测: {matcher.plugin_name}",
"HOOK",
session=session,
)
await MessageFactory(
[
Mention(user_id),
Text(f"检测到恶意触发命令,您将被封禁 30 分钟"),
]
).send()
logger.debug(
f"触发了恶意触发检测: {matcher.plugin_name}",
"HOOK",
session=session,
)
raise IgnoredException("检测到恶意触发命令")
_blmt.add(f"{user_id}__{command}")
@@ -0,0 +1,46 @@
import asyncio
from typing import Optional
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.matcher import Matcher
from nonebot.message import run_postprocessor
from zhenxun.services.log import logger
from zhenxun.utils.utils import WithdrawManager
# TODO: 其他平台撤回消息
# 消息撤回
@run_postprocessor
async def _(
matcher: Matcher,
exception: Optional[Exception],
bot: Bot,
):
tasks = []
for message_id in WithdrawManager._data:
second = WithdrawManager._data[message_id]
tasks.append(asyncio.ensure_future(_withdraw_message(bot, message_id, second)))
WithdrawManager.remove(message_id)
await asyncio.gather(*tasks)
async def _withdraw_message(bot: Bot, message_id: str, time: int):
await asyncio.sleep(time)
logger.debug(f"撤回消息ID: {message_id}", "HOOK")
if isinstance(bot, v11Bot):
await bot.delete_msg(message_id=int(message_id))
elif isinstance(bot, v12Bot):
await bot.delete_message(message_id=message_id)
elif isinstance(bot, DodoBot):
pass
elif isinstance(bot, KaiheilaBot):
pass
elif isinstance(bot, DiscordBot):
pass
+45 -20
View File
@@ -1,26 +1,15 @@
from pathlib import Path
from typing import List
import nonebot
from nonebot import get_loaded_plugins
from nonebot.drivers import Driver
from nonebot.plugin import Plugin
from ruamel import yaml
from ruamel.yaml import YAML, round_trip_dump, round_trip_load
from ruamel.yaml.comments import CommentedMap
from ruamel.yaml import YAML
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.configs.utils import (
BaseBlock,
PluginExtraData,
PluginSetting,
RegisterConfig,
)
from zhenxun.configs.utils import PluginExtraData, PluginSetting
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.plugin_limit import PluginLimit
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginLimitType
from zhenxun.utils.enum import PluginType
_yaml = YAML(pure=True)
_yaml.allow_unicode = True
@@ -29,8 +18,11 @@ _yaml.indent = 2
driver: Driver = nonebot.get_driver()
def _handle_setting(
plugin: Plugin, plugin_list: List[PluginInfo], limit_list: List[PluginLimit]
async def _handle_setting(
plugin: Plugin,
plugin_list: list[PluginInfo],
limit_list: list[PluginLimit],
task_list: list[TaskInfo],
):
"""处理插件设置
@@ -45,6 +37,8 @@ def _handle_setting(
extra_data = PluginExtraData(**extra)
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
setting = extra_data.setting or PluginSetting()
if metadata.type == "library":
extra_data.plugin_type = PluginType.HIDDEN
plugin_list.append(
PluginInfo(
module=plugin.name,
@@ -76,6 +70,16 @@ def _handle_setting(
max_count=getattr(limit, "max_count", None),
)
)
if extra_data.tasks:
for task in extra_data.tasks:
task_list.append(
TaskInfo(
module=task.module,
name=task.name,
status=task.status,
run_time=task.run_time,
)
)
@driver.on_startup
@@ -83,14 +87,15 @@ async def _():
"""
初始化插件数据配置
"""
plugin_list: List[PluginInfo] = []
limit_list: List[PluginLimit] = []
plugin_list: list[PluginInfo] = []
limit_list: list[PluginLimit] = []
task_list: list[TaskInfo] = []
module2id = {}
if module_list := await PluginInfo.all().values("id", "module_path"):
module2id = {m["module_path"]: m["id"] for m in module_list}
for plugin in get_loaded_plugins():
if plugin.metadata:
_handle_setting(plugin, plugin_list, limit_list)
await _handle_setting(plugin, plugin_list, limit_list, task_list)
create_list = []
update_list = []
for plugin in plugin_list:
@@ -124,3 +129,23 @@ async def _():
limit_create.append(limit)
if limit_create:
await PluginLimit.bulk_create(limit_create, 10)
if task_list:
module_dict = {
t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module")
}
create_list = []
update_list = []
for task in task_list:
if task.module not in module_list:
create_list.append(task)
else:
task.id = module_dict[task.module]
update_list.append(task)
if create_list:
await TaskInfo.bulk_create(create_list, 10)
if update_list:
await TaskInfo.bulk_update(
update_list,
["run_time", "status", "name"],
10,
)
+240
View File
@@ -0,0 +1,240 @@
import random
from typing import Any, List
from nonebot import on_regex
from nonebot.adapters import Bot
from nonebot.matcher import Matcher
from nonebot.params import Depends, RegexGroup
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Option, UniMsg, on_alconna, store_true
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
__plugin_meta__ = PluginMetadata(
name="昵称系统",
description="区区昵称,才不想叫呢!",
usage=f"""
个人昵称,将替换{NICKNAME}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
指令:
以后叫我 [昵称]: 设置当前群聊/私聊的昵称
全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称
{NICKNAME}我是谁
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.NORMAL,
menu_type="商店",
configs=[
RegisterConfig(
key="BLACK_WORD",
value=["爸", "爹", "爷", "父"],
help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示",
default_value=None,
type=List[str],
)
],
).dict(),
)
_nickname_matcher = on_regex(
"(?:以后)?(?:叫我|请叫我|称呼我)(.*)",
rule=to_me(),
priority=5,
block=True,
)
_global_nickname_matcher = on_regex(
"设置全局昵称(.*)", rule=to_me(), priority=5, block=True
)
_matcher = on_alconna(
Alconna(
"nickname",
Option("--name", action=store_true, help_text="用户昵称"),
Option("--cancel", action=store_true, help_text="取消昵称"),
),
rule=to_me(),
priority=5,
block=True,
)
CALL_NAME = [
"好啦好啦,我知道啦,{},以后就这么叫你吧",
f"嗯嗯,{NICKNAME}" + "记住你的昵称了哦,{}",
"好突然,突然要叫你昵称什么的...{}..",
f"{NICKNAME}" + "会好好记住{}的,放心吧",
"好..好.,那窝以后就叫你{}了.",
]
REMIND = [
"我肯定记得你啊,你是{}啊",
"我不会忘记你的,你也不要忘记我!{}",
f"哼哼,{NICKNAME}" + "记忆力可是很好的,{}",
"嗯?你是失忆了嘛...{}..",
f"不要小看{NICKNAME}" + "的记忆力啊!笨蛋{}!QAQ",
"哎?{}..怎么了吗..突然这样问..",
]
CANCEL = [
f"呜..{NICKNAME}" + "睡一觉就会忘记的..和梦一样..{}",
"窝知道了..{}..",
f"是{NICKNAME}" + "哪里做的不好嘛..好吧..晚安{}",
"呃,{},下次我绝对绝对绝对不会再忘记你!",
"可..可恶!{}!太可恶了!呜",
]
def CheckNickname():
"""
检查名称是否合法
"""
async def dependency(
bot: Bot,
matcher: Matcher,
session: EventSession,
message: UniMsg,
reg_group: tuple[Any, ...] = RegexGroup(),
):
black_word = Config.get_config("nickname", "BLACK_WORD")
(name,) = reg_group
logger.debug(f"昵称检查: {name}", "昵称设置", session=session)
if not name:
await Text("叫你空白?叫你虚空?叫你无名??").finish(at_sender=True)
if session.id1 in bot.config.superusers:
logger.debug(
f"超级用户设置昵称, 跳过合法检测: {name}", "昵称设置", session=session
)
return
if len(name) > 20:
await Text("昵称可不能超过20个字!").finish(at_sender=True)
if name in bot.config.nickname:
await Text("笨蛋!休想占用我的名字! #").finish(at_sender=True)
if black_word:
for x in name:
if x in black_word:
logger.debug("昵称设置禁止字符: [{x}]", "昵称设置", session=session)
await Text(f"字符 [{x}] 为禁止字符!").finish(at_sender=True)
for word in black_word:
if word in name:
logger.debug(
"昵称设置禁止字符: [{word}]", "昵称设置", session=session
)
await Text(f"字符 [{x}] 为禁止字符!").finish(at_sender=True)
return Depends(dependency)
@_nickname_matcher.handle(parameterless=[CheckNickname()])
async def _(
session: EventSession,
user_info: UserInfo = EventUserInfo(),
reg_group: tuple[Any, ...] = RegexGroup(),
):
if session.id1:
(name,) = reg_group
if len(name) < 5:
if random.random() < 0.3:
name = "~".join(name)
if gid := session.id3 or session.id2:
await GroupInfoUser.set_user_nickname(
session.id1,
gid,
name,
user_info.user_displayname
or user_info.user_remark
or user_info.user_name,
session.platform,
)
logger.info(f"设置群昵称成功: {name}", "昵称设置", session=session)
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
else:
await FriendUser.set_user_nickname(
session.id1,
name,
user_info.user_displayname
or user_info.user_remark
or user_info.user_name,
session.platform,
)
logger.info(f"设置私聊昵称成功: {name}", "昵称设置", session=session)
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
await Text("用户id为空...").send()
@_global_nickname_matcher.handle(parameterless=[CheckNickname()])
async def _(
session: EventSession,
user_info: UserInfo = EventUserInfo(),
reg_group: tuple[Any, ...] = RegexGroup(),
):
if session.id1:
(name,) = reg_group
await FriendUser.set_user_nickname(
session.id1,
name,
user_info.user_displayname or user_info.user_remark or user_info.user_name,
session.platform,
)
await GroupInfoUser.filter(user_id=session.id1).update(nickname=name)
logger.info(f"设置全局昵称成功: {name}", "设置全局昵称", session=session)
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
await Text("用户id为空...").send()
@_matcher.assign("name")
async def _(session: EventSession, user_info: UserInfo = EventUserInfo()):
if session.id1:
if gid := session.id3 or session.id2:
nickname = await GroupInfoUser.get_user_nickname(session.id1, gid)
card = user_info.user_displayname or user_info.user_name
else:
nickname = await FriendUser.get_user_nickname(session.id1)
card = user_info.user_name
if nickname:
await Text(random.choice(REMIND).format(nickname)).finish(reply=True)
else:
await Text(
random.choice(
[
"没..没有昵称嘛,{}",
"啊,你是{}啊,我想叫你的昵称!",
"是{}啊,有什么事吗?",
"你是{}?",
]
).format(card)
).finish(reply=True)
await Text("用户id为空...").send()
@_matcher.assign("cancel")
async def _(bot: Bot, session: EventSession, user_info: UserInfo = EventUserInfo()):
if session.id1:
gid = session.id3 or session.id2
if gid:
nickname = await GroupInfoUser.get_user_nickname(session.id1, gid)
else:
nickname = await FriendUser.get_user_nickname(session.id1)
if nickname:
await Text(random.choice(CANCEL).format(nickname)).send(reply=True)
if gid:
await GroupInfoUser.set_user_nickname(session.id1, gid, "")
else:
await FriendUser.set_user_nickname(session.id1, "")
await BanConsole.ban(session.id1, gid, 9, 60, bot.self_id)
return
else:
await Text("你在做梦吗?你没有昵称啊").finish(reply=True)
await Text("用户id为空...").send()
@@ -0,0 +1,297 @@
import os
import random
import re
from datetime import datetime
import nonebot
import ujson as json
from nonebot import on_notice, on_request
from nonebot.adapters import Bot
from nonebot.adapters.onebot.v11 import (
GroupDecreaseNoticeEvent,
GroupIncreaseNoticeEvent,
)
from nonebot.adapters.onebot.v12 import (
GroupMemberDecreaseEvent,
GroupMemberIncreaseEvent,
)
from nonebot.plugin import PluginMetadata
from nonebot_plugin_saa import Image, Mention, MessageFactory, Text
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.level_user import LevelUser
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType
from zhenxun.utils.utils import FreqLimiter
__plugin_meta__ = PluginMetadata(
name="QQ群事件处理",
description="群事件处理",
usage="",
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
configs=[
RegisterConfig(
module="invite_manager",
key="message",
value=f"请不要未经同意就拉{NICKNAME}入群!告辞!",
help="强制拉群后进群回复的内容",
),
RegisterConfig(
module="invite_manager",
key="flag",
value=True,
help="强制拉群后进群回复的内容",
default_value=True,
type=bool,
),
RegisterConfig(
module="invite_manager",
key="welcome_msg_cd",
value=5,
help="群欢迎消息cd",
default_value=5,
type=int,
),
RegisterConfig(
module="_task",
key="DEFAULT_GROUP_WELCOME",
value=True,
help="被动 进群欢迎 进群默认开关状态",
default_value=True,
type=bool,
),
RegisterConfig(
module="_task",
key="DEFAULT_REFUND_GROUP_REMIND",
value=True,
help="被动 退群提醒 进群默认开关状态",
default_value=True,
type=bool,
),
],
tasks=[
Task(module="group_welcome", name="进群欢迎"),
Task(module="refund_group_remind", name="退群提醒"),
],
).dict(),
)
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
base_config = Config.get("invite_manager")
limit_cd = base_config.get("welcome_msg_cd")
_flmt = FreqLimiter(limit_cd)
group_increase_handle = on_notice(priority=1, block=False)
"""群员增加处理"""
group_decrease_handle = on_notice(priority=1, block=False)
"""群员减少处理"""
add_group = on_request(priority=1, block=False)
"""加群同意请求"""
@group_increase_handle.handle()
async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
user_id = str(event.user_id)
group_id = str(event.group_id)
if user_id == bot.self_id:
"""新成员为bot本身"""
group = await GroupConsole.get_or_none(group_id=group_id)
if (not group or group.group_flag == 0) and base_config.get("flag"):
"""群聊不存在或被强制拉群,退出该群"""
try:
if result_msg := base_config.get("message"):
await bot.send_group_msg(
group_id=event.group_id, message=result_msg
)
await bot.set_group_leave(group_id=event.group_id)
await bot.send_private_msg(
user_id=int(superuser),
message=f"触发强制入群保护,已成功退出群聊 {group_id}...",
)
logger.info(
f"强制拉群或未有群信息,退出群聊成功",
"入群检测",
group_id=event.group_id,
)
if req := await FgRequest.get_or_none(group_id=group_id):
req.handle_type = RequestHandleType.IGNORE
await req.save(update_fields=["handle_type"])
except Exception as e:
logger.error(
f"强制拉群或未有群信息,退出群聊失败",
"入群检测",
group_id=event.group_id,
e=e,
)
await bot.send_private_msg(
user_id=int(superuser),
message=f"触发强制入群保护,退出群聊 {event.group_id} 失败...",
)
elif group_id not in await GroupConsole.all().values_list(
"group_id", flat=True
):
"""默认群功能开关"""
block_plugin = ""
if plugin_list := await PluginInfo.filter(default_status=False).all():
for plugin in plugin_list:
block_plugin += f"{plugin.module},"
group_info = await bot.get_group_info(group_id=event.group_id)
await GroupConsole.create(
group_id=group_info["group_id"],
group_name=group_info["group_name"],
max_member_count=group_info["max_member_count"],
member_count=group_info["member_count"],
group_flag=1,
block_plugin=block_plugin,
platform="qq",
)
admin_default_auth = Config.get_config(
"admin_bot_manage", "ADMIN_DEFAULT_AUTH"
)
# 即刻刷新权限
for user_info in await bot.get_group_member_list(group_id=event.group_id):
"""即刻刷新权限"""
if (
user_info["role"]
in [
"owner",
"admin",
]
and not await LevelUser.is_group_flag(
user_info["user_id"], group_id
)
and admin_default_auth is not None
):
await LevelUser.set_level(
user_info["user_id"],
user_info["group_id"],
admin_default_auth,
)
logger.debug(
f"添加默认群管理员权限: {admin_default_auth}",
"入群检测",
session=user_info["user_id"],
group_id=user_info["group_id"],
)
if str(user_info["user_id"]) in bot.config.superusers:
await LevelUser.set_level(
user_info["user_id"], user_info["group_id"], 9
)
logger.debug(
f"添加超级用户权限: 9",
"入群检测",
session=user_info["user_id"],
group_id=user_info["group_id"],
)
else:
join_time = datetime.now()
user_info = await bot.get_group_member_info(
group_id=event.group_id, user_id=event.user_id
)
await GroupInfoUser.update_or_create(
user_id=str(user_info["user_id"]),
group_id=str(user_info["group_id"]),
defaults={"user_name": user_info["nickname"], "user_join_time": join_time},
)
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
if _flmt.check(group_id):
"""群欢迎消息"""
_flmt.start_cd(group_id)
path = DATA_PATH / "welcome_message" / "qq" / f"{group_id}"
data = json.load((path / "text.json").open())
message = data["message"]
msg_split = re.split(r"\[image:\d+\]", message)
msg_list = []
if data["at"]:
msg_list.append(Mention(user_id))
for i, text in enumerate(msg_split):
msg_list.append(Text(text))
img_file = path / f"{i}.png"
if img_file.exists():
msg_list.append(Image(img_file))
if GroupConsole.is_block_task(group_id, "group_welcome"):
logger.info(f"发送群欢迎消息...", "入群检测", group_id=group_id)
if msg_list:
await MessageFactory(msg_list).send()
else:
await MessageFactory(
[
Text("新人快跑啊!!本群现状↓(快使用自定义!)"),
Image(
IMAGE_PATH
/ "qxz"
/ random.choice(os.listdir(IMAGE_PATH / "qxz"))
),
]
).send()
@group_decrease_handle.handle()
async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent):
if event.sub_type == "kick_me":
"""踢出Bot"""
group_id = event.group_id
operator_id = event.operator_id
if user := await GroupInfoUser.get_or_none(
user_id=str(event.operator_id), group_id=str(event.group_id)
):
operator_name = user.user_name
else:
operator_name = "None"
group = await GroupConsole.filter(group_id=str(group_id)).first()
group_name = group.group_name if group else ""
coffee = int(list(bot.config.superusers)[0])
await bot.send_private_msg(
user_id=coffee,
message=f"****呜..一份踢出报告****\n"
f"我被 {operator_name}({operator_id})\n"
f"踢出了 {group_name}({group_id})\n"
f"日期:{str(datetime.now()).split('.')[0]}",
)
return
if str(event.user_id) == bot.self_id:
"""踢出Bot"""
await GroupConsole.filter(group_id=str(event.group_id)).delete()
return
if user := await GroupInfoUser.get_or_none(
user_id=str(event.user_id), group_id=str(event.group_id)
):
user_name = user.user_name
else:
user_name = f"{event.user_id}"
await GroupInfoUser.filter(
user_id=str(event.user_id), group_id=str(event.group_id)
).delete()
logger.info(
f"名称: {user_name} 退出群聊",
"group_decrease_handle",
session=event.user_id,
group_id=event.group_id,
)
result = ""
if event.sub_type == "leave":
result = f"{user_name}离开了我们..."
if event.sub_type == "kick":
operator = await bot.get_group_member_info(
user_id=event.operator_id, group_id=event.group_id
)
operator_name = operator["card"] if operator["card"] else operator["nickname"]
result = f"{user_name} 被 {operator_name} 送走了."
if GroupConsole.is_block_task(str(event.group_id), "refund_group_remind"):
await group_decrease_handle.send(f"{result}")
+14 -6
View File
@@ -2,7 +2,8 @@ import time
from datetime import datetime
from typing import Dict
from nonebot import on_message, on_request
import nonebot
from nonebot import drivers, on_message, on_request
from nonebot.adapters.onebot.v11 import (
ActionFailed,
Bot,
@@ -18,7 +19,7 @@ from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_info import GroupInfo
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType, RequestType
@@ -26,7 +27,7 @@ base_config = Config.get("invite_manager")
__plugin_meta__ = PluginMetadata(
name="记录请求",
description="自定义群欢迎消息",
description="记录 好友/群组 请求",
usage="",
extra=PluginExtraData(
author="HibiKier",
@@ -61,6 +62,8 @@ class Timer:
cls.data = {k: v for k, v in cls.data.items() if v - now < 5 * 60}
# TODO: 其他平台请求
friend_req = on_request(priority=5, block=True)
group_req = on_request(priority=5, block=True)
_t = on_message(priority=999, block=False, rule=lambda: False)
@@ -69,13 +72,14 @@ _t = on_message(priority=999, block=False, rule=lambda: False)
@friend_req.handle()
async def _(bot: Bot, event: FriendRequestEvent, session: EventSession):
if event.user_id and Timer.check(event.user_id):
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
logger.debug(f"收录好友请求...", "好友请求", target=event.user_id)
user = await bot.get_stranger_info(user_id=event.user_id)
nickname = user["nickname"]
# sex = user["sex"]
# age = str(user["age"])
comment = event.comment
superuser = int(list(bot.config.superusers)[0])
superuser = int(superuser)
await Text(
f"*****一份好友申请*****\n"
f"昵称:{nickname}({event.user_id})\n"
@@ -84,7 +88,11 @@ async def _(bot: Bot, event: FriendRequestEvent, session: EventSession):
f"备注:{event.comment}"
).send_to(target=TargetQQPrivate(user_id=superuser), bot=bot)
if base_config.get("AUTO_ADD_FRIEND"):
logger.debug(f"已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id)
logger.debug(
f"已开启好友请求自动同意,成功通过该请求",
"好友请求",
target=event.user_id,
)
await bot.set_friend_add_request(flag=event.flag, approve=True)
await FriendUser.create(
user_id=str(user["user_id"]), user_name=user["nickname"]
@@ -119,7 +127,7 @@ async def _(bot: Bot, event: GroupRequestEvent, session: EventSession):
flag=event.flag, sub_type="invite", approve=True
)
group_info = await bot.get_group_info(group_id=event.group_id)
await GroupInfo.update_or_create(
await GroupConsole.update_or_create(
group_id=str(group_info["group_id"]),
defaults={
"group_name": group_info["group_name"],
@@ -0,0 +1,5 @@
from pathlib import Path
import nonebot
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
@@ -0,0 +1,62 @@
import shutil
from pathlib import Path
from nonebot_plugin_apscheduler import scheduler
from zhenxun.configs.config import Config
from zhenxun.services.log import logger
Config.add_plugin_config(
"_backup",
"BACKUP_FLAG",
True,
help="是否开启文件备份",
default_value=True,
type=bool,
)
Config.add_plugin_config(
"_backup",
"BACKUP_DIR_OR_FILE",
[
"data/black_word",
"data/configs",
"data/statistics",
"data/word_bank",
"data/manager",
"configs",
],
help="备份的文件夹或文件",
default_value=[],
type=list[str],
)
# 自动备份
@scheduler.scheduled_job(
"cron",
hour=3,
minute=25,
)
async def _():
if Config.get_config("_backup", "BACKUP_FLAG"):
_backup_path = Path() / "backup"
_backup_path.mkdir(exist_ok=True, parents=True)
if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
for path_file in backup_dir_or_file:
try:
path = Path(path_file)
_p = _backup_path / path_file
if path.exists():
if path.is_dir():
if _p.exists():
shutil.rmtree(_p, ignore_errors=True)
shutil.copytree(path_file, _p)
else:
if _p.exists():
_p.unlink()
shutil.copy(path_file, _p)
logger.debug(f"已完成自动备份:{path_file}", "自动备份")
except Exception as e:
logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e)
logger.info("自动备份成功...", "自动备份")
@@ -0,0 +1,68 @@
import nonebot
from nonebot_plugin_apscheduler import scheduler
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
# TODO: 其他平台更新
# 自动更新群组信息
@scheduler.scheduled_job(
"cron",
hour=3,
minute=1,
)
async def _():
bots = nonebot.get_bots()
_used_group = []
for bot in bots.values():
try:
group_list = await bot.get_group_list()
gl = [g["group_id"] for g in group_list if g["group_id"] not in _used_group]
for g in gl:
_used_group.append(g)
group_info = await bot.get_group_info(group_id=g)
await GroupConsole.update_or_create(
group_id=str(group_info["group_id"]),
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
"group_flag": 1,
},
)
logger.debug("自动更新群组信息成功", "自动更新群组", group_id=g)
except Exception as e:
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
logger.info("自动更新群组成员信息成功...")
# 自动更新好友信息
@scheduler.scheduled_job(
"cron",
hour=3,
minute=1,
)
async def _():
bots = nonebot.get_bots()
for key in bots:
try:
bot = bots[key]
fl = await bot.get_friend_list()
for f in fl:
if FriendUser.exists(user_id=str(f["user_id"])):
await FriendUser.create(
user_id=str(f["user_id"]), user_name=f["nickname"]
)
logger.debug(
f"更新好友信息成功", "自动更新好友", session=f["user_id"]
)
else:
logger.debug(
f"好友信息已存在", "自动更新好友", session=f["user_id"]
)
except Exception as e:
logger.error(f"自动更新好友信息错误", "自动更新好友", e=e)
logger.info("自动更新好友信息成功...")
@@ -0,0 +1,28 @@
from nonebot_plugin_apscheduler import scheduler
# TODO: 消息发送
# # 早上好
# @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("每日早安发送...")
# # 睡觉了
# @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("每日晚安发送...")
+59
View File
@@ -0,0 +1,59 @@
from asyncio.exceptions import TimeoutError
import nonebot
import ujson as json
from nonebot.drivers import Driver
from nonebot_plugin_apscheduler import scheduler
from zhenxun.configs.path_config import TEXT_PATH
from zhenxun.services.log import logger
from zhenxun.utils.http_utils import AsyncHttpx
driver: Driver = nonebot.get_driver()
@driver.on_startup
async def update_city():
"""
部分插件需要中国省份城市
这里直接更新,避免插件内代码重复
"""
china_city = TEXT_PATH / "china_city.json"
data = {}
if not china_city.exists():
try:
logger.debug("开始更新城市列表...")
res = await AsyncHttpx.get(
"http://www.weather.com.cn/data/city3jdata/china.html", timeout=5
)
res.encoding = "utf8"
provinces_data = json.loads(res.text)
for province in provinces_data.keys():
data[provinces_data[province]] = []
res = await AsyncHttpx.get(
f"http://www.weather.com.cn/data/city3jdata/provshi/{province}.html",
timeout=5,
)
res.encoding = "utf8"
city_data = json.loads(res.text)
for city in city_data.keys():
data[provinces_data[province]].append(city_data[city])
with open(china_city, "w", encoding="utf8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
logger.info("自动更新城市列表完成...")
except TimeoutError as e:
logger.warning("自动更新城市列表超时...", e=e)
except ValueError as e:
logger.warning("自动城市列表失败...", e=e)
except Exception as e:
logger.error(f"自动城市列表未知错误...", e=e)
# 自动更新城市列表
@scheduler.scheduled_job(
"cron",
hour=6,
minute=1,
)
async def _():
await update_city()
+102
View File
@@ -0,0 +1,102 @@
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.utils import BaseBlock, PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import BlockType, PluginType
from ._data_source import ShopManage
__plugin_meta__ = PluginMetadata(
name="商店",
description="商店系统[金币回收计划]",
usage="""
商品操作
指令:
添加商品 name:[名称] price:[价格] des:[描述] ?discount:[折扣](小数) ?limit_time:[限时时间](小时)
删除商品 [名称或序号]
修改商品 name:[名称或序号] price:[价格] des:[描述] discount:[折扣] limit_time:[限时]
示例:添加商品 name:萝莉酒杯 price:9999 des:普通的酒杯,但是里面.. discount:0.4 limit_time:90
示例:添加商品 name:可疑的药 price:5 des:效果未知
示例:删除商品 2
示例:修改商品 name:1 price:900 修改序号为1的商品的价格为900
* 修改商品只需添加需要值即可 *
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.NORMAL,
menu_type="商店",
limits=[BaseBlock(check_type=BlockType.GROUP)],
).dict(),
)
# TODO: 修改操作,shortcut
_matcher = on_alconna(
Alconna(
"shop",
Subcommand("my-cost", help_text="我的金币"),
Subcommand("my-props", help_text="我的道具"),
Subcommand("buy", Args["name", str]["num", int, 1], help_text="购买道具"),
Subcommand("use", Args["name", str]["num?", int, 1], help_text="使用道具"),
),
priority=5,
block=True,
)
@_matcher.assign("$main")
async def _(session: EventSession, arparma: Arparma):
image = await ShopManage.build_shop_image()
logger.info("查看商店", arparma.header_result, session=session)
await Image(image.pic2bs4()).send()
@_matcher.assign("my-cost")
async def _(session: EventSession, arparma: Arparma):
if session.id1:
logger.info("查看金币", arparma.header_result, session=session)
gold = await ShopManage.my_cost(session.id1, session.platform)
await Text(f"你的当前余额: {gold}").send(reply=True)
else:
await Text(f"用户id为空...").send(reply=True)
@_matcher.assign("my-props")
async def _(
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
):
if session.id1:
logger.info("查看道具", arparma.header_result, session=session)
if image := await ShopManage.my_props(
session.id1,
user_info.user_displayname or user_info.user_name,
session.platform,
):
await Image(image.pic2bs4()).finish(reply=True)
return await Text(f"你的道具为空捏...").send(reply=True)
else:
await Text(f"用户id为空...").send(reply=True)
@_matcher.assign("buy")
async def _(session: EventSession, arparma: Arparma, name: str, num: int):
if session.id1:
logger.info(
f"购买道具 {name}, 数量: {num}",
arparma.header_result,
session=session,
)
result = await ShopManage.buy_prop(session.id1, name, num, session.platform)
await Text(result).send(reply=True)
else:
await Text(f"用户id为空...").send(reply=True)
@_matcher.assign("use")
async def _(session: EventSession, arparma: Arparma, name: str, num: int):
pass
@@ -0,0 +1,319 @@
import time
from typing import Dict
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.goods_info import GoodsInfo
from zhenxun.models.user_console import UserConsole
from zhenxun.models.user_gold_log import UserGoldLog
from zhenxun.models.user_props_log import UserPropsLog
from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle, PropHandle
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image
ICON_PATH = IMAGE_PATH / "shop_icon"
class ShopManage:
@classmethod
async def buy_prop(
cls, user_id: str, name: str, num: int = 1, platform: str | None = None
) -> str:
if name == "神秘药水":
return "你们看看就好啦,这是不可能卖给你们的~"
if num < 0:
return "购买的数量要大于0!"
goods_list = await GoodsInfo.annotate().order_by("-id").all()
goods_list = [
goods
for goods in goods_list
if goods.goods_limit_time > time.time() or goods.goods_limit_time == 0
]
if name.isdigit():
goods = goods_list[int(name) - 1]
else:
if filter_goods := [g for g in goods_list if g.goods_name == name]:
goods = filter_goods[0]
else:
return "道具名称不存在..."
user, _ = await UserConsole.get_or_create(
user_id=user_id, defaults={"platform": platform}
)
price = goods.goods_price * num * goods.goods_discount
if user.gold < price:
return "糟糕! 您的金币好像不太够哦..."
count = await UserPropsLog.filter(
user_id=user_id, handle=PropHandle.BUY
).count()
if goods.daily_limit and count >= goods.daily_limit:
return "今天的购买已达限制了喔!"
await UserGoldLog.create(user_id=user_id, gold=price, handle=GoldHandle.BUY)
await UserPropsLog.create(
user_id=user_id, uuid=goods.uuid, gold=price, num=num, handle=PropHandle.BUY
)
logger.info(
f"花费 {price} 金币购买 {goods.goods_name} ×{num} 成功!",
"购买道具",
session=user_id,
)
user.gold -= int(price)
if goods.uuid not in user.props:
user.props[goods.uuid] = 0
user.props[goods.uuid] += num
await user.save(update_fields=["gold", "props"])
return f"花费 {price} 金币购买 {goods.goods_name} ×{num} 成功!"
@classmethod
async def my_props(
cls, user_id: str, name: str, platform: str | None = None
) -> BuildImage | None:
"""获取道具背包
参数:
user_id: 用户id
name: 用户昵称
platform: 平台.
返回:
BuildImage | None: 道具背包图片
"""
user, _ = await UserConsole.get_or_create(
user_id=user_id, defaults={"platform": platform}
)
if not user.props:
return None
result = await GoodsInfo.filter(uuid__in=user.props.keys()).all()
data_list = []
uuid2goods = {item.uuid: item for item in result}
column_name = ["-", "使用ID", "名称", "数量", "简介"]
for i, p in enumerate(user.props):
prop = uuid2goods[p]
data_list.append(
[
(ICON_PATH / prop.icon, 33, 33) if prop.icon else "",
i,
prop.goods_name,
user.props[p],
prop.goods_description,
]
)
return await ImageTemplate.table_page(
f"{name}的道具仓库", "", column_name, data_list
)
@classmethod
async def my_cost(cls, user_id: str, platform: str | None = None) -> int:
"""用户金币
参数:
user_id: 用户id
platform: 平台.
返回:
int: 金币数量
"""
user, _ = await UserConsole.get_or_create(
user_id=user_id, defaults={"platform": platform}
)
return user.gold
@classmethod
async def build_shop_image(cls) -> BuildImage:
"""制作商店图片
返回:
BuildImage: 商店图片
"""
goods_lst = await GoodsInfo.get_all_goods()
_dc = {}
font_h = BuildImage.get_text_size("正")[1]
h = 10
_list: list[GoodsInfo] = []
for goods in goods_lst:
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time:
_list.append(goods)
# A = BuildImage(1100, h, color="#f9f6f2")
total_n = 0
image_list = []
for idx, goods in enumerate(_list):
name_image = BuildImage(
580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf"
)
await name_image.text(
(15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height"
)
await name_image.line((380, -5, 280, 45), "#a29ad6", 5)
await name_image.text((390, 0), "售价:", center_type="height")
if goods.goods_discount != 1:
discount_price = int(goods.goods_discount * goods.goods_price)
old_price_image = await BuildImage.build_text_image(
str(goods.goods_price), font_color=(194, 194, 194), size=15
)
await old_price_image.line(
(
0,
int(old_price_image.height / 2),
old_price_image.width + 1,
int(old_price_image.height / 2),
),
(0, 0, 0),
)
await name_image.paste(old_price_image, (440, 0))
await name_image.text((440, 15), str(discount_price), (255, 255, 255))
else:
await name_image.text(
(440, 0),
str(goods.goods_price),
(255, 255, 255),
center_type="height",
)
_tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25)
await name_image.text(
(
440 + _tmp.width,
0,
),
f" 金币",
center_type="height",
)
des_image = None
font_img = BuildImage(600, 80, font_size=20, color="#a29ad6")
p = font_img.getsize("简介:")[0] + 20
if goods.goods_description:
des_list = goods.goods_description.split("\n")
desc = ""
for des in des_list:
if font_img.getsize(des)[0] > font_img.width - p - 20:
msg = ""
tmp = ""
for i in range(len(des)):
if font_img.getsize(tmp)[0] < font_img.width - p - 20:
tmp += des[i]
else:
msg += tmp + "\n"
tmp = des[i]
desc += msg
if tmp:
desc += tmp
else:
desc += des + "\n"
if desc[-1] == "\n":
desc = desc[:-1]
des_image = await text2image(desc, color="#a29ad6")
goods_image = BuildImage(
600,
(50 + des_image.height) if des_image else 50,
font_size=20,
color="#a29ad6",
font="CJGaoDeGuo.otf",
)
if des_image:
await goods_image.text((15, 50), "简介:")
await goods_image.paste(des_image, (p, 50))
await name_image.circle_corner(5)
await goods_image.paste(name_image, (0, 5), center_type="width")
await goods_image.circle_corner(20)
bk = BuildImage(
1180,
(50 + des_image.height) if des_image else 50,
font_size=15,
color="#f9f6f2",
font="CJGaoDeGuo.otf",
)
if goods.icon and (ICON_PATH / goods.icon).exists():
icon = BuildImage(70, 70, background=ICON_PATH / goods.icon)
await bk.paste(icon)
await bk.paste(goods_image, (70, 0))
n = 0
_w = 650
# 添加限时图标和时间
if goods.goods_limit_time > 0:
n += 140
_limit_time_logo = BuildImage(
40, 40, background=f"{IMAGE_PATH}/other/time.png"
)
await bk.paste(_limit_time_logo, (_w + 50, 0))
_time_img = await BuildImage.build_text_image("限时!", size=23)
await bk.paste(
_time_img,
(_w + 90, 10),
)
limit_time = time.strftime(
"%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)
).split()
y_m_d = limit_time[0]
_h_m = limit_time[1].split(":")
h_m = _h_m[0] + "时 " + _h_m[1] + "分"
await bk.text((_w + 55, 38), str(y_m_d))
await bk.text((_w + 65, 57), str(h_m))
_w += 140
if goods.goods_discount != 1:
n += 140
_discount_logo = BuildImage(
30, 30, background=f"{IMAGE_PATH}/other/discount.png"
)
await bk.paste(_discount_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image("折扣!", size=23)
await bk.paste(_tmp, (_w + 90, 15))
_tmp = await BuildImage.build_text_image(
f"{10 * goods.goods_discount:.1f} 折",
size=30,
font_color=(85, 156, 75),
)
await bk.paste(_tmp, (_w + 50, 44))
_w += 140
if goods.daily_limit != 0:
n += 140
_daily_limit_logo = BuildImage(
35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png"
)
await bk.paste(_daily_limit_logo, (_w + 50, 10))
_tmp = await BuildImage.build_text_image(
"限购!",
size=23,
)
await bk.paste(_tmp, (_w + 90, 20))
_tmp = await BuildImage.build_text_image(
f"{goods.daily_limit}", size=30
)
await bk.paste(_tmp, (_w + 72, 45))
if total_n < n:
total_n = n
if n:
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
# 添加限时图标和时间
image_list.append(bk)
# await A.apaste(bk, (0, current_h), True)
# current_h += 90
h = 0
current_h = 0
for img in image_list:
h += img.height + 10
A = BuildImage(1100, h, color="#f9f6f2")
for img in image_list:
await A.paste(img, (0, current_h))
current_h += img.height + 10
w = 950
if total_n:
w += total_n
h = A.height + 230 + 100
h = 1000 if h < 1000 else h
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
await shop.paste(A, (20, 230))
await shop.paste(shop_logo, (450, 30))
await shop.text(
(
int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2),
170,
),
"注【通过 序号 或者 商品名称 购买】",
)
await shop.text(
(20, h - 100),
"神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~",
)
return shop
+148
View File
@@ -0,0 +1,148 @@
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import (
Alconna,
Args,
Arparma,
Option,
on_alconna,
store_true,
)
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from ._data_source import SignManage
from .goods_register import driver
from .utils import clear_sign_data_pic
__plugin_meta__ = PluginMetadata(
name="签到",
description="每日签到,证明你在这里",
usage="""
每日签到
会影响色图概率和开箱次数,以及签到的随机道具获取
指令:
我的签到
好感度排行
* 签到时有 3% 概率 * 2 *
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
configs=[
RegisterConfig(
module="send_setu",
key="INITIAL_SETU_PROBABILITY",
value=0.7,
help="初始色图概率,总概率 = 初始色图概率 + 好感度",
default_value=0.7,
type=float,
),
RegisterConfig(
key="MAX_SIGN_GOLD",
value=200,
help="签到好感度加成额外获得的最大金币数",
default_value=200,
type=int,
),
RegisterConfig(
key="SIGN_CARD1_PROB",
value=0.2,
help="签到好感度双倍加持卡Ⅰ掉落概率",
default_value=0.2,
type=float,
),
RegisterConfig(
key="SIGN_CARD2_PROB",
value=0.09,
help="签到好感度双倍加持卡Ⅲ掉落概率",
default_value=0.09,
type=float,
),
RegisterConfig(
key="SIGN_CARD3_PROB",
value=0.05,
help="签到好感度双倍加持卡Ⅲ掉落概率",
default_value=0.05,
type=float,
),
],
limits=[PluginCdBlock()],
).dict(),
)
_sign_matcher = on_alconna(
Alconna(
"签到",
Option("--my", action=store_true, help_text="我的签到"),
Option(
"-l|--list", Args["num", int, 10], action=store_true, help_text="好感度排行"
),
),
priority=5,
block=True,
)
# TODO: shortcut
@_sign_matcher.assign("$main")
async def _(
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
):
nickname = (
user_info.user_displayname or user_info.user_remark or user_info.user_name
)
if session.id1:
if path := await SignManage.sign(session, nickname):
logger.info("签到成功", arparma.header_result, session=session)
await Image(path).finish(reply=True)
return Text("用户id为空...").send()
@_sign_matcher.assign("my")
async def _(
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
):
nickname = (
user_info.user_displayname or user_info.user_remark or user_info.user_name
)
if session.id1:
if image := await SignManage.sign(session, nickname, True):
logger.info("查看我的签到", arparma.header_result, session=session)
await Image(image).finish(reply=True)
return Text("用户id为空...").send()
@_sign_matcher.assign("list")
async def _(
session: EventSession,
arparma: Arparma,
num: int,
user_info: UserInfo = EventUserInfo(),
):
nickname = (
user_info.user_displayname or user_info.user_remark or user_info.user_name
)
if session.id1:
if image := await SignManage.rank(session.id1, num):
logger.info("查看签到排行", arparma.header_result, session=session)
await Image(image.pic2bs4()).finish()
return Text("用户id为空...").send()
@scheduler.scheduled_job(
"interval",
hours=1,
)
async def _():
try:
clear_sign_data_pic()
logger.info("清理日常签到图片数据数据完成...", "签到")
except Exception as e:
logger.error(f"清理日常签到图片数据数据失败...", e=e)
@@ -0,0 +1,175 @@
import os
import random
import secrets
from datetime import datetime
from pathlib import Path
import pytz
from nonebot_plugin_session import EventSession
from tortoise.functions import Count
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser
from zhenxun.models.user_console import UserConsole
from zhenxun.services.log import logger
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
from zhenxun.utils.utils import get_user_avatar
from ._random_event import random_event
from .utils import SIGN_TODAY_CARD_PATH, get_card
ICON_PATH = IMAGE_PATH / "_icon"
PLATFORM_PATH = {
"dodo": ICON_PATH / "dodo.png",
"discord": ICON_PATH / "discord.png",
"kaiheila": ICON_PATH / "kook.png",
"qq": ICON_PATH / "qq.png",
}
class SignManage:
@classmethod
async def rank(cls, user_id: str, num: int) -> BuildImage:
all_list = (
await SignUser.annotate()
.order_by("impression")
.values_list("user_id", flat=True)
)
index = all_list.index(user_id) + 1 # type: ignore
user_list = await SignUser.annotate().order_by("impression").limit(num).all()
user_id_list = [u.user_id for u in user_list]
log_list = (
await SignLog.filter(user_id__in=user_id_list)
.annotate(count=Count("id"))
.group_by("user_id")
.values_list("user_id", "count")
)
uid2cnt = {l[0]: l[1] for l in log_list}
column_name = ["排名", "-", "名称", "好感度", "签到次数", "平台"]
friend_list = await FriendUser.filter(user_id__in=user_id_list).values_list(
"user_id", "user_name"
)
uid2name = {f[0]: f[1] for f in friend_list}
group_member_list = await GroupInfoUser.filter(
user_id__in=user_id_list
).values_list("user_id", "user_name")
for gm in group_member_list:
uid2name[gm[0]] = gm[1]
data_list = []
for i, user in enumerate(user_list):
bytes = await get_user_avatar(user.user_id)
data_list.append(
[
f"{i+1}",
(bytes, 30, 30) if user.platform == "qq" else "",
uid2name.get(user.user_id),
user.impression,
uid2cnt.get(user.user_id) or 0,
(PLATFORM_PATH.get(user.platform), 30, 30),
]
)
return await ImageTemplate.table_page(
"好感度排行", f"你的排名在第 {index} 位哦!", column_name, data_list
)
@classmethod
async def sign(
cls, session: EventSession, nickname: str, is_view_card: bool = False
) -> Path | None:
"""签到
参数:
session: Session
nickname: 用户昵称
is_view_card: 是否展示卡片
返回:
Path: 卡片路径
"""
if not session.id1:
return None
now = datetime.now(pytz.timezone("Asia/Shanghai"))
user_console, _ = await UserConsole.get_or_create(
user_id=session.id1,
defaults={
"uid": await UserConsole.get_new_uid(),
"platform": session.platform,
},
)
user, _ = await SignUser.get_or_create(
user_id=session.id1,
defaults={"user_console": user_console, "platform": session.platform},
)
new_log = await SignLog.filter(user_id=session.id1).first()
file_name = f"{user}_sign_{datetime.now().date()}.png"
if (
user.sign_count != 0
or (new_log and now > new_log.create_time)
or file_name in os.listdir(SIGN_TODAY_CARD_PATH)
):
user_console, _ = await UserConsole.get_or_create(user_id=session.id1)
path = await get_card(user, nickname, -1, user_console.gold, "")
else:
path = await cls._handle_sign_in(user, nickname, session, is_view_card)
return path
@classmethod
async def _handle_sign_in(
cls,
user: SignUser,
nickname: str,
session: EventSession,
is_view_card: bool,
) -> Path:
"""签到处理
参数:
user: SignUser
nickname: 用户昵称
session: Session
is_view_card: 是否展示卡片
返回:
Path: 卡片路径
"""
impression_added = (secrets.randbelow(99) + 1) / 100
rand = random.random()
add_probability = float(user.add_probability)
specify_probability = user.specify_probability
if rand + add_probability > 0.97:
impression_added *= 2
elif rand < specify_probability:
impression_added *= 2
await SignUser.sign(user, impression_added, session.bot_id, session.platform)
gold = random.randint(1, 100)
gift = random_event(float(user.impression))
if isinstance(gift, int):
gold += gift
await UserConsole.add_gold(
user.user_id, gold + gift, "sign_in", session.platform
)
gift = f"额外金币 +{gift}"
else:
await UserConsole.add_gold(user.user_id, gold, "sign_in", session.platform)
await UserConsole.add_props(user.user_id, gift, 1, session.platform)
gift += " + 1"
logger.info(
f"签到成功. score: {user.impression:.2f} "
f"(+{impression_added:.2f}).获取金币/道具: {gold}",
"签到",
session=session,
)
return await get_card(
user,
nickname,
impression_added,
gold,
gift,
rand + add_probability > 0.97 or rand < specify_probability,
is_view_card,
)
@@ -0,0 +1,33 @@
import random
from zhenxun.configs.config import Config
PROB_DATA = None
def random_event(impression: float) -> str | int:
"""签到随机事件
参数:
impression: 好感度
返回:
额外奖励 和 类型
"""
global PROB_DATA
if not PROB_DATA:
PROB_DATA = {
Config.get_config("sign_in", "SIGN_CARD3_PROB"): "好感度双倍加持卡Ⅲ",
Config.get_config("sign_in", "SIGN_CARD2_PROB"): "好感度双倍加持卡Ⅱ",
Config.get_config("sign_in", "SIGN_CARD1_PROB"): "好感度双倍加持卡Ⅰ",
}
rand = random.random() - impression / 1000
for prob in PROB_DATA.keys():
if rand <= prob:
return PROB_DATA[prob]
gold = random.randint(
1, random.randint(1, int(1 if impression < 1 else impression))
)
max_sign_gold = Config.get_config("sign_in", "MAX_SIGN_GOLD")
gold = max_sign_gold if gold > max_sign_gold else gold
return gold
+49
View File
@@ -0,0 +1,49 @@
from zhenxun.configs.path_config import IMAGE_PATH
SIGN_RESOURCE_PATH = IMAGE_PATH / "sign" / "sign_res"
SIGN_TODAY_CARD_PATH = IMAGE_PATH / "sign" / "today_card"
SIGN_BORDER_PATH = SIGN_RESOURCE_PATH / "border"
SIGN_BACKGROUND_PATH = SIGN_RESOURCE_PATH / "background"
SIGN_BORDER_PATH.mkdir(exist_ok=True, parents=True)
SIGN_BACKGROUND_PATH.mkdir(exist_ok=True, parents=True)
lik2relation = {
"0": "路人",
"1": "陌生",
"2": "初识",
"3": "普通",
"4": "熟悉",
"5": "信赖",
"6": "相知",
"7": "厚谊",
"8": "亲密",
}
level2attitude = {
"0": "排斥",
"1": "警惕",
"2": "可以交流",
"3": "一般",
"4": "是个好人",
"5": "好朋友",
"6": "可以分享小秘密",
"7": "喜欢",
"8": "恋人",
}
weekdays = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
lik2level = {
9999: "9",
400: "8",
270: "7",
200: "6",
140: "5",
90: "4",
50: "3",
25: "2",
10: "1",
0: "0",
}
@@ -0,0 +1,75 @@
from decimal import Decimal
import nonebot
from nonebot.drivers import Driver
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.models.sign_user import SignUser
from zhenxun.models.user_console import UserConsole
from zhenxun.utils.decorator.shop import NotMeetUseConditionsException, shop_register
driver: Driver = nonebot.get_driver()
@driver.on_startup
async def _():
"""
导入内置的三个商品
"""
@shop_register(
name=("好感度双倍加持卡Ⅰ", "好感度双倍加持卡Ⅱ", "好感度双倍加持卡Ⅲ"),
price=(30, 150, 250),
des=(
"下次签到双倍好感度概率 + 10%(谁才是真命天子?)(同类商品将覆盖)",
"下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)",
"下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)",
),
load_status=bool(Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS")),
icon=(
"favorability_card_1.png",
"favorability_card_2.png",
"favorability_card_3.png",
),
**{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, # type: ignore
)
async def _(session: EventSession, user_id: int, group_id: int, prob: float):
user_console, _ = await UserConsole.get_or_create(
user_id=session.id1,
defaults={
"uid": await UserConsole.get_new_uid(),
"platform": session.platform,
},
)
user, _ = await SignUser.get_or_create(
user_id=user_id,
defaults={"platform": session.platform, "user_console": user_console},
)
user.add_probability = Decimal(prob)
await user.save(update_fields=["add_probability"])
@shop_register(
name="测试道具A",
price=99,
des="随便侧而出",
load_status=False,
icon="sword.png",
)
async def _(user_id: int, group_id: int):
print(user_id, group_id, "使用测试道具")
@shop_register.before_handle(name="测试道具A", load_status=False)
async def _(user_id: int, group_id: int):
print(user_id, group_id, "第一个使用前函数(before handle)")
@shop_register.before_handle(name="测试道具A", load_status=False)
async def _(user_id: int, group_id: int):
print(user_id, group_id, "第二个使用前函数(before handle)222")
raise NotMeetUseConditionsException(
"太笨了!"
) # 抛出异常,阻断使用,并返回信息
@shop_register.after_handle(name="测试道具A", load_status=False)
async def _(user_id: int, group_id: int):
print(user_id, group_id, "第一个使用后函数(after handle)")
+326
View File
@@ -0,0 +1,326 @@
import os
import random
from datetime import datetime
from io import BytesIO
from pathlib import Path
import nonebot
import pytz
from nonebot.drivers import Driver
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser
from zhenxun.utils.image_utils import BuildImage
from zhenxun.utils.utils import get_user_avatar
from .config import (
SIGN_BACKGROUND_PATH,
SIGN_BORDER_PATH,
SIGN_RESOURCE_PATH,
SIGN_TODAY_CARD_PATH,
level2attitude,
lik2level,
lik2relation,
)
driver: Driver = nonebot.get_driver()
@driver.on_startup
async def init_image():
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
await generate_progress_bar_pic()
clear_sign_data_pic()
async def get_card(
user: SignUser,
nickname: str,
add_impression: float,
gold: int | None,
gift: str,
is_double: bool = False,
is_card_view: bool = False,
) -> Path:
"""获取好感度卡片
参数:
user: SignUser
nickname: 用户昵称
impression: 新增的好感度
gold: 金币
gift: 礼物
is_double: 是否触发双倍.
is_card_view: 是否展示好感度卡片.
返回:
Path: 卡片路径
"""
user_id = user.user_id
date = datetime.now().date()
_type = "view" if is_card_view else "sign"
file_name = f"{user_id}_{_type}_{date}.png"
view_name = f"{user_id}_view_{date}.png"
card_file = Path(SIGN_TODAY_CARD_PATH) / file_name
if card_file.exists():
return IMAGE_PATH / "sign" / "today_card" / file_name
else:
if add_impression == -1:
card_file = Path(SIGN_TODAY_CARD_PATH) / view_name
if card_file.exists():
return card_file
is_card_view = True
return await _generate_card(
user, nickname, add_impression, gold, gift, is_double, is_card_view
)
async def _generate_card(
user: SignUser,
nickname: str,
impression: float,
gold: int | None,
gift: str,
is_double: bool = False,
is_card_view: bool = False,
) -> Path:
"""生成签到卡片
参数:
user: SignUser
nickname: 用户昵称
impression: 新增的好感度
gold: 金币
gift: 礼物
is_double: 是否触发双倍.
is_card_view: 是否展示好感度卡片.
返回:
Path: 卡片路径
"""
ava_bk = BuildImage(140, 140, (255, 255, 255, 0))
ava_border = BuildImage(
140,
140,
background=SIGN_BORDER_PATH / "ava_border_01.png",
)
if user.platform == "qq" and (byt := await get_user_avatar(user.user_id)):
ava = BuildImage(107, 107, background=BytesIO(byt))
else:
ava = BuildImage(107, 107, (0, 0, 0))
await ava.circle()
await ava_bk.paste(ava, (19, 18))
await ava_bk.paste(ava_border, center_type="center")
add_impression = impression
impression = float(user.impression)
info_img = BuildImage(250, 150, color=(255, 255, 255, 0), font_size=15)
level, next_impression, previous_impression = get_level_and_next_impression(
impression
)
interpolation = next_impression - impression
if level == "9":
level = "8"
interpolation = 0
await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]")
await info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}")
await info_img.text((0, 40), f"· 距离升级还差 {interpolation:.2f} 好感度")
bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
bar = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar.png")
ratio = 1 - (next_impression - user.impression) / (
next_impression - previous_impression
)
if next_impression == 0:
ratio = 0
await bar.resize(width=int(bar.width * ratio) or bar.width, height=bar.height)
await bar_bk.paste(bar)
font_size = 30
if "好感度双倍加持卡" in gift:
font_size = 20
gift_border = BuildImage(
270,
100,
background=SIGN_BORDER_PATH / "gift_border_02.png",
font_size=font_size,
)
await gift_border.text((0, 0), gift, center_type="center")
bk = BuildImage(
876,
424,
background=SIGN_BACKGROUND_PATH
/ random.choice(os.listdir(SIGN_BACKGROUND_PATH)),
font_size=25,
)
A = BuildImage(876, 274, background=SIGN_RESOURCE_PATH / "white.png")
line = BuildImage(2, 180, color="black")
await A.transparent(2)
await A.paste(ava_bk, (25, 80))
await A.paste(line, (200, 70))
nickname_img = await BuildImage.build_text_image(
nickname, size=50, font_color=(255, 255, 255)
)
user_console = await user.user_console.first()
if user_console and user_console.uid:
uid = f"{user_console.uid}".rjust(12, "0")
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
else:
uid = "XXXX XXXX XXXX"
uid_img = await BuildImage.build_text_image(
f"UID: {uid}", size=30, font_color=(255, 255, 255)
)
sign_count = await SignLog.filter(user_id=user.user_id).count()
sign_day_img = await BuildImage.build_text_image(
f"{sign_count}", size=40, font_color=(211, 64, 33)
)
lik_text1_img = await BuildImage.build_text_image("当前", size=20)
lik_text2_img = await BuildImage.build_text_image(
f"好感度:{user.impression:.2f}", size=30
)
watermark = await BuildImage.build_text_image(
f"{NICKNAME}@{datetime.now().year}", size=15, font_color=(155, 155, 155)
)
today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20)
if is_card_view:
today_sign_text_img = await BuildImage.build_text_image("", size=30)
value_list = (
await SignUser.annotate()
.order_by("impression")
.values_list("user_id", flat=True)
)
index = value_list.index(user.user_id) + 1 # type: ignore
rank_img = await BuildImage.build_text_image(
f"* 好感度排名第 {index} 位", size=30
)
await A.paste(rank_img, ((A.width - rank_img.width - 32), 20))
last_log = (
await SignLog.filter(user_id=user.user_id).order_by("create_time").first()
)
last_date = "从未"
if last_log:
last_date = last_log.create_time.astimezone(
pytz.timezone("Asia/Shanghai")
).date()
await today_data.text(
(0, 0),
f"上次签到日期:{last_date}",
)
await today_data.text((0, 25), f"总金币:{gold}")
default_setu_prob = (
Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 # type: ignore
)
await today_data.text(
(0, 50),
f"色图概率:{(default_setu_prob + float(user.impression) if user.impression < 100 else 100):.2f}%",
)
await today_data.text((0, 75), f"开箱次数:{(20 + int(user.impression / 3))}")
_type = "view"
else:
await A.paste(gift_border, (570, 140))
today_sign_text_img = await BuildImage.build_text_image("今日签到", size=30)
if is_double:
await today_data.text((0, 0), f"好感度 + {add_impression / 2:.2f} × 2")
else:
await today_data.text((0, 0), f"好感度 + {add_impression:.2f}")
await today_data.text((0, 25), f"金币 + {gold}")
_type = "sign"
current_date = datetime.now()
current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S")
data = current_date.date()
data_img = await BuildImage.build_text_image(
f"时间:{current_datetime_str}", size=20
)
await bk.paste(nickname_img, (30, 15))
await bk.paste(uid_img, (30, 85))
await bk.paste(A, (0, 150))
await bk.text((30, 167), "Accumulative check-in for")
_x = bk.getsize("Accumulative check-in for")[0] + sign_day_img.width + 45
await bk.paste(sign_day_img, (380, 158))
await bk.text((_x, 167), "days")
await bk.paste(data_img, (220, 370))
await bk.paste(lik_text1_img, (220, 240))
await bk.paste(lik_text2_img, (262, 234))
await bk.paste(bar_bk, (225, 275))
await bk.paste(info_img, (220, 305))
await bk.paste(today_sign_text_img, (550, 180))
await bk.paste(today_data, (580, 220))
await bk.paste(watermark, (15, 400))
await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{data}.png")
return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{data}.png"
async def generate_progress_bar_pic():
"""
初始化进度条图片
"""
bg_2 = (254, 1, 254)
bg_1 = (0, 245, 246)
bk = BuildImage(1000, 50)
img_x = BuildImage(50, 50, color=bg_2)
await img_x.circle()
await img_x.crop((25, 0, 50, 50))
img_y = BuildImage(50, 50, color=bg_1)
await img_y.circle()
await img_y.crop((0, 0, 25, 50))
A = BuildImage(950, 50)
width, height = A.size
step_r = (bg_2[0] - bg_1[0]) / width
step_g = (bg_2[1] - bg_1[1]) / width
step_b = (bg_2[2] - bg_1[2]) / width
for y in range(0, width):
bg_r = round(bg_1[0] + step_r * y)
bg_g = round(bg_1[1] + step_g * y)
bg_b = round(bg_1[2] + step_b * y)
for x in range(0, height):
await A.point((y, x), fill=(bg_r, bg_g, bg_b))
await bk.paste(img_y, (0, 0))
await bk.paste(A, (25, 0))
await bk.paste(img_x, (975, 0))
await bk.save(SIGN_RESOURCE_PATH / "bar.png")
A = BuildImage(950, 50)
bk = BuildImage(1000, 50)
img_x = BuildImage(50, 50)
await img_x.circle()
await img_x.crop((25, 0, 50, 50))
img_y = BuildImage(50, 50)
await img_y.circle()
await img_y.crop((0, 0, 25, 50))
await bk.paste(img_y, (0, 0))
await bk.paste(A, (25, 0))
await bk.paste(img_x, (975, 0))
await bk.save(SIGN_RESOURCE_PATH / "bar_white.png")
def get_level_and_next_impression(impression: float) -> tuple[str, int, int]:
"""获取当前好感等级与下一等级的差距
参数:
impression: 好感度
返回:
tuple[str, int, int]: 好感度等级中文,好感度等级,下一等级好感差距
"""
if impression == 0:
return lik2level[10], 10, 0
keys = list(lik2level.keys())
for i in range(len(keys)):
if impression > keys[i]:
return lik2level[keys[i]], keys[i - 1], keys[i]
return lik2level[10], 10, 0
def clear_sign_data_pic():
"""
清空当前签到图片数据
"""
date = datetime.now().date()
for file in os.listdir(SIGN_TODAY_CARD_PATH):
if str(date) not in file:
os.remove(SIGN_TODAY_CARD_PATH / file)
@@ -0,0 +1,61 @@
from typing import Annotated
from nonebot import on_command
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 UniMsg
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from ._data_source import BroadcastManage
__plugin_meta__ = PluginMetadata(
name="广播",
description="昭告天下!",
usage="""
广播 [消息] [图片]
示例:广播 你们好!
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
configs=[
RegisterConfig(
module="_task",
key="DEFAULT_BROADCAST",
value=True,
help="被动 广播 进群默认开关状态",
default_value=True,
type=bool,
)
],
tasks=[Task(module="broadcast", name="广播")],
).dict(),
)
_matcher = on_command("广播", priority=1, permission=SUPERUSER, block=True)
@_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
message: UniMsg,
command: Annotated[tuple[str, ...], Command()],
):
message[0].text = message[0].text.replace(command[0], "").strip()
# await Text("正在发送..请等一下哦!").send()
count, error_count = await BroadcastManage.send(bot, message, session)
result = f"成功广播 {count} 个群组"
if error_count:
result += f"\n广播失败 {error_count} 个群组"
await Text(f"发送广播完成!\n{result}").send(reply=True)
logger.info(f"发送广播信息: {message}", "广播", session=session)
@@ -0,0 +1,117 @@
import nonebot_plugin_alconna as alc
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_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"""
class BroadcastManage:
@classmethod
async def send(
cls, bot: Bot, message: UniMsg, session: EventSession
) -> tuple[int, int]:
"""发送广播消息
参数:
bot: Bot
message: 消息内容
session: Session
返回:
tuple[int, int]: 发送成功的群组数量, 发送失败的群组数量
"""
message_list = []
for msg in message:
if isinstance(msg, alc.Image) and msg.url:
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):
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}",
)
except Exception as e:
error_count += 1
logger.error(
"发送失败",
"广播",
session=session,
target=f"{group.group_id}:{group.channel_id}",
e=e,
)
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):
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 []
+1 -13
View File
@@ -1,21 +1,9 @@
from nonebot.adapters import Bot
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import (
Alconna,
AlconnaMatch,
Arparma,
Match,
Query,
Subcommand,
UniMessage,
on_alconna,
store_true,
)
from nonebot_plugin_alconna import Alconna, on_alconna
from nonebot_plugin_alconna.matcher import AlconnaMatcher
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
@@ -0,0 +1,161 @@
import nonebot
from arclet.alconna import Args, Option
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_alconna.matcher import AlconnaMatcher
from nonebot_plugin_saa import Image, Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.exception import EmptyError
from zhenxun.utils.image_utils import (
BuildImage,
build_sort_image,
group_image,
text2image,
)
from zhenxun.utils.rules import admin_check, ensure_group
__plugin_meta__ = PluginMetadata(
name="超级用户帮助",
description="超级用户帮助",
usage="""
超级用户帮助
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
).dict(),
)
_matcher = on_alconna(
Alconna("超级用户帮助"),
permission=SUPERUSER,
priority=5,
block=True,
)
SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png"
if SUPERUSER_HELP_IMAGE.exists():
SUPERUSER_HELP_IMAGE.unlink()
async def build_help() -> BuildImage:
"""构造超级用户帮助图片
异常:
EmptyError: 超级用户帮助为空
返回:
BuildImage: 超级用户帮助图片
"""
plugin_list = await PluginInfo.filter(plugin_type=PluginType.SUPERUSER).all()
data_list = []
for plugin in plugin_list:
if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path):
if _plugin.metadata:
data_list.append({"plugin": plugin, "metadata": _plugin.metadata})
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
image_list = []
for data in data_list:
plugin = data["plugin"]
metadata = data["metadata"]
try:
usage = None
description = None
if metadata.usage:
usage = await text2image(
metadata.usage,
padding=5,
color=(255, 255, 255),
font_color=(0, 0, 0),
)
if metadata.description:
description = await text2image(
metadata.description,
padding=5,
color=(255, 255, 255),
font_color=(0, 0, 0),
)
width = 0
height = 100
if usage:
width = usage.width
height += usage.height
if description and description.width > width:
width = description.width
height += description.height
font_width, font_height = BuildImage.get_text_size(
plugin.name + f"[{plugin.level}]", font
)
if font_width > width:
width = font_width
A = BuildImage(width + 30, height + 120, "#EAEDF2")
await A.text((15, 10), plugin.name + f"[{plugin.level}]")
await A.text((15, 70), "简介:")
if not description:
description = BuildImage(A.width - 30, 30, (255, 255, 255))
await description.circle_corner(10)
await A.paste(description, (15, 100))
if not usage:
usage = BuildImage(A.width - 30, 30, (255, 255, 255))
await usage.circle_corner(10)
await A.text((15, description.height + 115), "用法:")
await A.paste(usage, (15, description.height + 145))
await A.circle_corner(10)
image_list.append(A)
except Exception as e:
logger.warning(
f"获取超级用户管理员插件 {plugin.module}: {plugin.name} 设置失败...",
"超级用户帮助",
e=e,
)
if task_list := await TaskInfo.all():
task_str = "\n".join([task.name for task in task_list])
task_str = "通过 开启/关闭 来控制群被动\n----------\n" + task_str
task_image = await text2image(task_str, padding=5, color=(255, 255, 255))
await task_image.circle_corner(10)
A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2")
await A.text((25, 10), "被动技能")
await A.paste(task_image, (25, 50))
await A.circle_corner(10)
image_list.append(A)
if not image_list:
raise EmptyError()
image_group, _ = group_image(image_list)
A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160)
text = await BuildImage.build_text_image(
"超级用户帮助",
size=40,
)
tip = await BuildImage.build_text_image(
"注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red"
)
await A.paste(text, (50, 30))
await A.paste(tip, (50, 90))
await A.save(SUPERUSER_HELP_IMAGE)
return BuildImage(1, 1)
@_matcher.handle()
async def _(
session: EventSession,
matcher: AlconnaMatcher,
arparma: Arparma,
):
if not SUPERUSER_HELP_IMAGE.exists():
try:
await build_help()
except EmptyError:
await Text("超级用户帮助为空").finish(reply=True)
await Image(SUPERUSER_HELP_IMAGE).send()
logger.info("查看超级用户帮助", arparma.header_result, session=session)
@@ -1,119 +0,0 @@
from nonebot.adapters import Bot
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Arparma, At, Match, on_alconna
from nonebot_plugin_saa import Mention, MessageFactory, Text
from nonebot_plugin_session import EventSession, SessionLevel
from zhenxun.configs.config import Config
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_info import GroupInfo
from zhenxun.models.level_user import LevelUser
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
__plugin_meta__ = PluginMetadata(
name="更新群组/好友信息",
description="更新群组/好友信息",
usage="""
更新群组信息
更新好友信息
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
).dict(),
)
_group_matcher = on_alconna(
Alconna(
"更新群组信息",
),
permission=SUPERUSER,
rule=to_me(),
priority=1,
block=True,
)
_friend_matcher = on_alconna(
Alconna(
"更新好友信息",
),
permission=SUPERUSER,
rule=to_me(),
priority=1,
block=True,
)
# TODO: 其他adapter的更新操作
@_group_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
):
try:
gl = await bot.get_group_list()
gl = [g["group_id"] for g in gl]
num = 0
for g in gl:
try:
group_info = await bot.get_group_info(group_id=g)
await GroupInfo.update_or_create(
group_id=str(group_info["group_id"]),
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
},
)
num += 1
logger.debug(
"群聊信息更新成功", "更新群信息", session=session, target=group_info["group_id"]
)
except Exception as e:
logger.error(
f"更新群聊信息失败",
arparma.header_result,
session=session,
target=g,
)
await Text(f"成功更新了 {len(gl)} 个群的信息").send()
logger.info(
f"更新群聊信息完成,共更新了 {len(gl)} 个群的信息", arparma.header_result, session=session
)
except (ApiNotAvailable, AttributeError) as e:
await Text("Api未实现...").send()
except Exception as e:
logger.error("更新好友信息发生错误", arparma.header_result, session=session, e=e)
await Text("其他未知错误...").send()
@_friend_matcher.assign("delete")
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
):
num = 0
error_list = []
fl = await bot.get_friend_list()
for f in fl:
try:
await FriendUser.update_or_create(
user_id=str(f["user_id"]), defaults={"nickname": f["nickname"]}
)
logger.debug(f"更新好友信息成功", "更新好友信息", session=session, target=f["user_id"])
num += 1
except Exception as e:
logger.error(f"更新好友信息失败", "更新好友信息", session=session, target=f["user_id"], e=e)
await Text(f"成功更新了 {num} 个好友的信息!").send()
if error_list:
await Text(f"以下好友更新失败:\n" + "\n".join(error_list)).send()
logger.info(f"更新好友信息完成,共更新了 {num} 个群的信息", arparma.header_result, session=session)
@@ -0,0 +1,92 @@
from nonebot.adapters import Bot
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_saa import Text
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.friend_user import FriendUser
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from ._data_source import FgUpdateManage
__plugin_meta__ = PluginMetadata(
name="更新群组/好友信息",
description="更新群组/好友信息",
usage="""
更新群组信息
更新好友信息
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
).dict(),
)
_group_matcher = on_alconna(
Alconna(
"更新群组信息",
),
permission=SUPERUSER,
rule=to_me(),
priority=1,
block=True,
)
_friend_matcher = on_alconna(
Alconna(
"更新好友信息",
),
permission=SUPERUSER,
rule=to_me(),
priority=1,
block=True,
)
@_group_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
):
try:
num = await FgUpdateManage.update_group(bot, session.platform)
logger.info(
f"更新群聊信息完成,共更新了 {num} 个群组的信息!",
arparma.header_result,
session=session,
)
await Text(f"成功更新了 {num} 个群组的信息").send()
except Exception as e:
logger.error(
"更新群组信息发生错误", arparma.header_result, session=session, e=e
)
await Text("其他未知错误...").send()
@_friend_matcher.handle()
async def _(
bot: Bot,
session: EventSession,
arparma: Arparma,
):
try:
num = await FgUpdateManage.update_friend(bot, session.platform)
logger.info(
f"更新好友信息完成,共更新了 {num} 个好友的信息!",
arparma.header_result,
session=session,
)
await Text(f"成功更新了 {num} 个好友的信息").send()
except Exception as e:
logger.error(
"更新好友信息发生错误", arparma.header_result, session=session, e=e
)
await Text("其他未知错误...").send()
@@ -0,0 +1,156 @@
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 zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
class FgUpdateManage:
@classmethod
async def update_group(cls, bot: Bot, platform: str) -> int:
"""更新群组信息
参数:
bot: Bot
platform: 平台
返回:
int: 更新个数
"""
create_list = []
if group_list := await cls.__get_group_list(bot):
exists_group_list = await GroupConsole.all().values_list(
"group_id", "channel_id"
)
for group in group_list:
group.platform = platform
if (group.group_id, group.channel_id) not in exists_group_list:
create_list.append(group)
logger.debug(
"群聊信息更新成功",
"更新群信息",
target=f"{group.group_id}:{group.channel_id}",
)
if create_list:
await GroupConsole.bulk_create(create_list, 10)
return len(create_list)
@classmethod
async def __get_group_list(cls, bot: Bot) -> list[GroupConsole]:
"""获取群组列表
参数:
bot: Bot
返回:
list[GroupConsole]: 群组列表
"""
if isinstance(bot, v11Bot):
group_list = await bot.get_group_list()
return [
GroupConsole(
group_id=str(g["group_id"]),
group_name=g["group_name"],
max_member_count=g["max_member_count"],
member_count=g["member_count"],
)
for g in group_list
]
if isinstance(bot, v12Bot):
group_list = await bot.get_group_list()
return [
GroupConsole(
group_id=g.group_id, # type: ignore
user_name=g.group_name, # type: ignore
)
for g in group_list
]
if isinstance(bot, DodoBot):
island_list = await bot.get_island_list()
source_id_list = [
(g.island_source_id, g.island_name)
for g in island_list
if g.island_source_id
]
group_list = []
for id, name in source_id_list:
channel_list = await bot.get_channel_list(island_source_id=id)
group_list.append(GroupConsole(group_id=id, group_name=name))
group_list += [
GroupConsole(
group_id=id, group_name=c.channel_name, channel_id=c.channel_id
)
for c in channel_list
]
return group_list
if isinstance(bot, KaiheilaBot):
# TODO: kaiheila群组列表
pass
if isinstance(bot, DiscordBot):
# TODO: discord群组列表
pass
return []
@classmethod
async def update_friend(cls, bot: Bot, platform: str) -> int:
"""更新好友信息
参数:
bot: Bot
platform: 平台
返回:
int: 更新个数
"""
create_list = []
if friend_list := await cls.__get_friend_list(bot):
user_id_list = await FriendUser.all().values_list("user_id", flat=True)
for friend in friend_list:
friend.platform = platform
if friend.user_id not in user_id_list:
create_list.append(friend)
if create_list:
await FriendUser.bulk_create(create_list, 10)
return len(create_list)
@classmethod
async def __get_friend_list(cls, bot: Bot) -> list[FriendUser]:
"""获取好友列表
参数:
bot: Bot
返回:
list[FriendUser]: 好友列表
"""
if isinstance(bot, v11Bot):
friend_list = await bot.get_friend_list()
return [
FriendUser(user_id=str(f["user_id"]), user_name=f["nickname"])
for f in friend_list
]
if isinstance(bot, v12Bot):
friend_list = await bot.get_friend_list()
return [
FriendUser(
user_id=f.user_id, # type: ignore
user_name=f.user_displayname or f.user_remark or f.user_name, # type: ignore
)
for f in friend_list
]
if isinstance(bot, DodoBot):
# TODO: dodo好友列表
pass
if isinstance(bot, KaiheilaBot):
# TODO: kaiheila好友列表
pass
if isinstance(bot, DiscordBot):
# TODO: discord好友列表
pass
return []