From 14d1512b1e3b523c2a4543ab6f8abf29532e475c Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Fri, 18 Mar 2022 20:55:05 +0800 Subject: [PATCH] update v0.1.4.4 --- .gitignore | 1 - README.md | 6 +- __version__ | 2 +- basic_plugins/hooks/_utils.py | 4 + basic_plugins/hooks/auth_hook.py | 19 ++-- basic_plugins/hooks/ban_hook.py | 36 ++++--- .../genshin/query_user/_models/__init__.py | 3 +- plugins/search_image/__init__.py | 91 ++++++++++++++++++ plugins/search_image/saucenao.py | 50 ++++++++++ plugins/statistics/statistics_handle.py | Bin 10825 -> 10825 bytes update_info.json | 3 +- 11 files changed, 186 insertions(+), 29 deletions(-) create mode 100644 plugins/search_image/__init__.py create mode 100644 plugins/search_image/saucenao.py diff --git a/.gitignore b/.gitignore index 42103025..53aa4128 100644 --- a/.gitignore +++ b/.gitignore @@ -142,7 +142,6 @@ test.py server_ip.py member_activity_handle.py Yu-Gi-Oh/ -search_image/ black_word/ csgo/ fantasy_card/ diff --git a/README.md b/README.md index 08eb1b49..c08eb97a 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,11 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__ ## 更新 +### 2022/3/18 \[v0.1.4.4] + +* 修复戳一戳无法功能关闭与ban禁用 +* 新增图片搜索 search_image + ### 2022/3/7 * 优化增删权限插件 @@ -301,7 +306,6 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__ * 适配nonebot.beta2 * 删除图片搜索 nonebot_plugin_picsearcher -* 新增图片搜索 search_image * 替换cos api * 原神签到树脂提醒新增绑定群里,在某群绑定uid就会在某群发送提醒信息(有好友则私聊,需要重新绑定uid * 修改update_info.json diff --git a/__version__ b/__version__ index b7aa5f58..804f21b7 100644 --- a/__version__ +++ b/__version__ @@ -1 +1 @@ -__version__: v0.1.4.3 \ No newline at end of file +__version__: v0.1.4.4 \ No newline at end of file diff --git a/basic_plugins/hooks/_utils.py b/basic_plugins/hooks/_utils.py index 55d08d08..8e7ab0e0 100644 --- a/basic_plugins/hooks/_utils.py +++ b/basic_plugins/hooks/_utils.py @@ -2,6 +2,10 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent from utils.manager import plugins2block_manager, StaticData import time +ignore_rst_module = ["ai", "poke", "dialogue"] + +other_limit_plugins = ["poke"] + class StatusMessageManager(StaticData): diff --git a/basic_plugins/hooks/auth_hook.py b/basic_plugins/hooks/auth_hook.py index ce012e6f..a379937a 100755 --- a/basic_plugins/hooks/auth_hook.py +++ b/basic_plugins/hooks/auth_hook.py @@ -13,7 +13,12 @@ from utils.manager import ( plugins2block_manager, plugins2count_manager, ) -from ._utils import set_block_limit_false, status_message_manager +from ._utils import ( + set_block_limit_false, + status_message_manager, + ignore_rst_module, + other_limit_plugins, +) from nonebot.typing import T_State from typing import Optional from nonebot.adapters.onebot.v11 import ( @@ -37,12 +42,10 @@ _flmt_g = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD")) _flmt_s = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD")) _flmt_c = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD")) -ignore_rst_module = ["ai", "poke", "dialogue"] - # 权限检测 @run_preprocessor -async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): +async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State): module = matcher.plugin_name plugins2info_dict = plugins2settings_manager.get_data() # 功能的金币检测 ####################################### @@ -64,7 +67,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): await BagUser.spend_gold(event.user_id, event.group_id, cost_gold) try: if ( - (not isinstance(event, MessageEvent) and module != "poke") + (not isinstance(event, MessageEvent) and module not in other_limit_plugins) or await BanUser.is_ban(event.user_id) and str(event.user_id) not in bot.config.superusers ) or ( @@ -88,7 +91,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): except AttributeError: pass # 群黑名单检测 群总开关检测 - if isinstance(event, GroupMessageEvent) or matcher.plugin_name == "poke": + if isinstance(event, GroupMessageEvent) or matcher.plugin_name == other_limit_plugins: try: if ( group_manager.get_group_level(event.group_id) < 0 @@ -145,9 +148,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): raise IgnoredException("权限不足") if module in plugins2info_dict.keys() and matcher.priority not in [1, 9]: # 戳一戳单独判断 - if isinstance(event, GroupMessageEvent) or ( - isinstance(event, PokeNotifyEvent) and event.group_id - ): + if isinstance(event, GroupMessageEvent) or isinstance(event, PokeNotifyEvent) or matcher.plugin_name in other_limit_plugins: if status_message_manager.get(event.group_id) is None: status_message_manager.delete(event.group_id) if plugins2info_dict[module]["level"] > group_manager.get_group_level( diff --git a/basic_plugins/hooks/ban_hook.py b/basic_plugins/hooks/ban_hook.py index 757a7ed6..4f6a85e9 100755 --- a/basic_plugins/hooks/ban_hook.py +++ b/basic_plugins/hooks/ban_hook.py @@ -4,13 +4,16 @@ from nonebot.adapters.onebot.v11.exception import ActionFailed from nonebot.typing import T_State from nonebot.adapters.onebot.v11 import ( Bot, + Event, MessageEvent, + PokeNotifyEvent, GroupMessageEvent, ) from configs.config import Config from models.ban_user import BanUser from utils.utils import is_number, static_flmt, FreqLimiter from utils.message_builder import at +from ._utils import ignore_rst_module, other_limit_plugins Config.add_plugin_config( @@ -25,18 +28,19 @@ _flmt = FreqLimiter(300) # 检查是否被ban @run_preprocessor -async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): - try: - if ( - await BanUser.is_super_ban(event.user_id) - and str(event.user_id) not in bot.config.superusers - ): - raise IgnoredException("用户处于超级黑名单中") - except AttributeError: - pass - if not isinstance(event, MessageEvent): - return - if matcher.type == "message" and matcher.priority not in [1, 9]: +async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State): + if ( + (isinstance(event, MessageEvent) or isinstance(event, PokeNotifyEvent)) + and matcher.priority not in [1, 9] + ) or matcher.plugin_name in other_limit_plugins: + try: + if ( + await BanUser.is_super_ban(event.user_id) + and str(event.user_id) not in bot.config.superusers + ): + raise IgnoredException("用户处于超级黑名单中") + except AttributeError: + pass if ( await BanUser.is_ban(event.user_id) and str(event.user_id) not in bot.config.superusers @@ -57,7 +61,11 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): if matcher.priority != 9: try: ban_result = Config.get_config("hook", "BAN_RESULT") - if ban_result and _flmt.check(event.user_id): + if ( + ban_result + and _flmt.check(event.user_id) + and matcher.plugin_name not in ignore_rst_module + ): _flmt.start_cd(event.user_id) await bot.send_group_msg( group_id=event.group_id, @@ -74,7 +82,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): if matcher.priority != 9: try: ban_result = Config.get_config("hook", "BAN_RESULT") - if ban_result: + if ban_result and matcher.plugin_name not in ignore_rst_module: await bot.send_private_msg( user_id=event.user_id, message=at(event.user_id) diff --git a/plugins/genshin/query_user/_models/__init__.py b/plugins/genshin/query_user/_models/__init__.py index 71d11a76..a60285e4 100644 --- a/plugins/genshin/query_user/_models/__init__.py +++ b/plugins/genshin/query_user/_models/__init__.py @@ -254,6 +254,7 @@ class Genshin(db.Model): query = cls.query.where(cls.today_query_uid.contains(str(uid))) x = await query.gino.first() if x: + await cls._add_query_uid(uid, uid) return x.cookie for u in [ x for x in await cls.query.order_by(db.func.random()).gino.all() if x.cookie @@ -356,7 +357,7 @@ class Genshin(db.Model): """ query = cls.query.where(cls.uid == cookie_uid).with_for_update() user = await query.gino.first() - await user.update(today_query_uid=cls.today_query_uid + f"{uid} ").apply() + await user.update(today_query_uid=user.today_query_uid + f"{uid} ").apply() @classmethod async def _get_user_data( diff --git a/plugins/search_image/__init__.py b/plugins/search_image/__init__.py new file mode 100644 index 00000000..7f296034 --- /dev/null +++ b/plugins/search_image/__init__.py @@ -0,0 +1,91 @@ +from nonebot.plugin import on_command +from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message +from nonebot.typing import T_State +from services.log import logger +from utils.utils import get_message_img +from utils.message_builder import custom_forward_msg +from nonebot.params import CommandArg, Arg, ArgStr, Depends +from .saucenao import get_saucenao_image + + +__zx_plugin_name__ = "识图" +__plugin_usage__ = """ +usage: + 识别图片 [二次元图片] + 指令: + 识图 [图片] +""".strip() +__plugin_des__ = "以图搜图,看破本源" +__plugin_cmd__ = ["识图"] +__plugin_type__ = ("一些工具",) +__plugin_version__ = 0.1 +__plugin_author__ = "HibiKier" +__plugin_settings__ = { + "level": 5, + "default_status": True, + "limit_superuser": False, + "cmd": ["识图"], +} +__plugin_configs__ = { + "MAX_FIND_IMAGE_COUNT": {"value": 3, "help": "识图返回的最大结果数", "default_value": 3}, + "API_KEY": { + "value": None, + "help": "Saucenao的API_KEY,通过 https://saucenao.com/user.php?page=search-api 注册获取", + }, +} + + +search_image = on_command("识图", block=True, priority=5) + + +async def get_image_info(mod: str, url: str): + if mod == "saucenao": + return await get_saucenao_image(url) + + +def parse_image(key: str): + async def _key_parser( + state: T_State, img: Message = Arg(key) + ): + if not get_message_img(img): + await search_image.reject_arg(key, "请发送要识别的图片!") + state[key] = img + return _key_parser + + +@search_image.handle() +async def _(bot: Bot, event: MessageEvent, state: T_State, arg: Message = CommandArg()): + msg = arg.extract_plain_text().strip() + if msg: + state["mod"] = msg + else: + state["mod"] = "saucenao" + if get_message_img(event.json()): + state["img"] = event.message + + +@search_image.got("img", prompt="图来!", parameterless=[Depends(parse_image("img"))]) +async def _( + bot: Bot, + event: MessageEvent, + state: T_State, + mod: str = ArgStr("mod"), + img: Message = Arg("img"), +): + img = get_message_img(img)[0] + await search_image.send("开始处理图片...") + msg = await get_image_info(mod, img) + if isinstance(msg, str): + await search_image.finish(msg, at_sender=True) + if isinstance(event, GroupMessageEvent): + await bot.send_group_forward_msg( + group_id=event.group_id, messages=custom_forward_msg(msg, bot.self_id) + ) + else: + for m in msg[1:]: + await search_image.send(m) + logger.info( + f"(USER {event.user_id}, GROUP " + f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" + f" 识图:" + img + ) diff --git a/plugins/search_image/saucenao.py b/plugins/search_image/saucenao.py new file mode 100644 index 00000000..add20a49 --- /dev/null +++ b/plugins/search_image/saucenao.py @@ -0,0 +1,50 @@ +from utils.http_utils import AsyncHttpx +from configs.config import Config +from configs.path_config import TEMP_PATH +from utils.message_builder import image +from typing import Union, List +import random + +API_URL_SAUCENAO = "https://saucenao.com/search.php" +API_URL_ASCII2D = "https://ascii2d.net/search/url/" +API_URL_IQDB = "https://iqdb.org/" + + +async def get_saucenao_image(url: str) -> Union[str, List[str]]: + api_key = Config.get_config("search_image", "API_KEY") + if not api_key: + return "Saucenao 缺失API_KEY!" + + params = { + "output_type": 2, + "api_key": api_key, + "testmode": 1, + "numres": 6, + "db": 999, + "url": url, + } + data = (await AsyncHttpx.post(API_URL_SAUCENAO, params=params)).json() + if data["header"]["status"] != 0: + return "Saucenao识图失败.." + data = data["results"] + data = ( + data + if len(data) < Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT") + else data[: Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT")] + ) + msg_list = [] + index = random.randint(0, 10000) + if await AsyncHttpx.download_file( + url, TEMP_PATH / f"saucenao_search_{index}.jpg" + ): + msg_list.append(image(TEMP_PATH / f"saucenao_search_{index}.jpg")) + for info in data: + similarity = info["header"]["similarity"] + tmp = f"相似度:{similarity}%\n" + for x in info["data"].keys(): + if x != "ext_urls": + tmp += f"{x}:{info['data'][x]}\n" + if "source" not in info["data"].keys(): + tmp += f'source:{info["data"]["ext_urls"][0]}\n' + msg_list.append(tmp[:-1]) + return msg_list diff --git a/plugins/statistics/statistics_handle.py b/plugins/statistics/statistics_handle.py index e83f4e18f50fa5a63c33a80e56efc1e92fb2f44b..029ab2c7f83630b8272aac414ed9f558ee10b948 100755 GIT binary patch literal 10825 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 GMe~2#>g^H$ literal 10825 zcmeHNTZ?B`3w0@UAn7#YA!1)e#$Ipdb-Z7&b>})oTXEi zqzOMr3nr$sG%Hw|gu`??jgry!*4SDPqER#}c$NnWKDd4HqQQA7ExPP_mX@;{JkO&E zzjm7^c;)=1$xhP|kMm$cSfNP9sUarmyoMJVaF`}zG0B5jR2)Jy`e8tScJ=1f@aI== zzS?E4-MD)FntX1FJ&Uqvnj4Il)z+?NlP=TG8on$9#1T_SAIfKg@=__{(T%8Rz)jUY z!vk96nSqt)O~5Dt#R^~+mlKiXmvUYdBAMhN6iw?Ir#PD7t)X_aoW*>5OB*K2kCLH) zR*a$Y?XB&tB0JjO-eUOZ*X1wrG+|NB=zDvM&xd?gup8;9jQJaB@k&}Iqib1~W=%8& zO%%iMm-A4`879$`hau}R@AS7HJ$>unPanPYM(~BvGX{7@|fp}f;%uc0Mi6GQt?OQRw&0}w${m?ku zh>xQ(F2cNsiZaK_&8*~RF&0ySB@_5qeI<(XY8gI_SAEBJya7m@P&L@zBFZ8!#4v{( zb$8$!Z(k@3M$J(yqMYYFEjwjwPlxzY91F;4dPCcSa~jVd zjpxg)ovBRkDsV1h>&04LISjoM?KQJZq?ss=dd9~N;&gbcr~L}P-E@jjb#P-5bNENq zk+BQRt3Ah~pW!UyV=+e$S0+V34F}x4hCx|EZ8`@X3Ev<_lKr%%7#yODjX708**q}> z3k!WTyZcPGtO%wA&&@U!QGqC;)~k;q+GRly46rlZE&817LbYb9>i%h^3xel{90Rj zi5v&*g1v#m^NRf#*)&OGf?&c6-y(;|&(A}O$QfEqv3d+GiOqG{^AhVW19x+KL3`0M zEj2|sh%)4)Xm$wai{6X`MidF!nd-G~m~?giY~?3~qmRv`!ceBqFiumPjDoC8Fn0*~ zoDa()&3rOVmsQk(kSO*}vUQRO#t2&&#-O&6X^`&Pn%OysKd z4(rN{Y6fD9(W_KlV;G%yYE*Fc0nYXRc>KkCr=R@k^!`IsR3xnkoheT+Jdv%17Ov%H zS`?9EW>Q4SkgE#l7&f)@$x?})e)c&;c=FYQ(|6uh_2(z|rm6^fVZ zAqsM>`9&QnAZl;^=SozW0@kGwOw-GDRfB;_ZF?B{6oPKm6@_cpWsQxx?7(hSW6j!u zjwE5BBnP!Ef9H<=c6X2nDjGcq3(77eLDEXrD~Bo774 zH>t}@UG?n#-_CyX)#=?wm4{;bqVsDq78|k`W#YOw$6JRoxb#FU%nyL z5n)R2R>f)&XeED5OPzUCsR)KqHezV{8ZQ$uyhXq|2~6eDOEwIs;XI29ZZ+khm`;3B zk37e9rCeR6;=4=Dq*AR;0SmE7a#*Dv#Hh5>Uwy<}##PBzJElHK%2OuEzfY<$vgjX` zK>NrsxPvg>RnPq?6DjdwH}$YO&yfJSo9y&=&;jfWaQIZh>FWajb$%o-32?DOjwYWC zYsoOmas5C~RkH)`)Iw<^jOp+z53{YRV&x(mRVljF;3+eWy<^hOiF@8}Dz+>%hG)AL zSxn)I&SX9;vkcRKkWgjhm>ByW+f#d@-O@)Ak8?Uq_pP>xmO#8o2SY!LH-XT{rf{*# zE_K=EF1ymjRFA#bWqU26t*}re7^Aa~pu&?y$%Ok2zArG4-Lnm5ux$20G{XpBgc(5Q z3%f%#z-w51Bl>c1<4|FXr*jifGlIc4YV32Mq%XY3*urO1wQi1J}&ICnaV&|mA3r@{aVL!K_q?#qxO?XgE9NUKyM{BL- z09_`_qC9VO*bjcpOvvf|YZlKWI7*{Ev8D4B=}9t#Lg-K4$u+^`@oY2UY{MA2t)}6q zhfzE_y7|D)IYp#z2&%C?)^XpkYuB~9n*fhPyYQ6W^K-LWkre7G-);*w zsojot;CZwGwwlNQi)CLsd)qr}i?<89vbHHyX0VR}Yi`O&ZlqOeV9272S3CrudO7*O z)(qf^dz|kwTy^6ZG2S@_