mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-06 04:10:02 +08:00
feat✨: 更新内置插件
This commit is contained in:
@@ -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()))
|
||||
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -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 "没有查找到这个功能噢..."
|
||||
@@ -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
|
||||
@@ -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()))
|
||||
@@ -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
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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}")
|
||||
@@ -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("每日晚安发送...")
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)")
|
||||
@@ -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,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 []
|
||||
Reference in New Issue
Block a user