diff --git a/README.md b/README.md index 748fe9ec..4c2bc7bc 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ _基于 [Nonebot2](https://github.com/nonebot/nonebot2) 和 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 的 qq 机器人_ -![Nonebot2](https://img.shields.io/badge/Nonebot2-Release_v2.0.0_beta.3-brightgreen) -![go-cqhttp](https://img.shields.io/badge/go--cqhttp-v1.0.0_rc1-brightgreen) +![Nonebot2](https://img.shields.io/badge/Nonebot2-Release_v2.0.0_beta.5-brightgreen) +![go-cqhttp](https://img.shields.io/badge/go--cqhttp-v1.0.0_rc3-brightgreen)
![GitHub](https://img.shields.io/github/license/Ailitonia/omega-miya) ![Python](https://img.shields.io/badge/Python-3.10+-blue) @@ -18,11 +18,11 @@ _基于 [Nonebot2](https://github.com/nonebot/nonebot2) 和 [go-cqhttp](https:// ## 当前适配 nonebot2 版本 -[Nonebot2 Release v2.0.0-beta.3](https://github.com/nonebot/nonebot2/releases/tag/v2.0.0-beta.3) +[Nonebot2 Release v2.0.0-beta.5](https://github.com/nonebot/nonebot2/releases/tag/v2.0.0-beta.5) ## 当前适配 go-cqhttp 版本 -[go-cqhttp v1.0.0-rc1](https://github.com/Mrs4s/go-cqhttp/releases/tag/v1.0.0-rc1) +[go-cqhttp v1.0.0-rc3](https://github.com/Mrs4s/go-cqhttp/releases/tag/v1.0.0-rc3) ## 功能 & 特点 diff --git a/omega_miya/database/schemas/pixiv_artwork.py b/omega_miya/database/schemas/pixiv_artwork.py index 3ce83d13..a8a70bf6 100644 --- a/omega_miya/database/schemas/pixiv_artwork.py +++ b/omega_miya/database/schemas/pixiv_artwork.py @@ -286,9 +286,18 @@ async def query_all_pid_by_uid(cls, uid: int) -> IntListResult: return IntListResult(error=False, info='Success', result=(await cls._query_custom_all(stmt=stmt))) @classmethod - async def count_all(cls, keywords: Optional[List[str]] = None) -> PixivArtworkCountResult: + async def count_all( + cls, + keywords: Optional[List[str]] = None, + *, + classified: Optional[int] = 1 + ) -> PixivArtworkCountResult: try: all_stmt = select(func.count(cls.orm_model.id)).with_for_update(read=True) + + if classified is not None: + all_stmt = all_stmt.where(cls.orm_model.classified == classified) + if keywords: for keyword in keywords: all_stmt = all_stmt.where(or_( diff --git a/omega_miya/onebot_api/gocq/__init__.py b/omega_miya/onebot_api/gocq/__init__.py index ca2afe92..45419083 100644 --- a/omega_miya/onebot_api/gocq/__init__.py +++ b/omega_miya/onebot_api/gocq/__init__.py @@ -34,7 +34,7 @@ class GoCqhttpBot(BaseOnebotApi): """go-cqhttp api - 适配版本: go-cqhttp v1.0.0-rc2 + 适配版本: go-cqhttp v1.0.0-rc3 """ def __init__(self, bot: Bot): @@ -54,30 +54,35 @@ async def connecting_db_upgrade(self) -> None: # 更新群组相关信息 groups_result = await self.get_group_list() group_upgrade_tasks = [ - InternalBotGroup(bot_id=self.self_id, parent_id=self.self_id, entity_id=x.group_id).add_only( + InternalBotGroup(bot_id=self.self_id, parent_id=self.self_id, entity_id=x.group_id).add_upgrade( parent_entity_name=bot_login_info.nickname, entity_name=x.group_name, - entity_info=x.group_memo, - related_entity_name=x.group_name + related_entity_name=x.group_name, + parent_entity_info=bot_login_info.nickname, + entity_info=x.group_memo ) for x in groups_result] # 更新用户相关信息 users_result = await self.get_friend_list() user_upgrade_tasks = [ - InternalBotUser(bot_id=self.self_id, parent_id=self.self_id, entity_id=x.user_id).add_only( + InternalBotUser(bot_id=self.self_id, parent_id=self.self_id, entity_id=x.user_id).add_upgrade( parent_entity_name=bot_login_info.nickname, entity_name=x.nickname, - related_entity_name=x.remark + related_entity_name=x.remark, + parent_entity_info=bot_login_info.nickname, + entity_info=x.remark ) for x in users_result] # 更新频道相关信息 guild_profile = await self.get_guild_service_profile() guild_data = await self.get_guild_list() guild_upgrade_tasks = [ - InternalBotGuild(bot_id=self.self_id, parent_id=guild_profile.tiny_id, entity_id=x.guild_id).add_only( + InternalBotGuild(bot_id=self.self_id, parent_id=guild_profile.tiny_id, entity_id=x.guild_id).add_upgrade( parent_entity_name=guild_profile.nickname, entity_name=x.guild_name, - related_entity_name=x.guild_name + related_entity_name=x.guild_name, + parent_entity_info=guild_profile.nickname, + entity_info=x.guild_name ) for x in guild_data] @@ -85,10 +90,14 @@ async def connecting_db_upgrade(self) -> None: for guild in guild_data: channel_data = await self.get_guild_channel_list(guild_id=guild.guild_id) channel_upgrade_tasks.extend([ - InternalGuildChannel(bot_id=self.self_id, parent_id=x.owner_guild_id, entity_id=x.channel_id).add_only( + InternalGuildChannel( + bot_id=self.self_id, parent_id=x.owner_guild_id, entity_id=x.channel_id + ).add_upgrade( parent_entity_name=guild.guild_name, entity_name=x.channel_name, - related_entity_name=x.channel_name + related_entity_name=x.channel_name, + parent_entity_info=guild.guild_name, + entity_info=x.channel_name ) for x in channel_data ]) @@ -486,6 +495,17 @@ async def upload_group_file(self, group_id: int | str, file: str, name: str, fol """ return await self.bot.call_api('upload_group_file', group_id=group_id, file=file, name=name, folder=folder) + async def upload_private_file(self, user_id: int | str, file: str, name: str) -> None: + """上传私聊文件 + + 只能上传本地文件, 需要上传 http 文件的话请先调用 download_file API下载 + + :param user_id: 好友qq + :param file: 本地文件路径 + :param name: 储存名称 + """ + return await self.bot.call_api('upload_private_file', user_id=user_id, file=file, name=name) + async def get_group_file_system_info(self, group_id: int | str) -> GroupFileSystemInfo: system_info_result = await self.bot.call_api('get_group_file_system_info', group_id=group_id) return GroupFileSystemInfo.parse_obj(system_info_result) diff --git a/omega_miya/plugins/announce/__init__.py b/omega_miya/plugins/announce/__init__.py index 0bcbf11e..49dc630d 100644 --- a/omega_miya/plugins/announce/__init__.py +++ b/omega_miya/plugins/announce/__init__.py @@ -1,4 +1,5 @@ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.rule import to_me from nonebot.permission import SUPERUSER from nonebot.typing import T_State @@ -12,13 +13,13 @@ from omega_miya.utils.process_utils import run_async_catching_exception, semaphore_gather -# Custom plugin usage text -__plugin_custom_name__ = '公告' -__plugin_usage__ = r'''【公告插件】 -快速批量向启用了bot的群组发送通知公告 - -用法: -/公告 [公告内容]''' +__plugin_meta__ = PluginMetadata( + name="公告", + description="【公告插件】\n" + "快速批量向启用了bot的群组发送通知公告", + usage="/公告 [公告内容]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/auto_group_sign/__init__.py b/omega_miya/plugins/auto_group_sign/__init__.py new file mode 100644 index 00000000..e62effba --- /dev/null +++ b/omega_miya/plugins/auto_group_sign/__init__.py @@ -0,0 +1,75 @@ +""" +@Author : Ailitonia +@Date : 2022/06/27 20:48 +@FileName : auto_group_sign.py +@Project : nonebot2_miya +@Description : 自动群打卡 (go-cqhttp v1.0.0-rc3 以上版本可用) +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot import get_driver +from nonebot.log import logger +from nonebot.plugin import PluginMetadata +from nonebot.adapters import Bot +from pydantic import BaseModel + +from omega_miya.onebot_api import GoCqhttpBot +from omega_miya.utils.apscheduler import scheduler +from omega_miya.utils.process_utils import semaphore_gather + + +__plugin_meta__ = PluginMetadata( + name="自动群打卡", + description="【自动群打卡插件】\n" + "让机器人参与抢群打卡每日第一", + usage="由管理员配置, 无命令用法", + extra={"author": "Ailitonia"}, +) + + +class AutoGroupSignConfig(BaseModel): + """自动群打卡插件配置""" + # 启用自动群打卡 + enable_auto_group_sign: bool = False + + class Config: + extra = "ignore" + + +_plugin_config = AutoGroupSignConfig.parse_obj(get_driver().config) + + +async def _bot_group_sign(bot: Bot): + gocq_bot = GoCqhttpBot(bot=bot) + tasks = [gocq_bot.send_group_sign(group_id=group_result.group_id) for group_result in await gocq_bot.get_group_list()] + await semaphore_gather(tasks=tasks, semaphore_num=16) + + +async def _sign_main() -> None: + logger.debug('AutoGroupSign | Starting sign all groups') + tasks = [_bot_group_sign(bot=bot) for _, bot in get_driver().bots.items()] + await semaphore_gather(tasks=tasks, semaphore_num=8) + logger.debug('AutoGroupSign | Sign tasks completed') + + +if _plugin_config.enable_auto_group_sign: + scheduler.add_job( + _sign_main, + 'cron', + # year=None, + # month=None, + # day='*/1', + # week=None, + # day_of_week=None, + hour='0', + minute='0', + second='0', + # start_date=None, + # end_date=None, + # timezone=None, + id='auto_group_sign', + coalesce=True, + misfire_grace_time=30 + ) + logger.opt(colors=True).success('AutoGroupSign | 自动群打卡已启用') diff --git a/omega_miya/plugins/bilibili_dynamic_monitor/__init__.py b/omega_miya/plugins/bilibili_dynamic_monitor/__init__.py index c087deb8..386adbb6 100644 --- a/omega_miya/plugins/bilibili_dynamic_monitor/__init__.py +++ b/omega_miya/plugins/bilibili_dynamic_monitor/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -29,16 +30,16 @@ from .monitor import scheduler -# Custom plugin usage text -__plugin_custom_name__ = 'B站动态订阅' -__plugin_usage__ = r'''【B站动态订阅】 -订阅并跟踪Bilibili用户动态更新 - -用法: -仅限私聊或群聊中群管理员使用: -/B站动态订阅 [UID] -/B站动态取消订阅 [UID] -/B站动态订阅列表''' +__plugin_meta__ = PluginMetadata( + name="B站动态订阅", + description="【B站动态订阅插件】\n" + "订阅并跟踪Bilibili用户动态更新", + usage="仅限私聊或群聊中群管理员使用:\n" + "/B站动态订阅 [UID]\n" + "/B站动态取消订阅 [UID]\n" + "/B站动态订阅列表", + extra={"author": "Ailitonia"}, +) add_dynamic_sub = on_command( diff --git a/omega_miya/plugins/bilibili_live_monitor/__init__.py b/omega_miya/plugins/bilibili_live_monitor/__init__.py index 390ad504..55602f3f 100644 --- a/omega_miya/plugins/bilibili_live_monitor/__init__.py +++ b/omega_miya/plugins/bilibili_live_monitor/__init__.py @@ -1,4 +1,15 @@ -from nonebot import on_command, logger +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : bilibili_live_monitor.py +@Project : nonebot2_miya +@Description : Bilibili 直播间订阅 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -18,17 +29,17 @@ from .utils import add_bili_live_room_sub, delete_bili_live_room_sub, query_subscribed_bili_live_room_sub_source -# Custom plugin usage text -__plugin_custom_name__ = 'B站直播间订阅' -__plugin_usage__ = r'''【B站直播间订阅】 -订阅并监控Bilibili直播间状态 -提供开播、下播、直播间换标题提醒 - -用法: -仅限私聊或群聊中群管理员使用: -/B站直播间订阅 [RoomID] -/B站直播间取消订阅 [RoomID] -/B站直播间订阅列表''' +__plugin_meta__ = PluginMetadata( + name="B站直播间订阅", + description="【B站直播间订阅插件】\n" + "订阅并监控Bilibili直播间状态\n" + "提供开播、下播、直播间换标题提醒", + usage="仅限私聊或群聊中群管理员使用:\n" + "/B站直播间订阅 [RoomID]\n" + "/B站直播间取消订阅 [RoomID]\n" + "/B站直播间订阅列表", + extra={"author": "Ailitonia"}, +) add_live_sub = on_command( diff --git a/omega_miya/plugins/bilibili_live_monitor/config.py b/omega_miya/plugins/bilibili_live_monitor/config.py new file mode 100644 index 00000000..c35e081b --- /dev/null +++ b/omega_miya/plugins/bilibili_live_monitor/config.py @@ -0,0 +1,35 @@ +""" +@Author : Ailitonia +@Date : 2022/07/30 21:31 +@FileName : config.py +@Project : nonebot2_miya +@Description : Bilibili 直播间订阅插件配置 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot import get_driver, logger +from pydantic import BaseModel, ValidationError + + +class BilibiliLiveMonitorPluginConfig(BaseModel): + """BilibiliLiveMonitor 插件配置""" + + # 发送消息通知时尝试@全体 + bilibili_live_monitor_enable_group_at_all_notice: bool = False + + class Config: + extra = "ignore" + + +try: + bilibili_live_monitor_plugin_config = BilibiliLiveMonitorPluginConfig.parse_obj(get_driver().config) +except ValidationError as e: + import sys + logger.opt(colors=True).critical(f'Bilibili 直播间订阅插件配置格式验证失败, 错误信息:\n{e}') + sys.exit(f'Bilibili 直播间订阅插件配置格式验证失败, {e}') + + +__all__ = [ + 'bilibili_live_monitor_plugin_config' +] diff --git a/omega_miya/plugins/bilibili_live_monitor/utils.py b/omega_miya/plugins/bilibili_live_monitor/utils.py index 16ff25ae..f4d7711b 100644 --- a/omega_miya/plugins/bilibili_live_monitor/utils.py +++ b/omega_miya/plugins/bilibili_live_monitor/utils.py @@ -25,6 +25,7 @@ from omega_miya.utils.process_utils import run_async_catching_exception, semaphore_gather from omega_miya.utils.message_tools import MessageSender +from .config import bilibili_live_monitor_plugin_config as plugin_config from .model import (BilibiliLiveRoomStatus, BilibiliLiveRoomTitleChange, BilibiliLiveRoomStartLiving, BilibiliLiveRoomStartLivingWithUpdateTitle, BilibiliLiveRoomStopLiving, BilibiliLiveRoomStopLivingWithPlaylist, BilibiliLiveRoomStatusUpdate) @@ -164,7 +165,7 @@ async def _get_live_room_update_message( update_data: BilibiliLiveRoomStatusUpdate ) -> str | Message | None: """处理直播间更新为消息""" - send_message = f'【bilibili直播间】\n' + send_message = '【bilibili直播间】\n' user_name = _LIVE_STATUS.get(live_room_data.uid).live_user_name need_url = False @@ -206,6 +207,17 @@ async def _msg_sender(entity: BaseInternalEntity, message: str | Message) -> int """向 entity 发送消息""" try: msg_sender = MessageSender.init_from_bot_id(bot_id=entity.bot_id) + + # 通知群组时检查能不能@全体成员 + if plugin_config.bilibili_live_monitor_enable_group_at_all_notice and entity.relation_type == 'bot_group': + at_all_remain = await run_async_catching_exception(msg_sender.bot.get_group_at_all_remain)( + group_id=entity.entity_id) + if (not isinstance(at_all_remain, Exception) + and at_all_remain.can_at_all + and at_all_remain.remain_at_all_count_for_group + and at_all_remain.remain_at_all_count_for_uin): + message = MessageSegment.at(user_id='all') + message + sent_msg_id = await msg_sender.send_internal_entity_msg(entity=entity, message=message) except KeyError: logger.debug(f'BilibiliLiveRoomMonitor | Bot({entity.bot_id}) not online, ' diff --git a/omega_miya/plugins/calculator/__init__.py b/omega_miya/plugins/calculator/__init__.py index bea87452..f1b8b097 100644 --- a/omega_miya/plugins/calculator/__init__.py +++ b/omega_miya/plugins/calculator/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.permission import GROUP, PRIVATE_FRIEND from nonebot.adapters.onebot.v11.message import Message @@ -19,13 +20,13 @@ from .calculator import Calculator, CalculateException -# Custom plugin usage text -__plugin_custom_name__ = '计算器' -__plugin_usage__ = r'''【简易计算器】 -只能计算加减乘除和乘方! - -用法: -/计算 [算式]''' +__plugin_meta__ = PluginMetadata( + name="计算器", + description="【简易计算器插件】\n" + "只能计算加减乘除和乘方!", + usage="/计算 [算式]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/draw/__init__.py b/omega_miya/plugins/draw/__init__.py index 7c4a4a83..ee063ae2 100644 --- a/omega_miya/plugins/draw/__init__.py +++ b/omega_miya/plugins/draw/__init__.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from nonebot import on_command +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.message import Message from nonebot.adapters.onebot.v11.event import MessageEvent @@ -20,15 +20,16 @@ from .deck import draw, get_deck -# Custom plugin usage text -__plugin_custom_name__ = '抽卡' -__plugin_usage__ = r'''【抽卡】 -模拟各种抽卡 -没有保底的啦! -不要上头啊喂! -用法 -/抽卡 [卡组]''' +__plugin_meta__ = PluginMetadata( + name="抽卡", + description="【抽卡模拟插件】\n" + "模拟各种抽卡\n" + "没有保底的啦!\n" + "不要上头啊喂!", + usage="/抽卡 [卡组]", + extra={"author": "Ailitonia"}, +) draw_deck = on_command( diff --git a/omega_miya/plugins/draw/deck/arknights.py b/omega_miya/plugins/draw/deck/arknights.py index 639156a4..52d88317 100644 --- a/omega_miya/plugins/draw/deck/arknights.py +++ b/omega_miya/plugins/draw/deck/arknights.py @@ -26,24 +26,33 @@ class UpEvent(BaseModel): UpEvent( star=6, operator=[ - Operator(name='归溟幽灵鲨/Irene', star=6, limited=True, recruit_only=False, event_only=False, - special_only=False), - Operator(name='艾丽妮/Irene', star=6, limited=False, recruit_only=False, event_only=False, + Operator(name='多萝西/Dorothy', star=6, limited=False, recruit_only=False, event_only=False, special_only=False) ], - zoom=0.7 + zoom=0.5 ), UpEvent( star=5, operator=[ - Operator(name='掠风/Windflit', star=5, limited=False, recruit_only=False, event_only=False, - special_only=False) + Operator(name='承曦格雷伊/Greyy the Lightningbearer', star=5, limited=False, recruit_only=False, + event_only=False, special_only=False), + Operator(name='白面鸮/Ptilopsis', star=5, limited=False, recruit_only=False, event_only=False, + special_only=False), ], zoom=0.5 ) ] ALL_OPERATOR: list[Operator] = [ + Operator(name='多萝西/Dorothy', star=6, limited=False, recruit_only=False, event_only=False, special_only=False), + Operator(name='承曦格雷伊/Greyy the Lightningbearer', star=5, limited=False, recruit_only=False, event_only=False, + special_only=False), + Operator(name='星源/Astgenne', star=5, limited=False, recruit_only=False, event_only=True, special_only=False), + Operator(name='黑键/Ebenholz', star=6, limited=False, recruit_only=False, event_only=False, special_only=False), + Operator(name='濯尘芙蓉/Hibiscus the Purifier', star=5, limited=False, recruit_only=False, event_only=False, + special_only=False), + Operator(name='车尔尼/Czerny', star=5, limited=False, recruit_only=False, event_only=True, special_only=False), + Operator(name='埃拉托/Erato', star=5, limited=False, recruit_only=False, event_only=True, special_only=False), Operator(name='归溟幽灵鲨/Irene', star=6, limited=True, recruit_only=False, event_only=False, special_only=False), Operator(name='艾丽妮/Irene', star=6, limited=False, recruit_only=False, event_only=False, special_only=False), Operator(name='流明/Lumen', star=6, limited=False, recruit_only=False, event_only=True, special_only=False), diff --git a/omega_miya/plugins/http_cat/__init__.py b/omega_miya/plugins/http_cat/__init__.py index 29436f0d..34dfff7b 100644 --- a/omega_miya/plugins/http_cat/__init__.py +++ b/omega_miya/plugins/http_cat/__init__.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from nonebot import on_command +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.message import Message, MessageSegment from nonebot.adapters.onebot.v11.permission import GROUP, PRIVATE_FRIEND @@ -20,13 +20,13 @@ from .data_source import get_http_cat -# Custom plugin usage text -__plugin_custom_name__ = 'HttpCat' -__plugin_usage__ = r'''【HttpCat】 -用猫猫表示的http状态码 - -用法: -/HttpCat ''' +__plugin_meta__ = PluginMetadata( + name="HttpCat", + description="【HttpCat插件】\n" + "用猫猫表示的http状态码", + usage="/HttpCat ", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/image_searcher/__init__.py b/omega_miya/plugins/image_searcher/__init__.py index 646db91f..aac6a828 100644 --- a/omega_miya/plugins/image_searcher/__init__.py +++ b/omega_miya/plugins/image_searcher/__init__.py @@ -8,11 +8,12 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot -from nonebot.adapters.onebot.v11.event import MessageEvent, GroupMessageEvent +from nonebot.adapters.onebot.v11.event import MessageEvent from nonebot.adapters.onebot.v11.message import Message from nonebot.adapters.onebot.v11.permission import GROUP, PRIVATE_FRIEND from nonebot.params import Depends, RawCommand, CommandArg, Arg, ArgStr @@ -25,14 +26,14 @@ from omega_miya.onebot_api import GoCqhttpBot -# Custom plugin usage text -__plugin_custom_name__ = '识图搜番' -__plugin_usage__ = r'''【识图搜番助手】 -使用 SauceNAO/iqdb/ascii2d/trace.moe 识别各类图片、插画、番剧 - -用法: -/识图 -/搜番''' +__plugin_meta__ = PluginMetadata( + name="识图搜番", + description="【识图搜番插件】\n" + "使用 SauceNAO/iqdb/ascii2d/trace.moe 识别各类图片、插画、番剧", + usage="/识图 [图片]\n" + "/搜番 [图片]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 @@ -105,8 +106,6 @@ async def handle_sticker(bot: Bot, matcher: Matcher, event: MessageEvent, state: elif not searching_result: await matcher.finish('没有找到相似度足够高的图片QAQ') else: - if isinstance(event, GroupMessageEvent): - await MessageSender(bot=bot).send_group_node_custom_and_recall(group_id=event.group_id, recall_time=60, - message_list=searching_result) - else: - [await matcher.finish(x) for x in searching_result] + await MessageSender(bot=bot).send_node_custom_and_recall( + event=event, recall_time=90, message_list=searching_result + ) diff --git a/omega_miya/plugins/maybe/__init__.py b/omega_miya/plugins/maybe/__init__.py index 0ddd1531..e545fc66 100644 --- a/omega_miya/plugins/maybe/__init__.py +++ b/omega_miya/plugins/maybe/__init__.py @@ -1,5 +1,15 @@ +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : maybe.py +@Project : nonebot2_miya +@Description : 求签 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + import datetime -from nonebot import on_command +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.message import Message from nonebot.adapters.onebot.v11.event import MessageEvent @@ -12,15 +22,15 @@ from .utils import query_maybe -# Custom plugin usage text -__plugin_custom_name__ = '求签' -__plugin_usage__ = r'''【求签】 -求签, 求运势, 包括且不限于抽卡、吃饭、睡懒觉、DD -每个人每天求同一个东西的结果是一样的啦! -不要不信邪重新抽啦! - -用法: -/求签 [所求之事]''' +__plugin_meta__ = PluginMetadata( + name="求签", + description="【求签插件】\n" + "求签, 求运势, 包括且不限于抽卡、吃饭、睡懒觉、DD\n" + "每个人每天求同一个东西的结果是一样的啦!\n" + "不要不信邪重新抽啦!", + usage="/求签 [所求之事]", + extra={"author": "Ailitonia"}, +) maybe = on_command( diff --git a/omega_miya/plugins/mirage_tank/__init__.py b/omega_miya/plugins/mirage_tank/__init__.py index 4e4824e5..66e4efb5 100644 --- a/omega_miya/plugins/mirage_tank/__init__.py +++ b/omega_miya/plugins/mirage_tank/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.event import MessageEvent @@ -23,15 +24,14 @@ from .utils import simple_white, simple_black, complex_gray, complex_color, complex_difference -# Custom plugin usage text -__plugin_custom_name__ = '幻影坦克' -__plugin_usage__ = rf'''【幻影坦克图片生成工具】 -制作幻影坦克图片 - -/幻影坦克 [模式] [图片] - -合成模式可选: "白底", "黑底", "灰度混合", "彩色混合", "差分" -''' +__plugin_meta__ = PluginMetadata( + name="幻影坦克", + description="【幻影坦克图片生成插件】\n" + "制作幻影坦克图片", + usage="/幻影坦克 [模式] [图片]\n\n" + '合成模式可选: "白底", "黑底", "灰度混合", "彩色混合", "差分"', + extra={"author": "Ailitonia"}, +) mirage_tank = on_command( diff --git a/omega_miya/plugins/miya_button/__init__.py b/omega_miya/plugins/miya_button/__init__.py index c98317af..a965b469 100644 --- a/omega_miya/plugins/miya_button/__init__.py +++ b/omega_miya/plugins/miya_button/__init__.py @@ -9,7 +9,8 @@ """ import re -from nonebot import on_command, on_regex, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_regex, PluginMetadata from nonebot.rule import to_me from nonebot.typing import T_State from nonebot.matcher import Matcher @@ -26,13 +27,13 @@ get_voice_resource_name, set_voice_resource) -# Custom plugin usage text -__plugin_custom_name__ = '猫按钮' -__plugin_usage__ = r'''【猫按钮】 -发出可爱的猫叫 - -用法: -@bot 喵一个''' +__plugin_meta__ = PluginMetadata( + name="猫按钮", + description="【猫按钮插件】\n" + "发出可爱的猫叫", + usage="@bot 喵一个", + extra={"author": "Ailitonia"}, +) button_pattern = r'^(.*?)喵一个$' diff --git a/omega_miya/plugins/moe/__init__.py b/omega_miya/plugins/moe/__init__.py index 2e4f384c..7a4b4194 100644 --- a/omega_miya/plugins/moe/__init__.py +++ b/omega_miya/plugins/moe/__init__.py @@ -10,7 +10,8 @@ import re from copy import deepcopy -from nonebot import on_shell_command, on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_shell_command, PluginMetadata from nonebot.rule import to_me from nonebot.typing import T_State from nonebot.matcher import Matcher @@ -35,25 +36,24 @@ get_query_argument_parser, parse_from_query_parser) -# Custom plugin usage text -__plugin_custom_name__ = '来点萌图' -__plugin_usage__ = r'''【来点萌图】 -随机萌图和随机涩图 -不可以随意涩涩! - -用法: -/来点萌图 [关键词, ...] -/来点涩图 [关键词, ...] - -可用参数: -'-s', '--nsfw-tag': 指定nsfw_tag -'-n', '--num': 指定获取的图片数量 -'-nf', '--no-flash': 强制不使用闪照发送图片 - -仅限管理员使用: -/图库统计 -/图库查询 [关键词, ...] -/导入图库''' +__plugin_meta__ = PluginMetadata( + name="来点萌图", + description="【来点萌图插件】\n" + "随机萌图和随机涩图\n" + "不可以随意涩涩!", + usage="/来点萌图 [关键词, ...]\n" + "/来点涩图 [关键词, ...]\n\n" + "可用参数:\n" + "'-s', '--nsfw-tag': 指定nsfw_tag\n" + "'-n', '--num': 指定获取的图片数量\n" + "'-nf', '--no-flash': 强制不使用闪照发送图片\n\n" + "仅限管理员使用:\n" + "/图库统计\n" + "/图库查询 [关键词, ...]\n" + "/导入图库", + config=moe_plugin_config.__class__, + extra={"author": "Ailitonia"}, +) _ALLOW_R18_NODE = moe_plugin_config.moe_plugin_allow_r18_node @@ -118,13 +118,16 @@ async def handle_parse_success(bot: Bot, event: MessageEvent, matcher: Matcher, await matcher.finish('找不到涩图QAQ') await matcher.send('稍等, 正在下载图片~') - image_message_tasks = [prepare_send_image(pid=x.pid, enable_flash_mode=(not args.no_flash)) for x in artworks] + + flash_mode = True if moe_plugin_config.moe_plugin_enforce_setu_enable_flash_mode else not args.no_flash + + image_message_tasks = [prepare_send_image(pid=x.pid, enable_flash_mode=flash_mode) for x in artworks] message_result = await semaphore_gather(tasks=image_message_tasks, semaphore_num=5, filter_exception=True) send_messages = list(message_result) if not send_messages: await matcher.finish('所有图片都获取失败了QAQ, 可能是网络原因或作品被删除, 请稍后再试') await MessageSender(bot=bot).send_msgs_and_recall(event=event, message_list=send_messages, - recall_time=moe_plugin_config.moe_plugin_auto_recall_time) + recall_time=moe_plugin_config.moe_plugin_setu_auto_recall_time) moe = on_shell_command( @@ -176,13 +179,16 @@ async def handle_parse_success(bot: Bot, event: MessageEvent, matcher: Matcher, await matcher.finish('找不到萌图QAQ') await matcher.send('稍等, 正在下载图片~') - image_message_tasks = [prepare_send_image(pid=x.pid, enable_flash_mode=(not args.no_flash)) for x in artworks] + + flash_mode = False if moe_plugin_config.moe_plugin_enforce_moe_disable_flash_mode else not args.no_flash + + image_message_tasks = [prepare_send_image(pid=x.pid, enable_flash_mode=flash_mode) for x in artworks] message_result = await semaphore_gather(tasks=image_message_tasks, semaphore_num=5, filter_exception=True) send_messages = list(message_result) if not send_messages: await matcher.finish('所有图片都获取失败了QAQ, 可能是网络原因或作品被删除, 请稍后再试') await MessageSender(bot=bot).send_msgs_and_recall(event=event, message_list=send_messages, - recall_time=moe_plugin_config.moe_plugin_auto_recall_time) + recall_time=moe_plugin_config.moe_plugin_moe_auto_recall_time) statistics = on_command( diff --git a/omega_miya/plugins/moe/config.py b/omega_miya/plugins/moe/config.py index b23bb915..98d32334 100644 --- a/omega_miya/plugins/moe/config.py +++ b/omega_miya/plugins/moe/config.py @@ -25,10 +25,18 @@ class MoePluginConfig(BaseModel): moe_plugin_query_image_num: int = 3 # 允许用户通过参数调整的每次查询的图片数量上限 moe_plugin_query_image_limit: int = 10 + # 启用使用闪照模式发送图片 moe_plugin_enable_flash_mode: bool = True - # 默认自动撤回消息时间 - moe_plugin_auto_recall_time: int = 30 + # 强制不使用闪照发送萌图(会覆盖部分 moe_plugin_enable_flash_mode 配置行为) + moe_plugin_enforce_moe_disable_flash_mode: bool = False + # 强制使用闪照发送涩图(会覆盖部分 moe_plugin_enable_flash_mode 配置行为) + moe_plugin_enforce_setu_enable_flash_mode: bool = False + + # 萌图默认自动撤回消息时间(设置 0 为不撤回) + moe_plugin_moe_auto_recall_time: int = 60 + # 涩图默认自动撤回消息时间(设置 0 为不撤回) + moe_plugin_setu_auto_recall_time: int = 30 class Config: extra = "ignore" diff --git a/omega_miya/plugins/nbnhhsh/__init__.py b/omega_miya/plugins/nbnhhsh/__init__.py index 00c9e771..d532f4c5 100644 --- a/omega_miya/plugins/nbnhhsh/__init__.py +++ b/omega_miya/plugins/nbnhhsh/__init__.py @@ -1,4 +1,14 @@ -from nonebot import on_command +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : nbnhhsh.py +@Project : nonebot2_miya +@Description : 能不能好好说话 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.message import Message from nonebot.adapters.onebot.v11.permission import GROUP, PRIVATE_FRIEND @@ -10,13 +20,13 @@ from .data_source import get_guess -# Custom plugin usage text -__plugin_custom_name__ = '好好说话' -__plugin_usage__ = r'''【能不能好好说话?】 -拼音首字母缩写释义 - -用法 -/好好说话 [缩写]''' +__plugin_meta__ = PluginMetadata( + name="好好说话", + description="【能不能好好说话?】\n" + "拼音首字母缩写释义", + usage="/好好说话 [缩写]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/nbnhhsh/data_source.py b/omega_miya/plugins/nbnhhsh/data_source.py index 9d93b106..928bac0b 100644 --- a/omega_miya/plugins/nbnhhsh/data_source.py +++ b/omega_miya/plugins/nbnhhsh/data_source.py @@ -26,6 +26,9 @@ def guess_result(self) -> list[str]: async def _get_guess(guess: str) -> list[GuessResult]: + """从 magiconch API 处获取缩写查询结果""" + # 该 api 当前不支持查询的缩写中有空格 这里去除待查询文本中的空格 + guess = guess.replace(' ', '') payload = {'text': guess} result = await HttpFetcher().post_json(url=_API_URL, json=payload) return parse_obj_as(list[GuessResult], result.result) diff --git a/omega_miya/plugins/nhentai/__init__.py b/omega_miya/plugins/nhentai/__init__.py index bdef4ee4..0f8b7541 100644 --- a/omega_miya/plugins/nhentai/__init__.py +++ b/omega_miya/plugins/nhentai/__init__.py @@ -10,12 +10,13 @@ 要求go-cqhttp v0.9.40以上 """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot -from nonebot.adapters.onebot.v11.event import GroupMessageEvent -from nonebot.adapters.onebot.v11.permission import GROUP +from nonebot.adapters.onebot.v11.event import MessageEvent, GroupMessageEvent +from nonebot.adapters.onebot.v11.permission import GROUP, PRIVATE_FRIEND from nonebot.adapters.onebot.v11.message import Message, MessageSegment from nonebot.params import CommandArg, ArgStr @@ -26,13 +27,14 @@ from omega_miya.web_resource.nhentai import NhentaiGallery -# Custom plugin usage text -__plugin_custom_name__ = 'NHentai' -__plugin_usage__ = r'''【NHentai】 -神秘的插件 - -/nh search [tag] -/nh download [id]''' +__plugin_meta__ = PluginMetadata( + name="NHentai", + description="【NHentai插件】\n" + "神秘的插件", + usage="/nh search [tag]\n" + "/nh download [id]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 @@ -46,7 +48,7 @@ user_cool_down_override=2 ), aliases={'nhentai', 'nh'}, - permission=GROUP, + permission=GROUP | PRIVATE_FRIEND, priority=20, block=True ) @@ -68,7 +70,7 @@ async def handle_parse_operating(state: T_State, cmd_arg: Message = CommandArg() @nhentai.got('operation_arg', prompt='Please enter the search keywords or download gallery id') async def handle_operating( bot: Bot, - event: GroupMessageEvent, + event: MessageEvent, matcher: Matcher, operating: str = ArgStr('operating'), operation_arg: str = ArgStr('operation_arg') @@ -87,21 +89,19 @@ async def handle_operating( await matcher.finish('Invalid operation') -async def handle_search(bot: Bot, event: GroupMessageEvent, matcher: Matcher, keyword: str): +async def handle_search(bot: Bot, event: MessageEvent, matcher: Matcher, keyword: str): await matcher.send('获取搜索结果中, 请稍候') search_tasks = [NhentaiGallery.search_gallery_with_preview(keyword=keyword, page=p) for p in range(1, 6)] search_results = await semaphore_gather(tasks=search_tasks, semaphore_num=2, filter_exception=True) if not search_results: await matcher.finish('没有搜索结果或搜索失败了QAQ, 请稍后再试') else: - send_messages = ['已为你找到了以下结果, 可通过id下载'] - send_messages.extend([MessageSegment.image(x.file_uri) for x in search_results]) - await MessageSender(bot=bot).send_group_node_custom_and_recall(group_id=event.group_id, - message_list=send_messages, - recall_time=60) + message_list = ['已为你找到了以下结果, 可通过id下载'] + message_list.extend([MessageSegment.image(x.file_uri) for x in search_results]) + await MessageSender(bot=bot).send_node_custom_and_recall(event=event, message_list=message_list, recall_time=60) -async def handle_download(bot: Bot, event: GroupMessageEvent, matcher: Matcher, gallery_id: int): +async def handle_download(bot: Bot, event: MessageEvent, matcher: Matcher, gallery_id: int): await matcher.send('下载中, 请稍候') download_result = await run_async_catching_exception(NhentaiGallery(gallery_id=gallery_id).download)() if isinstance(download_result, Exception): @@ -109,9 +109,18 @@ async def handle_download(bot: Bot, event: GroupMessageEvent, matcher: Matcher, await matcher.finish('下载失败QAQ, 请稍后再试') else: await matcher.send(f'下载完成, 密码: {download_result.password},\n正在上传文件, 可能需要一段时间...') - gocq_bot = GoCqhttpBot(bot=bot) - upload_result = await run_async_catching_exception(gocq_bot.upload_group_file)( - group_id=event.group_id, file=download_result.file.resolve_path, name=download_result.file.path.name) + upload_result = await _upload_file( + bot=bot, event=event, file=download_result.file.resolve_path, name=download_result.file.path.name + ) if isinstance(upload_result, Exception): logger.error(f'NHentai | Upload gallery({gallery_id}) to group file failed, {upload_result}') await matcher.finish('上传文件失败QAQ, 请稍后再试') + + +@run_async_catching_exception +async def _upload_file(bot: Bot, event: MessageEvent, file: str, name: str) -> None: + gocq_bot = GoCqhttpBot(bot=bot) + if isinstance(event, GroupMessageEvent): + await gocq_bot.upload_group_file(group_id=event.group_id, file=file, name=name) + else: + await gocq_bot.upload_private_file(user_id=event.user_id, file=file, name=name) diff --git a/omega_miya/plugins/omega_anti_flash/__init__.py b/omega_miya/plugins/omega_anti_flash/__init__.py index 2d3bba2a..f1b08b0d 100644 --- a/omega_miya/plugins/omega_anti_flash/__init__.py +++ b/omega_miya/plugins/omega_anti_flash/__init__.py @@ -1,5 +1,16 @@ +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : omega_anti_flash.py +@Project : nonebot2_miya +@Description : Omega 反闪照插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + from typing import Literal -from nonebot import on_command, on_message, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_message, PluginMetadata from nonebot.permission import SUPERUSER from nonebot.typing import T_State from nonebot.matcher import Matcher @@ -15,13 +26,14 @@ from omega_miya.utils.rule import group_has_permission_node -# Custom plugin usage text -__plugin_custom_name__ = '反闪照' -__plugin_usage__ = r'''【AntiFlash 反闪照插件】 -检测闪照并提取原图 - -用法: -/AntiFlash ''' +__plugin_meta__ = PluginMetadata( + name="反闪照", + description="【AntiFlash 反闪照插件】\n" + "检测闪照并提取原图", + usage="仅限群聊中群管理员使用:\n" + "/AntiFlash ", + extra={"author": "Ailitonia"}, +) _ANTI_FLASH_CUSTOM_MODULE_NAME: Literal['Omega.AntiFlash'] = 'Omega.AntiFlash' diff --git a/omega_miya/plugins/omega_anti_recall/__init__.py b/omega_miya/plugins/omega_anti_recall/__init__.py index a84ed29f..6c7820fd 100644 --- a/omega_miya/plugins/omega_anti_recall/__init__.py +++ b/omega_miya/plugins/omega_anti_recall/__init__.py @@ -1,6 +1,17 @@ +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : omega_anti_recall.py +@Project : nonebot2_miya +@Description : Omega 反撤回插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + from datetime import datetime from typing import Literal -from nonebot import on_command, on_notice, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_notice, PluginMetadata from nonebot.permission import SUPERUSER from nonebot.typing import T_State from nonebot.matcher import Matcher @@ -18,13 +29,14 @@ from omega_miya.utils.message_tools import MessageTools -# Custom plugin usage text -__plugin_custom_name__ = '反撤回' -__plugin_usage__ = r'''【AntiRecall 反撤回插件】 -检测消息撤回并提取原消息 - -用法: -/AntiRecall ''' +__plugin_meta__ = PluginMetadata( + name="反撤回", + description="【AntiRecall 反撤回插件】\n" + "检测消息撤回并提取原消息", + usage="仅限群聊中群管理员使用:\n" + "/AntiRecall ", + extra={"author": "Ailitonia"}, +) _ANTI_RECALL_CUSTOM_MODULE_NAME: Literal['Omega.AntiRecall'] = 'Omega.AntiRecall' diff --git a/omega_miya/plugins/omega_auth_manager/__init__.py b/omega_miya/plugins/omega_auth_manager/__init__.py index 6dd4226e..1b3465c4 100644 --- a/omega_miya/plugins/omega_auth_manager/__init__.py +++ b/omega_miya/plugins/omega_auth_manager/__init__.py @@ -1,4 +1,15 @@ -from nonebot import on_command, get_plugin, get_loaded_plugins, logger +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : omega_auth_manager.py +@Project : nonebot2_miya +@Description : Omega 授权管理插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.log import logger +from nonebot.plugin import get_plugin, get_loaded_plugins, on_command, PluginMetadata from nonebot.rule import to_me from nonebot.permission import SUPERUSER from nonebot.typing import T_State @@ -22,23 +33,22 @@ ) -# Custom plugin usage text -__plugin_custom_name__ = '授权管理' -__plugin_usage__ = r'''【OmegaAuth 授权管理插件】 -插件特殊权限授权管理 -仅限管理员使用 - -用法: -/OmegaAuth [授权操作] [授权对象ID] [插件名称] [权限节点] -/OmegaAuth [allow|deny] [插件名称] [权限节点] -/OmegaAuth [list] - -可用授权操作: -allow: 允许会话所在群组/频道/用户 -deny: 禁止会话所在群组/频道/用户 -list: 列出会话所在群组/频道/用户已配置的权限节点 -custom_allow: 允许指定群组/频道/用户 -custom_deny: 禁止指定群组/频道/用户''' +__plugin_meta__ = PluginMetadata( + name="授权管理", + description="【OmegaAuth 授权管理插件】\n" + "插件特殊权限授权管理\n" + "仅限管理员使用", + usage="/OmegaAuth [授权操作] [授权对象ID] [插件名称] [权限节点]\n" + "/OmegaAuth [allow|deny] [插件名称] [权限节点]\n" + "/OmegaAuth [list]\n\n" + "可用授权操作:\n" + "allow: 允许会话所在群组/频道/用户\n" + "deny: 禁止会话所在群组/频道/用户\n" + "list: 列出会话所在群组/频道/用户已配置的权限节点\n" + "custom_allow: 允许指定群组/频道/用户\n" + "custom_deny: 禁止指定群组/频道/用户", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 @@ -122,8 +132,7 @@ async def handle_related_entity_id( @auth.handle() async def handle_plugin_name_tips(matcher: Matcher, state: T_State): if not state.get('plugin_name', None): - all_plugins = [p.name for p in get_loaded_plugins() - if getattr(p.module, '__plugin_custom_name__', None) is not None] + all_plugins = [p.name for p in get_loaded_plugins() if p.metadata is not None] all_plugins.sort() all_plugin_name = '\n'.join(all_plugins) info_msg = f'现在已安装的插件有:\n\n{all_plugin_name}' diff --git a/omega_miya/plugins/omega_email/__init__.py b/omega_miya/plugins/omega_email/__init__.py index 8e0cfdfb..0010cc4f 100644 --- a/omega_miya/plugins/omega_email/__init__.py +++ b/omega_miya/plugins/omega_email/__init__.py @@ -1,5 +1,16 @@ +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : omega_email.py +@Project : nonebot2_miya +@Description : Omega 邮箱插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + import re -from nonebot import CommandGroup, logger +from nonebot.log import logger +from nonebot.plugin import CommandGroup, PluginMetadata from nonebot.rule import to_me from nonebot.permission import SUPERUSER from nonebot.typing import T_State @@ -19,19 +30,18 @@ from .utils import check_mailbox, get_unseen_mail_data, encrypt_password, decrypt_password -# Custom plugin usage text -__plugin_custom_name__ = '收邮件' -__plugin_usage__ = r'''【OmegaEmail 邮箱插件】 -主要是用来收验证码OvO -仅限群聊使用 - -用法: -/收邮件 - -管理员命令: -/添加邮箱 -/绑定邮箱 -/解绑邮箱''' +__plugin_meta__ = PluginMetadata( + name="收邮件", + description="【OmegaEmail 邮箱插件】\n" + "主要是用来收验证码的\n" + "仅限群聊使用", + usage="/收邮件\n\n" + "管理员命令:\n" + "/添加邮箱\n" + "/绑定邮箱\n" + "/解绑邮箱", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/omega_help/__init__.py b/omega_miya/plugins/omega_help/__init__.py index 97ee838c..27309d67 100644 --- a/omega_miya/plugins/omega_help/__init__.py +++ b/omega_miya/plugins/omega_help/__init__.py @@ -1,5 +1,14 @@ -from nonebot import on_command -from nonebot.plugin import get_loaded_plugins +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : omega_help.py +@Project : nonebot2_miya +@Description : Omega 帮助插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.plugin import get_loaded_plugins, on_command, PluginMetadata from nonebot.typing import T_State from nonebot.permission import SUPERUSER from nonebot.adapters.onebot.v11.bot import Bot @@ -12,13 +21,13 @@ from omega_miya.service.omega_processor_tools import init_processor_state, parse_processor_state -# Custom plugin usage text -__plugin_custom_name__ = '帮助' -__plugin_usage__ = r'''【帮助】 -一个简单的帮助插件 - -用法: -/帮助 [插件名]''' +__plugin_meta__ = PluginMetadata( + name="帮助", + description="【Omega 帮助插件】\n" + "一个简单的帮助插件", + usage="/帮助 [插件名]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 @@ -57,18 +66,18 @@ async def handle_help_message(bot: Bot, event: MessageEvent, plugin_name: str = async def get_all_plugins_desc() -> str: """获取全部配置了自定义信息的插件信息""" - plugin_custom_name = '\n'.join(str(name) for name in ( - getattr(plugin.module, '__plugin_custom_name__', None) for plugin in get_loaded_plugins() - ) if name is not None) - return f'现在已安装的插件有: \n\n{plugin_custom_name}\n\n输入"/help [插件名]"即可查看插件详情及帮助' + plugins_custom_name = '\n'.join(plugin.metadata.name for plugin in get_loaded_plugins() if plugin.metadata) + return f'现在已安装的插件有: \n\n{plugins_custom_name}\n\n输入"/help [插件名]"即可查看插件详情及帮助' async def get_plugin_desc(plugin_name: str, *, for_superuser: bool = False) -> str: """获取指定的配置了自定义信息的插件信息""" for plugin in get_loaded_plugins(): - plugin_custom_name = getattr(plugin.module, '__plugin_custom_name__', None) - if plugin_name == plugin_custom_name: - plugin_usage = getattr(plugin.module, '__plugin_usage__', None) + if not plugin.metadata: + continue + + if plugin_name == plugin.metadata.name: + plugin_usage = f'{plugin.metadata.description}\n\n用法:\n{plugin.metadata.usage}' if for_superuser: processor_info = '\n'.join( f'\n[{s.name}]\nLevel: {s.level if s.level < 4294967296 else "Unlimited"}/Node: {s.auth_node}\n' diff --git a/omega_miya/plugins/omega_invite_manager/__init__.py b/omega_miya/plugins/omega_invite_manager/__init__.py index 37f5d9b3..1b6513bb 100644 --- a/omega_miya/plugins/omega_invite_manager/__init__.py +++ b/omega_miya/plugins/omega_invite_manager/__init__.py @@ -10,7 +10,8 @@ import random import string -from nonebot import on_request, on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_request, PluginMetadata from nonebot.typing import T_State from nonebot.rule import to_me from nonebot.permission import SUPERUSER @@ -27,21 +28,20 @@ from omega_miya.utils.message_tools import MessageTools -# Custom plugin usage text -__plugin_custom_name__ = '好友和群组请求管理' -__plugin_usage__ = r'''【OmegaInviteManager 好友和群组请求管理插件】 -处理加好友请求和加群、退群请求 - -用法: -/好友验证码 [用户qq] -/允许邀请进群 [用户qq] -/禁止邀请进群 [用户qq] - -说明: -以上命令均只允许管理员使用, -"好友验证码"命令会为指定用户生成一段验证码, 该用户在验证消息中输入该验证码可让 bot 通过好友验证 -"允许邀请进群"命令会为指定用户分配邀请 bot 进群的权限, 若该用户是 bot 的好友且具备该权限, 则 bot 会自动同意用户的邀请进群请求 -"禁止邀请进群"命令会移除指定用户的邀请 bot 进群的权限, 若 bot 被无该权限的用户邀请进群, 则会自动退群''' +__plugin_meta__ = PluginMetadata( + name="好友和群组请求管理", + description="【OmegaInviteManager 好友和群组请求管理插件】\n" + "处理加好友请求和加群、退群请求", + usage="/好友验证码 [用户qq]\n" + "/允许邀请进群 [用户qq]\n" + "/禁止邀请进群 [用户qq]\n\n" + "说明:\n" + "以上命令均只允许管理员使用\n" + '"好友验证码"命令会为指定用户生成一段验证码, 该用户在验证消息中输入该验证码可让 bot 通过好友验证\n' + '"允许邀请进群"命令会为指定用户分配邀请 bot 进群的权限, 若该用户是 bot 的好友且具备该权限, 则 bot 会自动同意用户的邀请进群请求\n' + '"禁止邀请进群"命令会移除指定用户的邀请 bot 进群的权限, 若 bot 被无该权限的用户邀请进群, 则会自动退群', + extra={"author": "Ailitonia"}, +) _FRIEND_ADD_VERIFY_CODE: dict[str, str] = {} diff --git a/omega_miya/plugins/omega_manager/__init__.py b/omega_miya/plugins/omega_manager/__init__.py index 0fee9872..c0d2e5d9 100644 --- a/omega_miya/plugins/omega_manager/__init__.py +++ b/omega_miya/plugins/omega_manager/__init__.py @@ -10,7 +10,8 @@ """ from datetime import datetime, timedelta -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.permission import SUPERUSER from nonebot.typing import T_State from nonebot.matcher import Matcher @@ -27,29 +28,28 @@ from omega_miya.utils.apscheduler import scheduler -# Custom plugin usage text -__plugin_custom_name__ = '核心管理' -__plugin_usage__ = r'''【Omega 机器人核心管理插件】 -机器人开关、维护、功能及基础权限管理 -仅限管理员或私聊使用 - -用法: -/omega Init -/omega Enable -/omega Disable -/omega SetLevel -/omega ShowPermission -/omega QuitGroup -/omega CancelQuitGroup - -说明: -Init: 初始化并启用基本功能, 不会覆盖已有信息, 仅供第一次使用bot时执行 -Enable: 启用 bot 功能 -Disable: 禁用 bot 功能 -SetLevel: 设置权限等级 -ShowPermission: 查询权限状态 -QuitGroup: 命令bot退群 -CancelQuitGroup: 取消bot退群''' +__plugin_meta__ = PluginMetadata( + name="管理核心", + description="【Omega 机器人核心管理插件】\n" + "机器人开关、维护、功能及基础权限管理\n" + "仅限管理员或私聊使用", + usage="/omega Init\n" + "/omega Enable\n" + "/omega Disable\n" + "/omega SetLevel \n" + "/omega ShowPermission\n" + "/omega QuitGroup\n" + "/omega CancelQuitGroup\n\n" + "说明:\n" + "Init: 初始化并启用基本功能, 不会覆盖已有信息, 仅供第一次使用bot时执行\n" + "Enable: 启用 bot 功能\n" + "Disable: 禁用 bot 功能\n" + "SetLevel: 设置权限等级\n" + "ShowPermission: 查询权限状态\n" + "QuitGroup: 命令bot退群\n" + "CancelQuitGroup: 取消bot退群", + extra={"author": "Ailitonia"}, +) DEFAULT_PERMISSION_LEVEL: int = 10 diff --git a/omega_miya/plugins/omega_plugin_manager/__init__.py b/omega_miya/plugins/omega_plugin_manager/__init__.py index 6be58124..9ed0a254 100644 --- a/omega_miya/plugins/omega_plugin_manager/__init__.py +++ b/omega_miya/plugins/omega_plugin_manager/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import CommandGroup, get_plugin, get_loaded_plugins, logger +from nonebot.log import logger +from nonebot.plugin import CommandGroup, get_plugin, get_loaded_plugins, PluginMetadata from nonebot.rule import to_me from nonebot.permission import SUPERUSER from nonebot.typing import T_State @@ -22,23 +23,22 @@ from omega_miya.utils.process_utils import run_async_catching_exception -# Custom plugin usage text -__plugin_custom_name__ = '插件管理' -__plugin_usage__ = r'''【OmegaPluginManager 插件管理器】 -管理启用和禁用插件 -仅限管理员使用 - -用法: -/OPM.[管理操作] [插件名] -或使用别名: -/启用插件 [插件名] -/禁用插件 [插件名] -/插件列表 - -可用管理操作: -enable: 启用插件 -disable: 禁用插件 -list: 显示插件列表''' +__plugin_meta__ = PluginMetadata( + name="插件管理", + description="【OmegaPluginManager 插件管理器】\n" + "管理启用和禁用插件\n" + "仅限管理员使用", + usage="/OPM.[管理操作] [插件名]\n" + "或使用别名:\n" + "/启用插件 [插件名]\n" + "/禁用插件 [插件名]\n" + "/插件列表\n\n" + "可用管理操作:\n" + "enable: 启用插件\n" + "disable: 禁用插件\n" + "list: 显示插件列表", + extra={"author": "Ailitonia"}, +) _log_prefix: str = 'OmegaPluginManager | ' @@ -157,6 +157,7 @@ def _desc(plugin_name: str) -> str: plugin = get_plugin(name=plugin_name) if plugin is None: return plugin_name + elif plugin.metadata is None: + return plugin_name - plugin_custom_name = getattr(plugin.module, '__plugin_custom_name__', '') - return f'{plugin_name}({plugin_custom_name})' + return f'{plugin_name}({plugin.metadata.name})' diff --git a/omega_miya/plugins/omega_rate_limiting/__init__.py b/omega_miya/plugins/omega_rate_limiting/__init__.py index b86b238b..5d528b31 100644 --- a/omega_miya/plugins/omega_rate_limiting/__init__.py +++ b/omega_miya/plugins/omega_rate_limiting/__init__.py @@ -9,8 +9,8 @@ """ from datetime import datetime, timedelta -from nonebot import logger -from nonebot.plugin import on_notice, CommandGroup +from nonebot.log import logger +from nonebot.plugin import on_notice, CommandGroup, PluginMetadata from nonebot.typing import T_State from nonebot.rule import to_me from nonebot.permission import SUPERUSER @@ -25,14 +25,14 @@ from omega_miya.utils.message_tools import MessageTools -# Custom plugin usage text -__plugin_custom_name__ = '流控限制' -__plugin_usage__ = r'''【OmegaRateLimiting 流控限制插件】 -用户及群组流控限制 - -用法: -/Ban [用户] -/GBan [群组]''' +__plugin_meta__ = PluginMetadata( + name="流控限制", + description="【OmegaRateLimiting 流控限制插件】\n" + "用户及群组流控限制", + usage="/Ban [用户]\n" + "/GBan [群组]", + extra={"author": "Ailitonia"}, +) _log_prefix: str = 'OmegaRateLimiting | ' diff --git a/omega_miya/plugins/omega_recaller/__init__.py b/omega_miya/plugins/omega_recaller/__init__.py index 1a589187..49d5b7ac 100644 --- a/omega_miya/plugins/omega_recaller/__init__.py +++ b/omega_miya/plugins/omega_recaller/__init__.py @@ -8,8 +8,8 @@ @Software : PyCharm """ -from nonebot import logger -from nonebot.plugin import on_command +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.permission import SUPERUSER from nonebot.adapters.onebot.v11.permission import GROUP_OWNER, GROUP_ADMIN from nonebot.adapters.onebot.v11.bot import Bot @@ -20,15 +20,15 @@ from omega_miya.onebot_api import GoCqhttpBot -# Custom plugin usage text -__plugin_custom_name__ = '快速撤回' -__plugin_usage__ = r'''【快速撤回】 -快速撤回 bot 发送的消息 -仅限群管或超管使用 - -用法: -回复需撤回的消息 -/撤回''' +__plugin_meta__ = PluginMetadata( + name="快速撤回", + description="【快速撤回插件】\n" + "快速撤回 bot 发送的消息\n" + "仅限群管或超管使用", + usage="回复需撤回的消息\n" + "/撤回", + extra={"author": "Ailitonia"}, +) self_recall = on_command( diff --git a/omega_miya/plugins/omega_sign_in/__init__.py b/omega_miya/plugins/omega_sign_in/__init__.py index 50ad7d42..a9632692 100644 --- a/omega_miya/plugins/omega_sign_in/__init__.py +++ b/omega_miya/plugins/omega_sign_in/__init__.py @@ -11,7 +11,9 @@ import random from typing import Union from datetime import datetime -from nonebot import MatcherGroup, on_notice, get_driver, logger +from nonebot import get_driver +from nonebot.log import logger +from nonebot.plugin import MatcherGroup, on_notice, PluginMetadata from nonebot.message import handle_event from nonebot.typing import T_State from nonebot.rule import to_me @@ -33,19 +35,19 @@ from .utils import get_head_image, get_hitokoto, generate_signin_card -# Custom plugin usage text -__plugin_custom_name__ = '签到' -__plugin_usage__ = r'''【OmegaSignIn 签到插件】 -签到插件, 好感度系统基础支持 -仅限群聊使用 - -用法: -/签到 -/今日运势|今日人品 -/好感度|我的好感 -/一言 - -可使用戳一戳触发''' +__plugin_meta__ = PluginMetadata( + name="签到", + description="【OmegaSignIn 签到插件】\n" + "签到插件\n" + "好感度系统基础支持", + usage="/签到\n" + "/今日运势|今日人品\n" + "/好感度|我的好感\n" + "/一言\n\n" + "可使用双击头像戳一戳触发", + config=sign_in_config.__class__, + extra={"author": "Ailitonia"}, +) _COMMAND_START: set[str] = get_driver().config.command_start @@ -171,7 +173,8 @@ async def handle_command_fix_sign_in_check(bot: Bot, event: GroupMessageEvent | fix_date = datetime.fromordinal(fix_date_result).strftime('%Y年%m月%d日') fix_days = datetime.now().toordinal() - fix_date_result - fix_cost = 10 if fix_days <= 3 else fix_days * 3 + base_cost = 2 * sign_in_config.signin_base_currency + fix_cost = base_cost if fix_days <= 3 else fix_days * base_cost # 获取当前好感度信息 friendship = await run_async_catching_exception(user.get_friendship_model)() @@ -221,7 +224,7 @@ async def handle_command_fix_sign_in_check(bot: Bot, event: GroupMessageEvent | f'成功补签了{fix_date}的签到!'}) msg = await handle_fortune(bot=bot, event=event, state=state) logger.info(f'SignIn | User({user.tid}), 补签成功') - await command_fix_sign_in.finish(msg) + await command_fix_sign_in.finish(msg, at_sender=True) async def handle_sign_in(bot: Bot, event: MessageEvent, state: T_State) -> Union[Message, MessageSegment, str]: @@ -252,14 +255,14 @@ async def handle_sign_in(bot: Bot, event: MessageEvent, state: T_State) -> Union # 尝试为用户增加好感度 # 根据连签日期设置不同增幅 if continuous_days < 7: - base_friendship_inc = int(10 * (1 + random.gauss(0.25, 0.25))) - currency_inc = 1 + base_friendship_inc = int(30 * (1 + random.gauss(0.25, 0.25))) + currency_inc = 1 * sign_in_config.signin_base_currency elif continuous_days < 30: - base_friendship_inc = int(30 * (1 + random.gauss(0.35, 0.2))) - currency_inc = 3 + base_friendship_inc = int(70 * (1 + random.gauss(0.35, 0.2))) + currency_inc = 3 * sign_in_config.signin_base_currency else: - base_friendship_inc = int(50 * (1 + random.gauss(0.45, 0.15))) - currency_inc = 5 + base_friendship_inc = int(110 * (1 + random.gauss(0.45, 0.15))) + currency_inc = 5 * sign_in_config.signin_base_currency # 将能量值兑换为好感度 friendship_inc = friendship.energy * sign_in_config.signin_ef_exchange_rate + base_friendship_inc diff --git a/omega_miya/plugins/omega_sign_in/config.py b/omega_miya/plugins/omega_sign_in/config.py index b85fe976..fc41ed10 100644 --- a/omega_miya/plugins/omega_sign_in/config.py +++ b/omega_miya/plugins/omega_sign_in/config.py @@ -32,6 +32,8 @@ class SignInConfig(BaseModel): # 能量值与好感度的兑换比例 公式为(能量值 * 兑换比 = 好感度) signin_ef_exchange_rate: float = 0.25 + # 每日首次签到获取的基础硬币数 同时也是补签所需硬币的倍率基数 + signin_base_currency: int = 5 class Config: extra = "ignore" diff --git a/omega_miya/plugins/omega_statistic/__init__.py b/omega_miya/plugins/omega_statistic/__init__.py index 894fbf7a..8e3b733e 100644 --- a/omega_miya/plugins/omega_statistic/__init__.py +++ b/omega_miya/plugins/omega_statistic/__init__.py @@ -9,7 +9,8 @@ """ from datetime import datetime -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -25,19 +26,18 @@ from .utils import draw_statistics -# Custom plugin usage text -__plugin_custom_name__ = '统计信息' -__plugin_usage__ = r'''【OmegaStatistic 插件使用统计】 -查询插件使用统计信息 - -用法: -/统计信息 [条件] - -条件: -- 本月 -- 本年 -- 全部 -- 所有''' +__plugin_meta__ = PluginMetadata( + name="统计信息", + description="【OmegaStatistic 插件使用统计】\n" + "查询插件使用统计信息", + usage="/统计信息 [条件]\n\n" + "条件:\n" + "- 本月\n" + "- 本年\n" + "- 全部\n" + "- 所有", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 @@ -59,7 +59,7 @@ async def handle_parse_condition(state: T_State, cmd_arg: Message = CommandArg() if condition: state.update({'condition': condition}) else: - state.update({'condition': '全部'}) + state.update({'condition': '本月'}) @statistic.got('condition', prompt='请输入查询条件:') diff --git a/omega_miya/plugins/omega_statistic/utils.py b/omega_miya/plugins/omega_statistic/utils.py index a7860ea1..10777785 100644 --- a/omega_miya/plugins/omega_statistic/utils.py +++ b/omega_miya/plugins/omega_statistic/utils.py @@ -11,19 +11,24 @@ import sys from datetime import datetime from io import BytesIO +from matplotlib import font_manager from matplotlib import pyplot as plt from omega_miya.database import Statistic -from omega_miya.local_resource import TmpResource +from omega_miya.local_resource import LocalResource, TmpResource from omega_miya.utils.process_utils import run_sync, run_async_catching_exception - +_DEFAULT_FONT: LocalResource = LocalResource('fonts', 'fzzxhk.ttf') +"""默认使用字体""" _TMP_STATISTIC_IMG_FOLDER: str = 'statistic' """生成统计图缓存文件夹名""" _TMP_STATISTIC_PATH: TmpResource = TmpResource(_TMP_STATISTIC_IMG_FOLDER) """生成统计图缓存资源地址""" +font_manager.fontManager.addfont(_DEFAULT_FONT.resolve_path) # 添加资源文件中字体 + + @run_async_catching_exception async def draw_statistics( self_id: str, @@ -43,13 +48,18 @@ async def draw_statistics( def _handle() -> bytes: plt.switch_backend('agg') # Fix RuntimeError caused by GUI needed + plt.rcParams['font.sans-serif'] = ['FZZhengHei-EL-GBK'] if sys.platform.startswith('win'): - plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False - plt.barh([x.custom_name for x in statistic_result], [x.call_count for x in statistic_result]) + + # 绘制条形图 + _bar_c = plt.barh([x.custom_name for x in statistic_result], [x.call_count for x in statistic_result]) + plt.bar_label(_bar_c, label_type='edge') plt.title(title) + + # 导出图片 with BytesIO() as bf: - plt.savefig(bf, dpi=300, format='JPG') + plt.savefig(bf, dpi=300, format='JPG', bbox_inches='tight') img_bytes = bf.getvalue() return img_bytes diff --git a/omega_miya/plugins/omega_su/__init__.py b/omega_miya/plugins/omega_su/__init__.py index 83e7aead..a1267c59 100644 --- a/omega_miya/plugins/omega_su/__init__.py +++ b/omega_miya/plugins/omega_su/__init__.py @@ -10,8 +10,8 @@ @Software : PyCharm """ -from nonebot import logger -from nonebot.plugin import on, on_command +from nonebot.log import logger +from nonebot.plugin import on, on_command, PluginMetadata from nonebot.typing import T_State from nonebot.message import handle_event from nonebot.rule import to_me @@ -25,19 +25,17 @@ from omega_miya.service.gocqhttp_self_sent_patch import MessageSentEvent, SU_SELF_SENT -_SU_TAG: bool = False - - -# Custom plugin usage text -__plugin_custom_name__ = '自调用消息' -__plugin_usage__ = r'''【OmegaSu 自调用消息插件】 -让人工登陆机器人账号时可以通过特殊命令来自己调用自己 - -用法: -/su +__plugin_meta__ = PluginMetadata( + name="自调用消息", + description="【OmegaSu 自调用消息插件】\n" + "让人工登陆机器人账号时可以通过特殊命令来自己调用自己", + usage="/su \n\n" + "人工登录 bot 使用命令:\n" + "!SU [command]", + extra={"author": "Ailitonia"}, +) -人工登录 bot 使用命令: -!SU [command]''' +_SU_TAG: bool = False # 注册事件响应器 diff --git a/omega_miya/plugins/omega_welcome_message/__init__.py b/omega_miya/plugins/omega_welcome_message/__init__.py index 399a049b..96e253eb 100644 --- a/omega_miya/plugins/omega_welcome_message/__init__.py +++ b/omega_miya/plugins/omega_welcome_message/__init__.py @@ -8,8 +8,8 @@ @Software : PyCharm """ -from nonebot import logger -from nonebot.plugin import on_notice, on_command +from nonebot.log import logger +from nonebot.plugin import on_notice, on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -26,14 +26,14 @@ from omega_miya.utils.message_tools import MessageTools -# Custom plugin usage text -__plugin_custom_name__ = '群欢迎消息' -__plugin_usage__ = r'''【群自定义欢迎消息插件】 -向新入群的成员发送欢迎消息 - -用法: -/设置欢迎消息 [消息内容] -/移除欢迎消息''' +__plugin_meta__ = PluginMetadata( + name="群欢迎消息", + description="【群自定义欢迎消息插件】\n" + "向新入群的成员发送欢迎消息", + usage="/设置欢迎消息 [消息内容]\n" + "/移除欢迎消息", + extra={"author": "Ailitonia"}, +) _SETTING_NAME: str = 'group_welcome_message' diff --git a/omega_miya/plugins/pixiv/__init__.py b/omega_miya/plugins/pixiv/__init__.py index 52791d31..772f3f98 100644 --- a/omega_miya/plugins/pixiv/__init__.py +++ b/omega_miya/plugins/pixiv/__init__.py @@ -9,7 +9,8 @@ """ from datetime import datetime, timedelta -from nonebot import on_command, on_shell_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_shell_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.rule import Namespace @@ -36,36 +37,35 @@ from .monitor import scheduler -# Custom plugin usage text -__plugin_custom_name__ = 'Pixiv' -__plugin_usage__ = r'''【Pixiv助手】 -查看Pixiv插画、发现与推荐、日榜、周榜、月榜以及搜索作品 -订阅并跟踪画师作品更新 - -用法: -/pixiv -/pixiv发现 -/pixiv推荐 [PID or ArtworkUrl] -/pixiv日榜 [页码] -/pixiv周榜 [页码] -/pixiv月榜 [页码] -/pixiv用户搜索 [用户昵称] -/pixiv用户作品 [UID] -/pixiv用户订阅列表 -/pixiv下载 [页数] -/pixiv搜索 [关键词] - -仅限私聊或群聊中群管理员使用: -/pixiv用户订阅 [UID] -/pixiv取消用户订阅 [UID] - -搜索命令参数: -'-c', '--custom': 启用自定义参数 -'-p', '--page': 搜索结果页码 -'-o', '--order': 排序方式, 可选: "date_d", "popular_d" -'-l', '--like': 筛选最低收藏数 -'-d', '--from-days-ago': 筛选作品发布日期, 从几天前起始发布的作品 -'-s', '--safe-mode': NSFW 模式, 可选: "safe", "all", "r18"''' +__plugin_meta__ = PluginMetadata( + name="Pixiv", + description="【Pixiv助手插件】\n" + "查看Pixiv插画、发现与推荐、日榜、周榜、月榜以及搜索作品\n" + "订阅并跟踪画师作品更新", + usage="/pixiv \n" + "/pixiv发现\n" + "/pixiv推荐 [PID or ArtworkUrl]\n" + "/pixiv日榜 [页码]\n" + "/pixiv周榜 [页码]\n" + "/pixiv月榜 [页码]\n" + "/pixiv用户搜索 [用户昵称]\n" + "/pixiv用户作品 [UID]\n" + "/pixiv用户订阅列表\n" + "/pixiv下载 [页数]\n" + "/pixiv搜索 [关键词]\n\n" + "仅限私聊或群聊中群管理员使用:\n" + "/pixiv用户订阅 [UID]\n" + "/pixiv取消用户订阅 [UID]\n\n" + "搜索命令参数:\n" + "'-c', '--custom': 启用自定义参数\n" + "'-p', '--page': 搜索结果页码\n" + "'-o', '--order': 排序方式, 可选: 'date_d', 'popular_d'\n" + "'-l', '--like': 筛选最低收藏数\n" + "'-d', '--from-days-ago': 筛选作品发布日期, 从几天前起始发布的作品\n" + "'-s', '--safe-mode': NSFW 模式, 可选: 'safe', 'all', 'r18'", + config=pixiv_plugin_config.__class__, + extra={"author": "Ailitonia"}, +) _ALLOW_R18_NODE = pixiv_plugin_config.pixiv_plugin_allow_r18_node @@ -373,7 +373,7 @@ async def handle_parse_success(bot: Bot, event: MessageEvent, matcher: Matcher, user_cool_down_override=2 ), aliases={'pixiv下载', 'Pixiv下载', 'pixivdl'}, - permission=GROUP, + permission=GROUP | PRIVATE_FRIEND, priority=20, block=True ) @@ -395,7 +395,7 @@ async def handle_parse_download_args(state: T_State, cmd_arg: Message = CommandA @pixiv_download.got('pid', prompt='想要下载哪个作品呢? 请输入作品PID:') @pixiv_download.got('page', prompt='想要下载作品的哪一页呢? 请输入页码:') -async def handle_download(bot: Bot, event: GroupMessageEvent, matcher: Matcher, +async def handle_download(bot: Bot, event: MessageEvent, matcher: Matcher, pid: str = ArgStr('pid'), page: str = ArgStr('page')): pid = pid.strip() page = page.strip() @@ -428,8 +428,16 @@ async def handle_download(bot: Bot, event: GroupMessageEvent, matcher: Matcher, gocq_bot = GoCqhttpBot(bot=bot) file_name = f'{artwork_data.pid}_p{page}_{artwork_data.title}_{artwork_data.uname}{download_file.path.suffix}' - upload_result = await run_async_catching_exception(gocq_bot.upload_group_file)( - group_id=event.group_id, file=download_file.resolve_path, name=file_name) + + if isinstance(event, GroupMessageEvent): + upload_task = run_async_catching_exception(gocq_bot.upload_group_file)( + group_id=event.group_id, file=download_file.resolve_path, name=file_name + ) + else: + upload_task = run_async_catching_exception(gocq_bot.upload_private_file)( + user_id=event.user_id, file=download_file.resolve_path, name=file_name + ) + upload_result = await upload_task if isinstance(upload_result, Exception): logger.warning(f'PixivDownload | 下载作品(pid={pid})失败, 上传群文件失败: {upload_result}') await matcher.finish('上传图片到群文件失败QAQ, 可能上传仍在进行中, 请等待1~2分钟后再重试') diff --git a/omega_miya/plugins/pixivsion/__init__.py b/omega_miya/plugins/pixivsion/__init__.py index 96e3c211..b09d3ca5 100644 --- a/omega_miya/plugins/pixivsion/__init__.py +++ b/omega_miya/plugins/pixivsion/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -27,17 +28,17 @@ from .monitor import scheduler -# Custom plugin usage text -__plugin_custom_name__ = 'Pixivision' -__plugin_usage__ = r'''【Pixivision助手】 -探索并查看Pixivision文章, 订阅最新的Pixivision特辑 - -用法: -/pixivision [AID] - -仅限私聊或群聊中群管理员使用: -/pixivision订阅 -/pixivision取消订阅''' +__plugin_meta__ = PluginMetadata( + name="Pixivision", + description="【Pixivision助手插件】\n" + "探索并查看Pixivision文章\n" + "订阅最新的Pixivision特辑", + usage="/pixivision [AID]\n\n" + "仅限私聊或群聊中群管理员使用:\n" + "/pixivision订阅\n" + "/pixivision取消订阅", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/repeater/__init__.py b/omega_miya/plugins/repeater/__init__.py index ce1f7662..3fe692a9 100644 --- a/omega_miya/plugins/repeater/__init__.py +++ b/omega_miya/plugins/repeater/__init__.py @@ -1,4 +1,14 @@ -from nonebot import on_message +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : repeater.py +@Project : nonebot2_miya +@Description : 复读姬 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.plugin import on_message, PluginMetadata from nonebot.exception import FinishedException from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.event import GroupMessageEvent @@ -7,10 +17,13 @@ from omega_miya.utils.rule import group_has_permission_level -# Custom plugin usage text -__plugin_custom_name__ = '复读姬' -__plugin_usage__ = r'''【复读姬】 -如同人类的本质一样复读''' +__plugin_meta__ = PluginMetadata( + name="复读姬", + description="【复读姬插件】\n" + "如同人类的本质一样复读", + usage="由群聊复读触发", + extra={"author": "Ailitonia"}, +) LAST_MSG: dict[int, str] = {} diff --git a/omega_miya/plugins/roll/__init__.py b/omega_miya/plugins/roll/__init__.py index 717b28ce..6a8e0bc0 100644 --- a/omega_miya/plugins/roll/__init__.py +++ b/omega_miya/plugins/roll/__init__.py @@ -10,7 +10,7 @@ import re import random -from nonebot import on_command +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.message import Message @@ -23,16 +23,16 @@ from omega_miya.onebot_api import GoCqhttpBot -# Custom plugin usage text -__plugin_custom_name__ = 'Roll' -__plugin_usage__ = r'''【Roll】 -各种姿势的掷骰子 -选择困难症患者福音 - -用法: -/roll d -/抽奖 <人数> -/帮我选 [选项1 选项2 ...]''' +__plugin_meta__ = PluginMetadata( + name="Roll", + description="【骰子插件】\n" + "各种姿势的掷骰子\n" + "选择困难症患者福音", + usage="/roll d\n" + "/抽奖 <人数>\n" + "/帮我选 [选项1 选项2 ...]", + extra={"author": "Ailitonia"}, +) _ALL_LOTTERY_NUM: int = 50 diff --git a/omega_miya/plugins/schedule_message/__init__.py b/omega_miya/plugins/schedule_message/__init__.py index 0a2a59a3..a16237fa 100644 --- a/omega_miya/plugins/schedule_message/__init__.py +++ b/omega_miya/plugins/schedule_message/__init__.py @@ -9,7 +9,7 @@ """ from nonebot.log import logger -from nonebot.plugin import on_command +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -25,20 +25,18 @@ get_schedule_message_job_list, set_schedule_message_job, remove_schedule_message_job) -# Custom plugin usage text -__plugin_custom_name__ = '定时消息' -__plugin_usage__ = r'''【定时消息】 -设置定时消息 - -用法: -/设置定时消息 -/删除定时消息 -/定时消息列表 - -Crontab格式说明: - * | * | * | * | * -分|时|日|月|星期 -''' +__plugin_meta__ = PluginMetadata( + name="定时消息", + description="【定时消息插件】\n" + "设置定时消息", + usage="/设置定时消息\n" + "/删除定时消息\n" + "/定时消息列表\n\n" + "Crontab格式说明:\n" + " * | * | * | * | *\n" + "分|时|日|月|星期", + extra={"author": "Ailitonia"}, +) set_schedule_message = on_command( diff --git a/omega_miya/plugins/self_mute/__init__.py b/omega_miya/plugins/self_mute/__init__.py index f72baf25..bd989c40 100644 --- a/omega_miya/plugins/self_mute/__init__.py +++ b/omega_miya/plugins/self_mute/__init__.py @@ -10,7 +10,8 @@ import re import random -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot @@ -24,13 +25,13 @@ from omega_miya.onebot_api import GoCqhttpBot -# Custom plugin usage text -__plugin_custom_name__ = '随机口球' -__plugin_usage__ = r'''【随机口球】 -自取随机口球礼包 - -用法: -/随机口球 [n倍]''' +__plugin_meta__ = PluginMetadata( + name="随机口球", + description="【随机口球插件】\n" + "自取随机口球礼包", + usage="/随机口球 [n倍]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/shindan_maker/__init__.py b/omega_miya/plugins/shindan_maker/__init__.py index 30badc7e..adcc4cd9 100644 --- a/omega_miya/plugins/shindan_maker/__init__.py +++ b/omega_miya/plugins/shindan_maker/__init__.py @@ -10,7 +10,8 @@ import re import datetime -from nonebot import on_command, on_regex, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_regex, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot @@ -27,15 +28,15 @@ from .data_source import ShindanMaker -# Custom plugin usage text -__plugin_custom_name__ = 'ShindanMaker' -__plugin_usage__ = r'''【ShindanMaker 占卜】 -使用ShindanMaker进行各种奇怪的占卜 -只能在群里使用 -就是要公开处刑! - -用法: -/ShindanMaker [占卜名称] [占卜对象名称]''' +__plugin_meta__ = PluginMetadata( + name="ShindanMaker", + description="【ShindanMaker 占卜插件】\n" + "使用ShindanMaker进行各种奇怪的占卜\n" + "只能在群里使用\n" + "就是要公开处刑!", + usage="/ShindanMaker [占卜名称] [占卜对象名称]", + extra={"author": "Ailitonia"}, +) shindan_maker = on_command( diff --git a/omega_miya/plugins/sticker_maker/__init__.py b/omega_miya/plugins/sticker_maker/__init__.py index 6a8ae680..1daee964 100644 --- a/omega_miya/plugins/sticker_maker/__init__.py +++ b/omega_miya/plugins/sticker_maker/__init__.py @@ -1,4 +1,15 @@ -from nonebot import on_command, logger +""" +@Author : Ailitonia +@Date : 2022/04/28 20:26 +@FileName : sticker_maker.py +@Project : nonebot2_miya +@Description : 表情包插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot @@ -17,12 +28,13 @@ from .render import get_render, get_all_render_name, download_source_image -# Custom plugin usage text -__plugin_custom_name__ = '表情包' -__plugin_usage__ = rf'''【表情包助手】 -使用模板快速制作表情包 - -/表情包 [模板名] [表情包文本] [表情包图片]''' +__plugin_meta__ = PluginMetadata( + name="表情包", + description="【表情包助手插件】\n" + "使用模板快速制作表情包", + usage="/表情包 [模板名] [表情包文本] [表情包图片]", + extra={"author": "Ailitonia"}, +) sticker = on_command( diff --git a/omega_miya/plugins/sticker_maker/model.py b/omega_miya/plugins/sticker_maker/model.py index a93ada1d..3fc3e0bf 100644 --- a/omega_miya/plugins/sticker_maker/model.py +++ b/omega_miya/plugins/sticker_maker/model.py @@ -9,6 +9,8 @@ """ import abc +import imageio +from typing import Iterable from datetime import datetime from io import BytesIO from PIL import Image @@ -60,16 +62,37 @@ def need_image(cls) -> bool: """是否需要外部图片来作为表情包生成的内容""" return cls._need_external_img + @abc.abstractmethod + def _static_handler(self, *args, **kwargs) -> bytes: + """静态图片表情包制作方法""" + raise NotImplementedError + + @abc.abstractmethod + def _gif_handler(self, *args, **kwargs) -> bytes: + """动态图片表情包制作方法""" + raise NotImplementedError + @abc.abstractmethod def _handler(self) -> bytes: - """表情包制作方法""" + """表情包制作入口函数""" raise NotImplementedError - def _load_source_image(self) -> Image.Image: + def _get_source_image_info(self) -> (str, dict): + """获取图片素材格式信息 + + :return: format: str, num of frames: int, info: dict + """ + with Image.open(self.source_image.resolve_path) as im: + f_im = im.format + info = im.info + return f_im, info + + def _load_source_image(self, frame: int | None = None) -> Image.Image: """载入并初始化图片素材""" - with self.source_image.open('rb') as f: - image: Image.Image = Image.open(f) - image.load() + image: Image.Image = Image.open(self.source_image.resolve_path) + if frame: + image.seek(frame=frame) + image.load() return image @staticmethod @@ -122,6 +145,22 @@ def _get_pil_image(image: Image.Image, output_format: str = 'JPEG') -> bytes: content = bf.getvalue() return content + @staticmethod + def _generate_gif_from_bytes_seq( + frames: Iterable[bytes], + duration: float = 0.06, + *, + quantizer: int = 2 + ) -> bytes: + """使用图片序列输出 GIF 图像""" + frames_list = [imageio.v2.imread(frame) for frame in frames] + + with BytesIO() as bf: + imageio.mimsave(bf, frames_list, 'GIF-PIL', duration=duration, quantizer=quantizer) + content = bf.getvalue() + + return content + async def make(self) -> TmpResource: """使用 _handle 方法制作表情包并输出""" image_content = await run_sync(self._handler)() diff --git a/omega_miya/plugins/sticker_maker/render.py b/omega_miya/plugins/sticker_maker/render.py index 5359786a..6113af1a 100644 --- a/omega_miya/plugins/sticker_maker/render.py +++ b/omega_miya/plugins/sticker_maker/render.py @@ -8,7 +8,6 @@ @Software : PyCharm """ -import imageio import numpy from typing import Type, Any from datetime import date @@ -43,6 +42,12 @@ class TraitorRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('pixel.ttf') _default_output_width = 800 + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -89,6 +94,12 @@ class JichouRender(StickerRender): _static_resource: LocalResource = _STATIC_RESOURCE('jichou', 'default_bg.png') _font: LocalResource = _FONT_RESOURCE('SourceHanSansSC-Regular.otf') + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -128,6 +139,12 @@ class PhlogoRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('SourceHanSansSC-Heavy.otf') _default_font_size = 320 + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: # 处理文本主体 test_sentences = self.text.strip().split(maxsplit=1) @@ -196,6 +213,12 @@ class LuxunSayRender(StickerRender): _static_resource: LocalResource = _STATIC_RESOURCE('luxunsay', 'default_bg.png') _font: LocalResource = _FONT_RESOURCE('SourceHanSansSC-Regular.otf') + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -257,6 +280,12 @@ class LuxunWriteRender(LuxunSayRender): _sticker_name: str = 'luxunwrite' _static_resource: LocalResource = _STATIC_RESOURCE('luxunwrite', 'default_bg.png') + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + class JiangzhuangRender(StickerRender): """奖状表情包模板 @@ -269,6 +298,12 @@ class JiangzhuangRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('HanYiWeiBeiJian.ttf') _default_output_width = 1024 + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -304,6 +339,12 @@ class XibaoHorizontalRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('HanYiWeiBeiJian.ttf') _default_output_width = 1024 + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -343,6 +384,12 @@ class XibaoVerticalRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('SourceHanSerif-Bold.ttc') _default_output_width = 1024 + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: self.source_image = self._static_resource image = self._load_source_image() @@ -382,10 +429,7 @@ class DefaultRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('msyhbd.ttc') _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) - + def _static_handler(self, image: Image) -> bytes: font_size = image.width // 8 text_stroke_width = int(font_size / 20) font = ImageFont.truetype(self._font.resolve_path, font_size) @@ -408,6 +452,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=image) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + class LittleAngelRender(StickerRender): """小天使表情包模板 @@ -420,10 +493,7 @@ class LittleAngelRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('msyhbd.ttc') _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) - + def _static_handler(self, image: Image) -> bytes: # 处理文本内容 font_size_up = int(image.width / 7) font_up = ImageFont.truetype(self._font.resolve_path, font_size_up) @@ -467,6 +537,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + class WhiteBackgroundRender(StickerRender): """白底加字表情包模板 @@ -479,10 +578,7 @@ class WhiteBackgroundRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('msyhbd.ttc') _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) - + def _static_handler(self, image: Image) -> bytes: font_size = image.width // 10 font = ImageFont.truetype(self._font.resolve_path, font_size) text_w, text_h = font.getsize_multiline(self.text) @@ -507,6 +603,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + class BlackBackgroundRender(StickerRender): """黑边加底字表情包模板 @@ -519,10 +644,7 @@ class BlackBackgroundRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('msyhbd.ttc') _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) - + def _static_handler(self, image: Image) -> bytes: font_size = image.width // 8 font = ImageFont.truetype(self._font.resolve_path, font_size) text_w, text_h = font.getsize_multiline(self.text) @@ -545,6 +667,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + class DeColorizeRender(StickerRender): """去色表情包模板 @@ -556,14 +707,41 @@ class DeColorizeRender(StickerRender): _need_text: bool = False _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() + def _static_handler(self, image: Image) -> bytes: image = image.convert('RGB') enhancer = ImageEnhance.Color(image) made_image = enhancer.enhance(0) content = self._get_pil_image(image=made_image) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + content = self._static_handler(image=image) + + return content + class GunjoRender(StickerRender): """群青表情包模板 @@ -577,10 +755,8 @@ class GunjoRender(StickerRender): _default_output_width = 512 _font: LocalResource = _FONT_RESOURCE('SourceHanSansSC-Bold.otf') - def _handler(self) -> bytes: - image = self._load_source_image() + def _static_handler(self, image: Image) -> bytes: image = image.convert('RGBA') - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) # 图片去色 made_image = ImageEnhance.Color(image).enhance(0) @@ -613,6 +789,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + class MarriageRender(StickerRender): """结婚登记表情包模板 @@ -626,17 +831,56 @@ class MarriageRender(StickerRender): _need_external_img: bool = True _default_output_width = 1080 - def _handler(self) -> bytes: - image = self._load_source_image() - image = self._resize_with_filling(image=image, size=(self._default_output_width, self._default_output_width)) + def _static_handler(self, image: Image, *, resize_width: int | None = None) -> bytes: upper_image = self._load_extra_source_image(source_file=self._static_resource) background = Image.new(mode='RGBA', size=image.size, color=(255, 255, 255, 255)) background.paste(im=image, box=(0, 0), mask=image) background.paste(im=upper_image, box=(0, 0), mask=upper_image) + + if resize_width: + background = self._zoom_pil_image_width(image=background, width=resize_width) content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float, output_width: int) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image, resize_width=output_width) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + output_width = 360 + while True: + try: + image = self._load_source_image(frame=frame_index) + output_width = image.width + image = self._resize_with_filling( + image=image, + size=(self._default_output_width, self._default_output_width) + ) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration, output_width=output_width) + else: + image = self._load_source_image() + image = self._resize_with_filling( + image=image, + size=(self._default_output_width, self._default_output_width) + ) + content = self._static_handler(image=image) + + return content + class GrassJaRender(StickerRender): """生草日语表情包模板 @@ -649,12 +893,10 @@ class GrassJaRender(StickerRender): _font: LocalResource = _FONT_RESOURCE('fzzxhk.ttf') _need_external_img: bool = True - def _handler(self) -> bytes: - image = self._load_source_image() + def _static_handler(self, image: Image) -> bytes: image = image.convert('RGB') enhancer = ImageEnhance.Color(image) image = enhancer.enhance(0) - image = self._zoom_pil_image_width(image=image, width=self._default_output_width) # 分割文本 font_zh = ImageFont.truetype(self._font.resolve_path, int(image.width / 13)) @@ -685,6 +927,35 @@ def _handler(self) -> bytes: content = self._get_pil_image(image=background) return content + def _gif_handler(self, frames: list[Image], duration: float) -> bytes: + content = self._generate_gif_from_bytes_seq( + frames=(self._static_handler(image=image) for image in frames), + duration=duration + ) + return content + + def _handler(self) -> bytes: + fm, info = self._get_source_image_info() + if fm == 'GIF': + frames = [] + frame_index = 0 + while True: + try: + image = self._load_source_image(frame=frame_index) + frames.append(image) + frame_index += 1 + except EOFError: + break + duration = info.get('duration', 60) / 1000 + self._default_output_format = 'gif' + content = self._gif_handler(frames=frames, duration=duration) + else: + image = self._load_source_image() + image = self._zoom_pil_image_width(image=image, width=self._default_output_width) + content = self._static_handler(image=image) + + return content + async def _translate_preprocessor(self) -> None: text_zh = self.text.replace('\n', ' ') text_trans_result = await TencentTMT().translate(source_text=self.text, target='ja') @@ -713,6 +984,12 @@ class PetPetRender(StickerRender): _need_external_img: bool = True _default_output_format: str = 'gif' + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: resize_paste_loc: list[tuple[tuple[int, int], tuple[int, int]]] = [ ((95, 95), (12, 15)), @@ -730,12 +1007,9 @@ def _handler(self) -> bytes: background.paste(frame, (0, 0), mask=frame) with BytesIO() as f_bf: background.save(f_bf, format='PNG') - img_bytes = f_bf.getvalue() - frames_list.append(imageio.v2.imread(img_bytes)) + frames_list.append(f_bf.getvalue()) - with BytesIO() as bf: - imageio.mimsave(bf, frames_list, 'GIF', duration=0.06) - content = bf.getvalue() + content = self._generate_gif_from_bytes_seq(frames=frames_list, duration=0.06) return content @@ -772,6 +1046,12 @@ def _get_perspective_data( res = numpy.dot(numpy.linalg.inv(target_matrix.T * target_matrix) * target_matrix.T, source_array) return numpy.array(res).reshape(8) + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: image = self._load_source_image() width, height = image.size @@ -790,12 +1070,9 @@ def _handler(self) -> bytes: background.paste(im=frame, box=(0, 0), mask=frame) with BytesIO() as f_bf: background.save(f_bf, format='PNG') - img_bytes = f_bf.getvalue() - frames_list.append(imageio.v2.imread(img_bytes)) + frames_list.append(f_bf.getvalue()) - with BytesIO() as bf: - imageio.mimsave(bf, frames_list, 'GIF', duration=0.04) - content = bf.getvalue() + content = self._generate_gif_from_bytes_seq(frames=frames_list, duration=0.04) return content @@ -812,6 +1089,12 @@ class TwistRender(StickerRender): _need_external_img: bool = True _default_output_format: str = 'gif' + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: image = self._load_source_image() image = self._resize_with_filling(image=image, size=(128, 128)) @@ -833,14 +1116,10 @@ def _handler(self) -> bytes: background.paste(im=frame, box=(0, 0), mask=frame) with BytesIO() as f_bf: background.save(f_bf, format='PNG') - img_bytes = f_bf.getvalue() - frames_list.append(imageio.v2.imread(img_bytes)) - + frames_list.append(f_bf.getvalue()) angle += 36 - with BytesIO() as bf: - imageio.mimsave(bf, frames_list, 'GIF', duration=0.03) - content = bf.getvalue() + content = self._generate_gif_from_bytes_seq(frames=frames_list, duration=0.03) return content @@ -858,6 +1137,12 @@ class WangjingzeRender(StickerRender): _need_external_img: bool = False _default_output_format: str = 'gif' + def _gif_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + + def _static_handler(self, *args, **kwargs) -> bytes: + raise NotImplementedError + def _handler(self) -> bytes: # 分割文本 text_list = self.text.split(maxsplit=3) @@ -891,14 +1176,11 @@ def _handler(self) -> bytes: stroke_width=2, stroke_fill=(0, 0, 0) ) - with BytesIO() as bf0: - frame.save(bf0, format='JPEG') - img_bytes = bf0.getvalue() - frames_list.append(imageio.v2.imread(img_bytes)) + with BytesIO() as bf: + frame.save(bf, format='JPEG') + frames_list.append(bf.getvalue()) - with BytesIO() as bf: - imageio.mimsave(bf, frames_list, 'GIF', duration=0.13) - content = bf.getvalue() + content = self._generate_gif_from_bytes_seq(frames=frames_list, duration=0.13) return content diff --git a/omega_miya/plugins/tarot/__init__.py b/omega_miya/plugins/tarot/__init__.py index 5b1a2cea..30caa22e 100644 --- a/omega_miya/plugins/tarot/__init__.py +++ b/omega_miya/plugins/tarot/__init__.py @@ -9,7 +9,8 @@ """ import random -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -26,16 +27,15 @@ from .utils import generate_tarot_card, get_tarot_resource_name, set_tarot_resource -# Custom plugin usage text -__plugin_custom_name__ = '塔罗牌' -__plugin_usage__ = r'''【塔罗牌】 -简单的塔罗牌插件 - -用法: -/塔罗牌 [卡牌名] - -仅限私聊或群聊中群管理员使用: -/设置塔罗牌组 [资源名]''' +__plugin_meta__ = PluginMetadata( + name="塔罗牌", + description="【塔罗牌插件】\n" + "简单的塔罗牌插件", + usage="/塔罗牌 [卡牌名]\n\n" + "仅限私聊或群聊中群管理员使用:\n" + "/设置塔罗牌组 [资源名]", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/translate/__init__.py b/omega_miya/plugins/translate/__init__.py index 19cd50c2..86cd0171 100644 --- a/omega_miya/plugins/translate/__init__.py +++ b/omega_miya/plugins/translate/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.typing import T_State from nonebot.rule import ArgumentParser from nonebot.exception import ParserExit @@ -23,13 +24,14 @@ from omega_miya.web_resource.tencent_cloud import TencentTMT -# Custom plugin usage text -__plugin_custom_name__ = '翻译' -__plugin_usage__ = r'''【翻译插件】 -简单的翻译插件 -目前使用了腾讯云的翻译API - -/翻译 [翻译内容]''' +__plugin_meta__ = PluginMetadata( + name="翻译", + description="【翻译插件】\n" + "简单的翻译插件\n" + "目前使用了腾讯云的翻译API", + usage="/翻译 [翻译内容]", + extra={"author": "Ailitonia"}, +) translate = on_command( diff --git a/omega_miya/plugins/what_to_eat/__init__.py b/omega_miya/plugins/what_to_eat/__init__.py index ce495f46..0e61e422 100644 --- a/omega_miya/plugins/what_to_eat/__init__.py +++ b/omega_miya/plugins/what_to_eat/__init__.py @@ -9,7 +9,8 @@ """ from datetime import datetime -from nonebot import on_command, logger +from nonebot.log import logger +from nonebot.plugin import on_command, PluginMetadata from nonebot.matcher import Matcher from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.permission import GROUP @@ -21,17 +22,17 @@ from .model import get_random_food_message -# Custom plugin usage text -__plugin_custom_name__ = '今天吃啥' -__plugin_usage__ = r'''【今天吃啥】 -给吃饭选择困难症一个解决方案 - -用法: -/今天吃啥 -/早上吃啥 -/中午吃啥 -/晚上吃啥 -/夜宵吃啥''' +__plugin_meta__ = PluginMetadata( + name="今天吃啥", + description="【今天吃啥插件】\n" + "给吃饭选择困难症一个解决方案", + usage="/今天吃啥\n" + "/早上吃啥\n" + "/中午吃啥\n" + "/晚上吃啥\n" + "/夜宵吃啥", + extra={"author": "Ailitonia"}, +) what_eat_today = on_command( diff --git a/omega_miya/plugins/word_bank/__init__.py b/omega_miya/plugins/word_bank/__init__.py index dbcdc1ec..591e782b 100644 --- a/omega_miya/plugins/word_bank/__init__.py +++ b/omega_miya/plugins/word_bank/__init__.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot import MatcherGroup, logger +from nonebot.log import logger +from nonebot.plugin import MatcherGroup, PluginMetadata from nonebot.typing import T_State from nonebot.rule import to_me from nonebot.matcher import Matcher @@ -25,20 +26,19 @@ from .word_bank import WordBankManager, WordBankMatcher -# Custom plugin usage text -__plugin_custom_name__ = '自动问答' -__plugin_usage__ = r'''【自动问答】 -使用模糊匹配的轻量化问答插件 -仅限群聊使用 - -用法: -@Bot [关键词] -若匹配成功则会回复 - -仅限群管理员使用: -/添加问答 -/删除问答 -/问答列表''' +__plugin_meta__ = PluginMetadata( + name="自动问答", + description="【自动问答插件】\n" + "使用模糊匹配的轻量化问答插件\n" + "仅限群聊使用", + usage="@Bot [关键词]\n" + "若匹配成功则会回复\n\n" + "仅限群管理员使用:\n" + "/添加问答\n" + "/删除问答\n" + "/问答列表", + extra={"author": "Ailitonia"}, +) # 注册事件响应器 diff --git a/omega_miya/plugins/zhoushen_hime/__init__.py b/omega_miya/plugins/zhoushen_hime/__init__.py index 5200a888..94fa9810 100644 --- a/omega_miya/plugins/zhoushen_hime/__init__.py +++ b/omega_miya/plugins/zhoushen_hime/__init__.py @@ -11,7 +11,8 @@ """ from typing import Literal -from nonebot import on_command, on_notice, logger +from nonebot.log import logger +from nonebot.plugin import on_command, on_notice, PluginMetadata from nonebot.typing import T_State from nonebot.matcher import Matcher from nonebot.permission import SUPERUSER @@ -29,15 +30,15 @@ from .utils import ZhouChecker, download_file, upload_result_file -# Custom plugin usage text -__plugin_custom_name__ = '自动审轴姬' -__plugin_usage__ = r'''【自动审轴姬】 -检测群内上传文件并自动锤轴 -仅限群聊使用 - -用法: -仅限群管理员使用: -/审轴姬 ''' +__plugin_meta__ = PluginMetadata( + name="自动审轴姬", + description="【自动审轴姬插件】\n" + "检测群内上传文件并自动锤轴\n" + "仅限群聊使用", + usage="仅限群管理员使用:\n" + "/审轴姬 ", + extra={"author": "Ailitonia"}, +) _ZHOUSHEN_HIME_CUSTOM_MODULE_NAME: Literal['Omega.ZhoushenHime'] = 'Omega.ZhoushenHime' diff --git a/omega_miya/service/gocqhttp_guild_patch/__init__.py b/omega_miya/service/gocqhttp_guild_patch/__init__.py index 764c7414..99905023 100644 --- a/omega_miya/service/gocqhttp_guild_patch/__init__.py +++ b/omega_miya/service/gocqhttp_guild_patch/__init__.py @@ -3,8 +3,15 @@ from nonebot.adapters.onebot.v11 import Bot, Event, Message, MessageSegment from nonebot.log import logger -from .models import (GuildMessageEvent, GuildChannelRecallNoticeEvent, MessageReactionsUpdatedNoticeEvent, - ChannelUpdatedNoticeEvent, ChannelCreatedNoticeEvent, ChannelDestroyedNoticeEvent) +from .models import ( + ChannelCreatedNoticeEvent, + ChannelDestroyedNoticeEvent, + ChannelNoticeEvent, + ChannelUpdatedNoticeEvent, + GuildChannelRecallNoticeEvent, + GuildMessageEvent, + MessageReactionsUpdatedNoticeEvent, +) from .permission import GUILD, GUILD_SUPERUSER original_send = Bot.send @@ -47,12 +54,13 @@ async def patched_send( __all__ = [ - 'GUILD', - 'GUILD_SUPERUSER', - 'GuildMessageEvent', - 'GuildChannelRecallNoticeEvent', - 'MessageReactionsUpdatedNoticeEvent', - 'ChannelUpdatedNoticeEvent', - 'ChannelCreatedNoticeEvent', - 'ChannelDestroyedNoticeEvent' + "GUILD", + "GUILD_SUPERUSER", + "GuildMessageEvent", + "ChannelNoticeEvent", + "GuildChannelRecallNoticeEvent", + "MessageReactionsUpdatedNoticeEvent", + "ChannelUpdatedNoticeEvent", + "ChannelCreatedNoticeEvent", + "ChannelDestroyedNoticeEvent", ] diff --git a/omega_miya/service/gocqhttp_guild_patch/models.py b/omega_miya/service/gocqhttp_guild_patch/models.py index 4486a7b1..9acc2a88 100644 --- a/omega_miya/service/gocqhttp_guild_patch/models.py +++ b/omega_miya/service/gocqhttp_guild_patch/models.py @@ -1,4 +1,3 @@ -import inspect from typing import List, Optional, Type, TypeVar from nonebot.adapters.onebot.v11 import ( @@ -6,14 +5,14 @@ Event, Message, MessageEvent, + MessageSegment, NoticeEvent, - MessageSegment ) +from nonebot.exception import NoLogException from nonebot.log import logger from nonebot.typing import overrides from nonebot.utils import escape_tag -from nonebot.exception import NoLogException -from pydantic import BaseModel, Field, parse_obj_as, validator, root_validator +from pydantic import BaseModel, Field, parse_obj_as, root_validator, validator from typing_extensions import Literal from .config import guild_patch_config @@ -25,7 +24,7 @@ def register_event(event: Event_T) -> Event_T: Adapter.add_custom_model(event) logger.opt(colors=True).debug( f"Custom event {event.__qualname__!r} registered " - f"from module {inspect.getmodule(event).__name__!r}" + f"from module {event.__class__.__module__!r}" ) return event @@ -33,6 +32,7 @@ def register_event(event: Event_T) -> Event_T: @register_event class GuildMessageEvent(MessageEvent): """收到频道消息""" + message_type: Literal["guild"] self_tiny_id: int message_id: str @@ -42,35 +42,38 @@ class GuildMessageEvent(MessageEvent): raw_message: str = Field(alias="message") font: None = None - @validator('raw_message', pre=True) + @validator("raw_message", pre=True) def _validate_raw_message(cls, raw_message): if isinstance(raw_message, str): return raw_message elif isinstance(raw_message, list): return str(parse_obj_as(Message, raw_message)) - raise ValueError('unknown raw message type') + raise ValueError("unknown raw message type") @root_validator(pre=False) def _validate_is_tome(cls, values): - message = values.get('message') - self_tiny_id = values.get('self_tiny_id') + message = values.get("message") + self_tiny_id = values.get("self_tiny_id") message, is_tome = cls._check_at_me(message=message, self_tiny_id=self_tiny_id) - values.update({'message': message, 'to_me': is_tome, 'raw_message': str(message)}) + values.update( + {"message": message, "to_me": is_tome, "raw_message": str(message)} + ) return values @overrides(Event) def is_tome(self) -> bool: return self.to_me or any( - str(msg_seg.data.get('qq', '')) == str(self.self_tiny_id) + str(msg_seg.data.get("qq", "")) == str(self.self_tiny_id) for msg_seg in self.message - if msg_seg.type == 'at' + if msg_seg.type == "at" ) @overrides(Event) def get_event_description(self) -> str: return ( - f'Message {self.message_id} from {self.user_id}@[频道:{self.guild_id}/子频道:{self.channel_id}] "' - + "".join( + f"Message {self.message_id} from " + f'{self.user_id}@[Guild:{self.guild_id}/Channel:{self.channel_id}] "%s"' + % "".join( map( lambda x: escape_tag(str(x)) if x.is_text() @@ -78,7 +81,6 @@ def get_event_description(self) -> str: self.message, ) ) - + '"' ) def get_log_string(self) -> str: @@ -92,15 +94,17 @@ def get_session_id(self) -> str: return f"guild_{self.guild_id}_channel_{self.channel_id}_{self.user_id}" @staticmethod - def _check_at_me(message: Message, self_tiny_id: int | str) -> tuple[Message, bool]: - """检查消息开头或结尾是否存在 @机器人,去除并赋值 ``event.to_me``""" + def _check_at_me(message: Message, self_tiny_id: int) -> tuple[Message, bool]: + """检查消息开头或结尾是否存在 @机器人,去除并赋值 event.to_me""" is_tome = False # ensure message not empty if not message: message.append(MessageSegment.text("")) def _is_at_me_seg(segment: MessageSegment): - return segment.type == 'at' and str(segment.data.get('qq', '')) == str(self_tiny_id) + return segment.type == "at" and str(segment.data.get("qq", "")) == str( + self_tiny_id + ) # check the first segment if _is_at_me_seg(message[0]): @@ -113,9 +117,7 @@ def _is_at_me_seg(segment: MessageSegment): if message and _is_at_me_seg(message[0]): message.pop(0) if message and message[0].type == "text": - message[0].data["text"] = ( - message[0].data["text"].lstrip() - ) + message[0].data["text"] = message[0].data["text"].lstrip() if not message[0].data["text"]: del message[0] @@ -124,9 +126,9 @@ def _is_at_me_seg(segment: MessageSegment): i = -1 last_msg_seg = message[i] if ( - last_msg_seg.type == "text" - and not last_msg_seg.data["text"].strip() - and len(message) >= 2 + last_msg_seg.type == "text" + and not last_msg_seg.data["text"].strip() + and len(message) >= 2 ): i -= 1 last_msg_seg = message[i] @@ -156,12 +158,12 @@ class Config: @register_event class ChannelNoticeEvent(NoticeEvent): """频道通知事件""" + notice_type: Literal["channel"] self_tiny_id: int guild_id: int channel_id: int user_id: int - sub_type: None = None def get_log_string(self) -> str: @@ -174,6 +176,7 @@ def get_log_string(self) -> str: @register_event class GuildChannelRecallNoticeEvent(ChannelNoticeEvent): """频道消息撤回""" + notice_type: Literal["guild_channel_recall"] operator_id: int message_id: str @@ -188,6 +191,7 @@ def get_log_string(self) -> str: @register_event class MessageReactionsUpdatedNoticeEvent(ChannelNoticeEvent): """频道消息表情贴更新""" + notice_type: Literal["message_reactions_updated"] message_id: str current_reactions: Optional[List[ReactionInfo]] = None @@ -229,6 +233,7 @@ class Config: @register_event class ChannelUpdatedNoticeEvent(ChannelNoticeEvent): """子频道信息更新""" + notice_type: Literal["channel_updated"] operator_id: int old_info: ChannelInfo @@ -244,6 +249,7 @@ def get_log_string(self) -> str: @register_event class ChannelCreatedNoticeEvent(ChannelNoticeEvent): """子频道创建""" + notice_type: Literal["channel_created"] operator_id: int channel_info: ChannelInfo @@ -258,6 +264,7 @@ def get_log_string(self) -> str: @register_event class ChannelDestroyedNoticeEvent(ChannelNoticeEvent): """子频道删除""" + notice_type: Literal["channel_destroyed"] operator_id: int channel_info: ChannelInfo @@ -270,14 +277,14 @@ def get_log_string(self) -> str: __all__ = [ - 'GuildMessageEvent', - 'ChannelNoticeEvent', - 'GuildChannelRecallNoticeEvent', - 'MessageReactionsUpdatedNoticeEvent', - 'ChannelUpdatedNoticeEvent', - 'ChannelCreatedNoticeEvent', - 'ChannelDestroyedNoticeEvent', - 'ReactionInfo', - 'SlowModeInfo', - 'ChannelInfo' + "GuildMessageEvent", + "ChannelNoticeEvent", + "GuildChannelRecallNoticeEvent", + "MessageReactionsUpdatedNoticeEvent", + "ChannelUpdatedNoticeEvent", + "ChannelCreatedNoticeEvent", + "ChannelDestroyedNoticeEvent", + "ReactionInfo", + "SlowModeInfo", + "ChannelInfo", ] diff --git a/omega_miya/service/gocqhttp_guild_patch/permission.py b/omega_miya/service/gocqhttp_guild_patch/permission.py index de713735..67a84059 100644 --- a/omega_miya/service/gocqhttp_guild_patch/permission.py +++ b/omega_miya/service/gocqhttp_guild_patch/permission.py @@ -8,8 +8,9 @@ @Software : PyCharm """ -from nonebot.permission import Permission from nonebot.adapters.onebot.v11.bot import Bot +from nonebot.permission import Permission + from .models import GuildMessageEvent @@ -18,9 +19,11 @@ async def _guild(event: GuildMessageEvent) -> bool: async def _guild_superuser(bot: Bot, event: GuildMessageEvent) -> bool: - return (f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}" - in bot.config.superusers - or event.get_user_id() in bot.config.superusers) # 兼容旧配置 + return ( + f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}" + in bot.config.superusers + or event.get_user_id() in bot.config.superusers + ) # 兼容旧配置 GUILD: Permission = Permission(_guild) @@ -29,7 +32,4 @@ async def _guild_superuser(bot: Bot, event: GuildMessageEvent) -> bool: """匹配任意超级用户频道消息类型事件""" -__all__ = [ - 'GUILD', - 'GUILD_SUPERUSER' -] +__all__ = ["GUILD", "GUILD_SUPERUSER"] \ No newline at end of file diff --git a/omega_miya/service/omega_processor/history.py b/omega_miya/service/omega_processor/history.py index 19b9294e..30dc3590 100644 --- a/omega_miya/service/omega_processor/history.py +++ b/omega_miya/service/omega_processor/history.py @@ -24,8 +24,8 @@ async def postprocessor_history(event: Event): self_id = str(event.self_id) event_type = getattr(event, 'post_type', 'Undefined') message_id = getattr(event, 'message_id', -1) - raw_data = repr(event) - message_data = str(getattr(event, 'message', '')) + raw_data = event.json() + message_data = getattr(event, 'message', '') raw_data = str(raw_data) if not isinstance(raw_data, str) else raw_data msg_data = str(message_data) if not isinstance(message_data, str) else message_data diff --git a/omega_miya/service/omega_processor/plugin.py b/omega_miya/service/omega_processor/plugin.py index 7e72fff8..8b9cddc2 100644 --- a/omega_miya/service/omega_processor/plugin.py +++ b/omega_miya/service/omega_processor/plugin.py @@ -24,10 +24,11 @@ async def startup_init_plugins(): - tasks = [Plugin( - plugin_name=plugin.name, - module_name=plugin.module_name - ).add_only(info=getattr(plugin.module, '__plugin_custom_name__', None)) for plugin in get_loaded_plugins()] + tasks = [ + Plugin(plugin_name=plugin.name, module_name=plugin.module_name).add_only( + info=(plugin.metadata.name if plugin.metadata else None) + ) for plugin in get_loaded_plugins() + ] plugins_init_result = await semaphore_gather(tasks=tasks, semaphore_num=1) diff --git a/omega_miya/service/omega_processor/statistic.py b/omega_miya/service/omega_processor/statistic.py index 4d847122..ef605b3c 100644 --- a/omega_miya/service/omega_processor/statistic.py +++ b/omega_miya/service/omega_processor/statistic.py @@ -45,10 +45,10 @@ async def postprocessor_statistic(matcher: Matcher, bot: Bot, event: Event): return # 跳过没有配置自定义名称的(一般来说这样的插件也不用展示统计信息) - custom_plugin_name = getattr(matcher.plugin.module, '__plugin_custom_name__', None) - if custom_plugin_name is None: - logger.opt(colors=True).debug(f'{_log_prefix}Non-custom-name plugin, ignore') + if matcher.plugin.metadata is None: + logger.opt(colors=True).debug(f'{_log_prefix}Non-metadata plugin, ignore') return + custom_plugin_name = matcher.plugin.metadata.name # 从 state 中解析 processor 配置要求 module_name = matcher.plugin.module_name diff --git a/omega_miya/utils/message_tools/__init__.py b/omega_miya/utils/message_tools/__init__.py index 5f6444b9..ed9813af 100644 --- a/omega_miya/utils/message_tools/__init__.py +++ b/omega_miya/utils/message_tools/__init__.py @@ -14,7 +14,7 @@ from nonebot.log import logger from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.message import Message, MessageSegment -from nonebot.adapters.onebot.v11.event import Event +from nonebot.adapters.onebot.v11.event import Event, GroupMessageEvent, PrivateMessageEvent from omega_miya.onebot_api import GoCqhttpBot from omega_miya.database import EventEntityHelper @@ -142,6 +142,10 @@ async def send_group_node_custom_and_recall( message_list=message_list) sent_result = await self.bot.send_group_forward_msg(group_id=group_id, messages=node_message) + + if recall_time <= 0: + return sent_result.message_id + await asyncio.sleep(recall_time) await self.bot.delete_msg(message_id=sent_result.message_id) return sent_result.message_id @@ -177,6 +181,10 @@ async def send_private_node_custom_and_recall( message_list=message_list) sent_result = await self.bot.send_private_forward_msg(user_id=user_id, messages=node_message) + + if recall_time <= 0: + return sent_result.message_id + await asyncio.sleep(recall_time) await self.bot.delete_msg(message_id=sent_result.message_id) return sent_result.message_id @@ -187,6 +195,7 @@ async def send_node_custom( message_list: list[str | Message | MessageSegment], user_id: int | str | None = None, group_id: int | str | None = None, + event: Event | None = None, *, custom_nickname: str = 'Ωμεγα_Μιγα' ) -> int: @@ -195,7 +204,13 @@ async def send_node_custom( node_message = self._construct_node_custom(custom_user_id=self.bot.self_id, custom_nickname=custom_nickname, message_list=message_list) - sent_result = await self.bot.send_forward_msg(user_id=user_id, group_id=group_id, messages=node_message) + if isinstance(event, GroupMessageEvent): + sent_result = await self.bot.send_group_forward_msg(group_id=event.group_id, messages=node_message) + elif isinstance(event, PrivateMessageEvent): + sent_result = await self.bot.send_private_forward_msg(user_id=event.user_id, messages=node_message) + else: + sent_result = await self.bot.send_forward_msg(user_id=user_id, group_id=group_id, messages=node_message) + return sent_result.message_id @run_async_catching_exception @@ -204,6 +219,7 @@ async def send_node_custom_and_recall( message_list: list[str | Message | MessageSegment], user_id: int | str | None = None, group_id: int | str | None = None, + event: Event | None = None, *, recall_time: int = 30, custom_nickname: str = 'Ωμεγα_Μιγα' @@ -213,7 +229,16 @@ async def send_node_custom_and_recall( node_message = self._construct_node_custom(custom_user_id=self.bot.self_id, custom_nickname=custom_nickname, message_list=message_list) - sent_result = await self.bot.send_forward_msg(user_id=user_id, group_id=group_id, messages=node_message) + if isinstance(event, GroupMessageEvent): + sent_result = await self.bot.send_group_forward_msg(group_id=event.group_id, messages=node_message) + elif isinstance(event, PrivateMessageEvent): + sent_result = await self.bot.send_private_forward_msg(user_id=event.user_id, messages=node_message) + else: + sent_result = await self.bot.send_forward_msg(user_id=user_id, group_id=group_id, messages=node_message) + + if recall_time <= 0: + return sent_result.message_id + await asyncio.sleep(recall_time) await self.bot.delete_msg(message_id=sent_result.message_id) return sent_result.message_id @@ -243,6 +268,9 @@ async def send_msgs_and_recall( logger.opt(colors=True).debug(f'MessageSender| Message({", ".join(str(x) for x in sent_msg_id)}) ' f'will be auto-recalled after {recall_time} seconds') + if recall_time <= 0: + return sent_msg_id + await asyncio.sleep(recall_time) delete_tasks = [self.bot.delete_msg(message_id=msg_id) for msg_id in sent_msg_id] diff --git a/omega_miya/web_resource/bilibili/model/dynamic.py b/omega_miya/web_resource/bilibili/model/dynamic.py index 894dfa15..046dd35e 100644 --- a/omega_miya/web_resource/bilibili/model/dynamic.py +++ b/omega_miya/web_resource/bilibili/model/dynamic.py @@ -176,7 +176,7 @@ def output_std_model(self) -> _StdCardOutputData: class CardType16ShortVideo(_BaseCardType): - """Bilibili 动态 Card Type 16 小视频动态(大概已废弃)""" + """Bilibili 动态 Card Type 16 小视频动态 (可能已废弃)""" class _Item(BaseBilibiliModel): """内部内容信息字段""" @@ -195,7 +195,7 @@ def output_std_model(self) -> _StdCardOutputData: class CardType32Anime(_BaseCardType): - """Bilibili 动态 Card Type 32 番剧动态(大概已废弃)""" + """Bilibili 动态 Card Type 32 番剧动态 (可能已废弃)""" verify_type: int = 32 title: str dynamic: str @@ -271,7 +271,7 @@ def output_std_model(self) -> _StdCardOutputData: class CardType512Anime(_BaseCardType): - """Bilibili 动态 Card Type 512 番剧更新动态(已废弃)""" + """Bilibili 动态 Card Type 512 番剧更新动态 (可能已废弃)""" class _ApiSeasonInfo(BaseBilibiliModel): title: str @@ -317,7 +317,7 @@ def output_std_model(self) -> _StdCardOutputData: class CardType4200LiveRoom(_BaseCardType): - """Bilibili 动态 Card Type 4200 直播间动态(疑似)""" + """Bilibili 动态 Card Type 4200 直播间动态 (可能已废弃)""" verify_type: int = 4200 uname: str title: str @@ -333,8 +333,40 @@ def output_std_model(self) -> _StdCardOutputData: return _StdCardOutputData.parse_obj({'content': content, 'text': text, 'img_urls': [self.cover]}) +class CardType4300MediaListShare(_BaseCardType): + """Bilibili 动态 Card Type 4300 收藏夹/播放列表分享""" + + class _Upper(BaseBilibiliModel): + """内部上传用户信息字段""" + mid: int + name: str + face: AnyHttpUrl + + verify_type: int = 4300 + cover: AnyHttpUrl + cover_type: int + fid: int + id: int + intro: str + media_count: int + mid: int + sharable: bool + title: str + type: int + upper: _Upper + + @property + def user_name(self) -> str: + return self.upper.name + + def output_std_model(self) -> _StdCardOutputData: + content = f'《{self.title}》\n{self.intro}\n- 共{self.media_count}个内容' + text = f'{self.user_name}分享了收藏夹和播放列表!\n\n{content}\n' + return _StdCardOutputData.parse_obj({'content': content, 'text': text, 'img_urls': [self.cover]}) + + class CardType4308LiveRoom(_BaseCardType): - """Bilibili 动态 Card Type 4308 直播间动态(基本确定)(最近出现的)""" + """Bilibili 动态 Card Type 4308 直播间动态""" class _LivePlayInfo(BaseBilibiliModel): area_id: int @@ -398,6 +430,7 @@ class _Item(BaseBilibiliModel): Json[CardType512Anime] | Json[CardType2048Active] | Json[CardType4200LiveRoom] | + Json[CardType4300MediaListShare] | Json[CardType4308LiveRoom] )] # 被转发动态信息, 套娃, (注意多次转发后原动态一直是最开始的那个, 所以源动态类型不可能也是转发) origin_user: Optional[BilibiliDynamicCardDescUserProfile] # 被转发用户信息 @@ -432,6 +465,7 @@ class BilibiliDynamicCard(BaseBilibiliModel): Json[CardType512Anime] | Json[CardType2048Active] | Json[CardType4200LiveRoom] | + Json[CardType4300MediaListShare] | Json[CardType4308LiveRoom] ) diff --git a/omega_miya/web_resource/bilibili/model/user.py b/omega_miya/web_resource/bilibili/model/user.py index 06dbe87d..0a7afc22 100644 --- a/omega_miya/web_resource/bilibili/model/user.py +++ b/omega_miya/web_resource/bilibili/model/user.py @@ -34,7 +34,7 @@ class BilibiliUserDataModel(BaseBilibiliModel): sign: str level: int top_photo: AnyHttpUrl - live_room: BilibiliUserLiveRoom + live_room: Optional[BilibiliUserLiveRoom] is_senior_member: int diff --git a/omega_miya/web_resource/pixiv/helper.py b/omega_miya/web_resource/pixiv/helper.py index 78894e68..e1a9f165 100644 --- a/omega_miya/web_resource/pixiv/helper.py +++ b/omega_miya/web_resource/pixiv/helper.py @@ -153,8 +153,8 @@ def parse_pixivision_article_page(content: str, root_url: str) -> PixivisionArti article_title = article_main.find(name='h1', attrs={'class': 'am__title'}).get_text(strip=True) article_description = article_main.find( name='div', attrs={'class': 'am__description _medium-editor-text'}).get_text(strip=True) - article_eyecatch = article_main.find(name='img', attrs={'class': 'aie__image'}) - article_eyecatch_image = None if article_eyecatch is None else article_eyecatch.attrs.get('src') + article_eyecatch = article_main.find(name='div', attrs={'class': '_article-illust-eyecatch'}) + article_eyecatch_image = None if article_eyecatch is None else article_eyecatch.find('img').attrs.get('src') # 解析tag tags_list = [] diff --git a/omega_miya/web_resource/pixiv/pixiv.py b/omega_miya/web_resource/pixiv/pixiv.py index d8b2c169..5c2d92f8 100644 --- a/omega_miya/web_resource/pixiv/pixiv.py +++ b/omega_miya/web_resource/pixiv/pixiv.py @@ -3,7 +3,7 @@ import zipfile import imageio import asyncio -from datetime import datetime, timedelta +from datetime import datetime from typing import Literal, Optional from urllib.parse import urlparse, quote from io import BytesIO @@ -185,9 +185,8 @@ async def search( @classmethod async def search_by_default_popular_condition(cls, word: str) -> PixivSearchingResultModel: - """Pixiv 搜索 (使用通用的好图筛选条件) (近三年的图) (会用到仅限pixiv高级会员可用的部分参数)""" - start_date = datetime.now() - timedelta(days=1080) - return await cls.search(word=word, mode='illustrations', order='date_d', scd_=start_date, blt_=2500) + """Pixiv 搜索 (使用热度作为过滤条件筛选条件) (需要pixiv高级会员)""" + return await cls.search(word=word, mode='illustrations', order='popular_d', mode_='safe', type_='illust') @classmethod async def search_with_preview( diff --git a/requirements.txt b/requirements.txt index 4848aef0..60b45954 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,25 @@ -nonebot2==2.0.0b3 +nonebot2==2.0.0b5 nonebot-adapter-onebot==2.1.1 -fastapi==0.78.0 +fastapi==0.79.0 pydantic==1.9.1 APScheduler~=3.9.1 pytz~=2022.1 aiohttp~=3.8.1 -SQLAlchemy~=1.4.37 +SQLAlchemy~=1.4.40 asyncmy~=0.2.5 aiomysql~=0.1.1 aiofiles==0.8.0 -ujson~=5.3.0 +ujson~=5.4.0 beautifulsoup4~=4.11.1 bs4~=0.0.1 -lxml~=4.9.0 -numpy~=1.22.4 +lxml~=4.9.1 +numpy~=1.23.1 matplotlib~=3.5.2 -Pillow~=9.1.1 -imageio~=2.19.3 +Pillow~=9.2.0 +imageio~=2.21.1 msgpack~=1.0.4 -pycryptodome~=3.14.1 -py7zr~=0.18.9 +pycryptodome~=3.15.0 +py7zr~=0.20.0 zhconv~=1.4.3 -rapidfuzz~=2.0.11 -emoji~=1.7.0 \ No newline at end of file +rapidfuzz~=2.4.3 +emoji~=2.0.0 \ No newline at end of file