diff --git a/LittlePaimon/config/config/model.py b/LittlePaimon/config/config/model.py index 252dc0f..ba10762 100644 --- a/LittlePaimon/config/config/model.py +++ b/LittlePaimon/config/config/model.py @@ -47,7 +47,7 @@ class ConfigModel(BaseModel): command_alias_enable: bool = Field(True, alias='启用命令别名') - github_proxy: str = Field('https://ghproxy.com/', alias='github资源地址') + github_proxy: str = Field('https://github.cherishmoon.fun/', alias='github资源地址') @property def alias_dict(self): diff --git a/LittlePaimon/config/plugin/manage.py b/LittlePaimon/config/plugin/manage.py index d10b934..9e4fb87 100644 --- a/LittlePaimon/config/plugin/manage.py +++ b/LittlePaimon/config/plugin/manage.py @@ -32,6 +32,14 @@ class PluginManager: if file.is_file() and file.name.endswith('.yml'): data = load_yaml(file) plugins[file.name.replace('.yml', '')] = PluginInfo.parse_obj(data) + # ------临时删除------ + if plugins.get('Paimon_Wiki'): + plugins['Paimon_Wiki'].matchers = [matcher for matcher in plugins['Paimon_Wiki'].matchers + if matcher.pm_name not in {'圣遗物图鉴', '每日材料', '原魔图鉴', '参考面板', + '武器图鉴', + '收益曲线', '角色材料', '角色攻略', + '七圣召唤图鉴', + '角色图鉴'}] @classmethod def save(cls): @@ -73,16 +81,15 @@ class PluginManager: }) else: cls.plugins[plugin.name] = PluginInfo(name=plugin.name, module_name=plugin.name) + if cls.plugins[plugin.name].matchers is None: + cls.plugins[plugin.name].matchers = [] matchers = plugin.matcher for matcher in matchers: if matcher._default_state: - matcher_info = MatcherInfo.parse_obj(matcher._default_state) - if cls.plugins[plugin.name].matchers is not None and matcher_info.pm_name not in [m.pm_name for - m - in - cls.plugins[ - plugin.name].matchers]: - cls.plugins[plugin.name].matchers.append(matcher_info) + with contextlib.suppress(Exception): + matcher_info = MatcherInfo.parse_obj(matcher._default_state) + if matcher_info.pm_name not in [m.pm_name for m in cls.plugins[plugin.name].matchers]: + cls.plugins[plugin.name].matchers.append(matcher_info) cls.save() logger.success('插件管理器', '初始化完成') diff --git a/LittlePaimon/plugins/Genshin_Voice/__init__.py b/LittlePaimon/plugins/Genshin_Voice/__init__.py index 433c6a6..81cf7ed 100644 --- a/LittlePaimon/plugins/Genshin_Voice/__init__.py +++ b/LittlePaimon/plugins/Genshin_Voice/__init__.py @@ -77,7 +77,7 @@ async def _(event: Union[GroupMessageEvent, PrivateMessageEvent], lang=CommandLa voice = await GenshinVoice.get_or_none(id=int(msg)) await get_voice.finish(MessageBuild.Record(voice.voice_url) if voice else MessageBuild.Text(f'没有{msg}号原神语音')) else: - if chara := get_match_alias(msg, '角色', True): + if chara := get_match_alias(msg, ['角色'], True): chara = list(chara.keys())[0] else: await get_voice.finish(MessageBuild.Text(f'没有叫{chara}的角色')) diff --git a/LittlePaimon/plugins/Paimon_Wiki/__init__.py b/LittlePaimon/plugins/Paimon_Wiki/__init__.py index 728d548..23a7e2b 100644 --- a/LittlePaimon/plugins/Paimon_Wiki/__init__.py +++ b/LittlePaimon/plugins/Paimon_Wiki/__init__.py @@ -1,10 +1,11 @@ import datetime +import re from nonebot import on_regex, on_command from nonebot.adapters.onebot.v11 import MessageEvent, Message, MessageSegment, GroupMessageEvent, PrivateMessageEvent, \ Bot from nonebot.adapters.onebot.v11.exception import ActionFailed -from nonebot.adapters.onebot.v11.helpers import HandleCancellation +from nonebot.adapters.onebot.v11.helpers import HandleCancellation, convert_chinese_to_bool from nonebot.params import RegexDict, ArgPlainText, CommandArg, Arg from nonebot.permission import SUPERUSER from nonebot.plugin import PluginMetadata @@ -17,29 +18,18 @@ from LittlePaimon.utils.alias import get_match_alias from LittlePaimon.utils.message import MessageBuild, fullmatch_rule from LittlePaimon.utils.path import RESOURCE_BASE_PATH from LittlePaimon.utils.tool import freq_limiter +from LittlePaimon.utils.typing import COMMAND_START_RE from .draw_daily_material import draw_material from .draw_map import init_map, draw_map, get_full_map from .SereniteaPot import draw_pot_materials -from .card import get_match_card, CARD_API, get_card_resources +from .card import get_match_card, get_card_resources +from .wiki_api import API -__paimon_help__ = { - 'type': '原神Wiki', - 'range': ['private', 'group', 'guild'] -} - -help_msg = """"1.[xx角色攻略]查看西风驿站出品的角色一图流攻略\n" -"2.[xx角色材料]查看惜月出品的角色材料统计\n" -"3.[xx参考面板]查看blue菌hehe出品的参考面板攻略\n" -"4.[xx收益曲线]查看blue菌hehe出品的收益曲线攻略\n" -"5.[今日/明日/周x材料]查看每日角色天赋材料和武器突破材料表\n" -"6.[xx武器攻略]查看武器攻略\n" -"7.[xx原魔图鉴]查看原魔图鉴\n" -""" __plugin_meta__ = PluginMetadata( name='原神Wiki', description='原神WIKI百科', - usage=help_msg, + usage='', extra={ 'author': '惜月', 'version': '3.0', @@ -47,74 +37,52 @@ __plugin_meta__ = PluginMetadata( } ) -daily_material = on_regex(r'^(?P现在|(今|明|后)(天|日)|周(一|二|三|四|五|六|日))(天赋|角色|武器)?材料$', - priority=11, block=True, state={ - 'pm_name': '每日材料', - 'pm_description': '查看某日开放材料刷取的角色和武器', - 'pm_usage': '<今天|周几>材料', - 'pm_priority': 8 +cancel = [HandleCancellation(f'好吧,有需要再找{NICKNAME}')] + +total_wiki = on_regex( + COMMAND_START_RE + r'(?P\w{0,7})(?P(材料|图鉴|攻略|参考面板|收益曲线))(?P\w{0,7})', + priority=12, + block=True, + state={ + 'pm_name': '原神WIKI', + 'pm_description': '支持查询:角色的图鉴、攻略、材料、参考面板、收益曲线,武器、圣遗物、原魔、七圣召唤图鉴,今日|周几材料' + '\n示例:钟离攻略、护摩图鉴、今日材料', + 'pm_usage': '<对象名><图鉴|攻略|材料>', + 'pm_priority': 1 }) material_map = on_command('材料图鉴', priority=11, block=True, state={ 'pm_name': '材料图鉴', 'pm_description': '查看某个材料的介绍和采集点。', 'pm_usage': '材料图鉴<材料名>[地图]', - 'pm_priority': 9 + 'pm_priority': 2 }) material_map_full = on_command('材料地图', priority=11, block=True, state={ 'pm_name': '材料地图', 'pm_description': '查看多个材料大地图采集点。\n示例:材料地图 鸣草 鬼兜虫 提瓦特', 'pm_usage': '材料地图<材料名列表>[地图]', - 'pm_priority': 10 + 'pm_priority': 3 }) generate_map = on_command('生成地图', priority=1, block=True, permission=SUPERUSER, state={ 'pm_name': '生成地图', 'pm_description': '生成材料图鉴等所需要的地图资源,仅超级用户可用。', 'pm_usage': '生成地图', - 'pm_priority': 11 + 'pm_priority': 4 }) pot_material = on_command('尘歌壶摹本', aliases={'摹本材料', '尘歌壶材料', '尘歌壶摹本材料'}, priority=11, block=True, state={ 'pm_name': '尘歌壶摹本材料', 'pm_description': '查看尘歌壶摹本所需要的材料总览', 'pm_usage': '尘歌壶材料<摹数>', - 'pm_priority': 12 + 'pm_priority': 5 }) -card_wiki = on_command('七圣召唤图鉴', aliases={'原牌图鉴', '原石传说图鉴', '原牌'}, priority=11, block=True, state={ - 'pm_name': '七圣召唤图鉴', - 'pm_description': '查看七圣召唤图鉴,支持模糊查询', - 'pm_usage': '七圣召唤图鉴<卡牌名>', - 'pm_priority': 13 -}) card_wiki_list = on_command('七圣召唤列表', aliases={'七圣召唤卡牌列表', '原牌列表', '原石传说列表'}, priority=11, rule=fullmatch_rule, block=True, state={ 'pm_name': '七圣召唤图鉴列表', - 'pm_description': '查看七圣召唤卡牌图鉴列表', + 'pm_description': '查看已支持查询的七圣召唤卡牌图鉴列表', 'pm_usage': '原牌列表', - 'pm_priority': 14 + 'pm_priority': 6 }) -week_str = ['周一', '周二', '周三', '周四', '周五', '周六'] - - -@daily_material.handle() -async def _(event: MessageEvent, regex_dict: dict = RegexDict()): - if regex_dict['day'] in {'今日', '今天', '现在'}: - day = datetime.datetime.now().weekday() - elif regex_dict['day'] in {'明日', '明天'}: - day = (datetime.datetime.now() + datetime.timedelta(days=1)).weekday() - elif regex_dict['day'] in {'后日', '后天'}: - day = (datetime.datetime.now() + datetime.timedelta(days=2)).weekday() - elif regex_dict['day'] == '周日': - await daily_material.finish('周日所有材料都可以刷哦!', at_sender=True) - elif regex_dict['day'].startswith('周'): - await daily_material.send('开始获取每日材料,请稍候...') - await daily_material.finish(await draw_material(str(event.user_id), regex_dict['day'])) - if day == 6: - await daily_material.finish('周日所有材料都可以刷哦!', at_sender=True) - else: - await daily_material.send('开始获取每日材料,请稍候...') - await daily_material.finish(await draw_material(str(event.user_id), week_str[day]), at_sender=True) - @material_map.handle() async def _(event: MessageEvent, state: T_State, msg: Message = CommandArg()): @@ -131,7 +99,7 @@ async def _(event: MessageEvent, state: T_State, msg: Message = CommandArg()): @material_map.got('map', prompt='地图名称有误,请在【提瓦特、层岩巨渊、渊下宫】中选择,或回答【取消】退出', - parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) + parameterless=cancel) async def _(event: MessageEvent, state: T_State, map_: str = ArgPlainText('map')): if map_ not in {'提瓦特', '层岩巨渊', '渊下宫'}: await material_map.reject('地图名称有误,请在【提瓦特、层岩巨渊、渊下宫】中选择') @@ -140,7 +108,7 @@ async def _(event: MessageEvent, state: T_State, map_: str = ArgPlainText('map') @material_map.got('name', prompt='请输入要查询的材料名称,或回答【取消】退出', - parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) + parameterless=cancel) async def _(event: MessageEvent, map_: str = ArgPlainText('map'), name: str = ArgPlainText('name')): if (file_path := RESOURCE_BASE_PATH / 'genshin_map' / 'results' / f'{map_}_{name}.png').exists(): await material_map.finish(MessageSegment.image(file_path), at_sender=True) @@ -163,7 +131,7 @@ async def _(event: MessageEvent, state: T_State, msg: Message = CommandArg()): @material_map_full.got('names', prompt='请输入要查询的材料名称,或回答【取消】退出', - parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) + parameterless=cancel) async def _(event: MessageEvent, map_: str = Arg('map'), names=Arg('names')): if isinstance(names, Message): names = names.extract_plain_text().split(' ') @@ -179,11 +147,15 @@ async def _(event: MessageEvent, map_: str = Arg('map'), names=Arg('names')): await material_map_full.finish(result, at_sender=True) -@generate_map.handle() +@generate_map.got('confirm', + prompt=f'生成材料地图资源要求大量内存,如果内存不足,可能会导致{NICKNAME}崩溃,请确认你的机器内存充足,回答【是|否】继续执行!') async def _(event: MessageEvent): - await generate_map.send('开始生成地图资源,这可能需要较长时间。') - result = await init_map() - await generate_map.finish(result) + if convert_chinese_to_bool(event.message): + await generate_map.send('开始生成地图资源,这可能需要较长时间...') + result = await init_map() + await generate_map.finish(result) + else: + await generate_map.finish('取消生成地图资源!') @pot_material.handle() @@ -200,173 +172,160 @@ async def _(event: MessageEvent, msg: Message = CommandArg()): await pot_material.finish(result, at_sender=True) -def create_wiki_matcher(pattern: str, help_fun: str, help_name: str): - maps = on_regex(pattern, priority=11, block=True, state={ - 'pm_name': help_fun, - 'pm_description': f"查看该{help_name}的{help_fun}", - 'pm_usage': f'<{help_name}名> {help_fun}', - 'pm_priority': 5 - }) - maps.plugin_name = 'Paimon_Wiki' - - @maps.handle() - async def _(event: MessageEvent, state: T_State, regex_dict: dict = RegexDict()): - if regex_dict['name1'] and regex_dict['name2']: - await maps.finish() - name = regex_dict['name1'] or regex_dict['name2'] - state['type'] = regex_dict['type'] - if '武器' in state['type']: - state['type'] = '武器' - # state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/WeaponMaps/{}.jpg' - state['img_url'] = '{}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/weapon/{}.png' - elif '圣遗物' in state['type']: - state['type'] = '圣遗物' - state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/ArtifactMaps/{}.jpg' - elif '怪物' in state['type'] or '原魔' in state['type']: - state['type'] = '原魔' - state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/MonsterMaps/{}.jpg' - elif state['type'] == '角色攻略': - state['type'] = '角色' - state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/XFGuide/{}.jpg' - elif state['type'] == '角色材料': - state['type'] = '角色' - # state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/RoleMaterials/{}材料.jpg' - state[ - 'img_url'] = '{}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/material%20for%20role/{}.png' - elif state['type'] == '角色图鉴': - state['type'] = '角色' - state[ - 'img_url'] = '{}https://raw.githubusercontent.com/CMHopeSunshine/GenshinWikiMap/master/results/character_map/{}.jpg' - elif state['type'] == '收益曲线': - state['type'] = '角色' - state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/blue/{}.jpg' - elif state['type'] == '参考面板': - state['type'] = '角色' - state['img_url'] = 'https://static.cherishmoon.fun/LittlePaimon/blueRefer/{}.jpg' - if name: - state['name'] = name - - @maps.got('name', prompt=Message.template('请提供要查询的{type}'), - parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) - async def _(event: MessageEvent, state: T_State): - name = state['name'] - if isinstance(name, Message): - name = name.extract_plain_text().strip() - if state['type'] == '角色' and ( - match_alias := await PlayerAlias.get_or_none(user_id=str(event.user_id), alias=name)): - a = '1' - try: - await maps.finish( - MessageSegment.image(state['img_url'].format(config.github_proxy, match_alias.character) if - not state['img_url'].startswith('http') else state['img_url'].format( - match_alias.character))) - except ActionFailed: - await maps.finish(MessageBuild.Text(f'没有找到{name}的图鉴')) - match_alias = get_match_alias(name, state['type']) - true_name = match_alias[0] if ( - isinstance(match_alias, list) and len(match_alias) == 1) else match_alias if isinstance(match_alias, - str) else None - if true_name: - try: - await maps.finish(MessageSegment.image(state['img_url'].format(config.github_proxy, match_alias) - if not state['img_url'].startswith('http') else state[ - 'img_url'].format(match_alias))) - except ActionFailed: - await maps.finish(MessageBuild.Text(f'没有找到{name}的图鉴')) - elif match_alias: - if isinstance(match_alias, dict): - match_alias = list(match_alias.keys()) - if 'choice' not in state: - msg = f'你要查询的{state["type"]}是:\n' - msg += '\n'.join([f'{int(i) + 1}. {name}' for i, name in enumerate(match_alias)]) - await maps.send(msg + '\n回答\"取消\"来取消查询', at_sender=True) - state['match_alias'] = match_alias +@total_wiki.handle() +async def _(state: T_State, regex_dict: dict = RegexDict()): + if regex_dict['name1'] and regex_dict['name2']: + await total_wiki.finish() + name = regex_dict['name1'] or regex_dict['name2'] or '' + type = regex_dict.get('type') + if type == '材料': + if name.endswith(('角色', '天赋', '培养')): + state['type'] = '角色材料' + name = name[:-2] + elif name.endswith('武器'): + state['type'] = '武器图鉴' + name = name[:-2] + elif re.match(r'现在|[今明后][天日]|周[一二三四五六日]', name): + state['type'] = '每日材料' else: - # await maps.finish(MessageBuild.Text(f'没有找到{name}的图鉴')) - await maps.finish() + state['type'] = '材料' + elif type in {'图鉴', '攻略'}: + if name.endswith(('角色', '天赋', '命座', '技能')): + state['type'] = f'角色{type}' + name = name[:-2] + elif name.endswith('武器'): + state['type'] = '武器图鉴' + name = name[:-2] + elif name.endswith(('原魔', '怪物')): + state['type'] = '原魔图鉴' + name = name[:-2] + elif name.endswith('圣遗物'): + state['type'] = '圣遗物图鉴' + name = name[:-3] + elif name.endswith(('七圣召唤', '原牌', '卡牌')): + state['type'] = '七圣召唤图鉴' + name = name.replace('七圣召唤', '').replace('原牌', '').replace('卡牌', '') + else: + state['type'] = type + elif type in {'参考面板', '收益曲线'}: + state['type'] = type + if name: + state['name'] = Message(name) + state['times'] = 1 - @maps.got('choice', parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) - async def _(event: MessageEvent, state: T_State, choice: str = ArgPlainText('choice')): - match_alias = state['match_alias'] - if choice.isdigit() and (1 <= int(choice) <= len(match_alias)): + +week_str = ['周一', '周二', '周三', '周四', '周五', '周六'] + + +@total_wiki.got('name', prompt=Message.template('你要查询谁的{type}呢?'), parameterless=cancel) +async def _(event: MessageEvent, state: T_State, type: str = Arg('type'), name: str = ArgPlainText('name')): + if not name: + if state['times'] == 2: + await total_wiki.finish('旅行者似乎不太能理解,下次再问我吧' + MessageSegment.face(146)) + else: + state['times'] = state['times'] + 1 + await total_wiki.reject(f'你要查询谁的{type}呢?', at_sender=True) + if type == '每日材料': + if name in {'今日', '今天', '现在'}: + day = datetime.datetime.now().weekday() + elif name in {'明日', '明天'}: + day = (datetime.datetime.now() + datetime.timedelta(days=1)).weekday() + elif name in {'后日', '后天'}: + day = (datetime.datetime.now() + datetime.timedelta(days=2)).weekday() + elif name == '周日': + await total_wiki.finish('周日所有材料都可以刷哦!', at_sender=True) + elif name.startswith('周'): + await total_wiki.send('开始获取每日材料,请稍候...') + await total_wiki.finish(await draw_material(str(event.user_id), name)) + if day == 6: + await total_wiki.finish('周日所有材料都可以刷哦!', at_sender=True) + else: + await total_wiki.send('开始获取每日材料,请稍候...') + await total_wiki.finish(await draw_material(str(event.user_id), week_str[day]), at_sender=True) + else: + if type.startswith('角色') or type in {'参考面板', '收益曲线'}: + if alias := await PlayerAlias.get_or_none(user_id=str(event.user_id), alias=name): + final_name = alias.character + matches = {} + try: + await total_wiki.finish( + MessageSegment.image(API[type].format(proxy=config.github_proxy, name=final_name))) + except ActionFailed: + await total_wiki.finish( + MessageBuild.Text(f'{final_name}的{type}发送失败,可能是网络问题或者不存在该资源')) + else: + matches = get_match_alias(name, ['角色']) + elif type.startswith(('武器', '原魔')): + matches = get_match_alias(name, [type[:2]]) + elif type.startswith('圣遗物'): + matches = get_match_alias(name, ['圣遗物']) + elif type.startswith('七圣召唤'): + matches = await get_match_card(name) + else: + matches = get_match_alias(name, ['角色', '武器', '原魔', '圣遗物']) + if m := await get_match_card(name): + matches['七圣召唤'] = m + if not matches: + await total_wiki.finish() + elif len(matches) == 1 and len(list(matches.values())[0]) == 1: + final_name = list(matches.values())[0][0] + if type in {'材料', '攻略', '图鉴'}: + type = list(matches.keys())[0] + type + if type.endswith(('攻略', '图鉴')) and type.startswith(('原魔', '圣遗物', '武器', '七圣召唤')): + type = f'{type[-2:]}图鉴' try: - await maps.finish(MessageSegment.image( - state['img_url'].format(match_alias[int(choice) - 1]) if state['img_url'].startswith('http') - else state['img_url'].format(config.github_proxy, match_alias[int(choice) - 1]))) - + await total_wiki.finish( + MessageSegment.image(API[type].format(proxy=config.github_proxy, name=final_name))) except ActionFailed: - await maps.finish(MessageBuild.Text(f'没有找到{match_alias[int(choice) - 1]}的图鉴')) - if choice not in match_alias: - state['times'] = state['times'] + 1 if 'times' in state else 1 - if state['times'] == 1: - await maps.reject(f'请旅行者从上面的{state["type"]}中选一个问{NICKNAME}\n回答\"取消\"可以取消查询') + await total_wiki.finish( + MessageBuild.Text(f'{final_name}的{type}发送失败,可能是网络问题或者不存在该资源')) + else: + msg = f'你要查询的{type}是:\n' + index = 1 + for key, value in matches.items(): + for v in value: + msg += f'{index}.{v}({key})\n' if len(matches) > 1 else f'{index}.{v}\n' + index += 1 + # msg += '\n'.join([f'{i}({key})' if len(matches) > 1 else f'{i}' for i in value]) + '\n' + msg += '回答\"序号\"查询或回答\"取消\"取消查询' + await total_wiki.send(msg) + state['matches'] = matches - elif state['times'] == 2: - await maps.reject(f'别调戏{NICKNAME}啦,快选一个吧,不想问了请回答\"取消\"!') - elif state['times'] >= 3: - await maps.finish( - MessageSegment.text(f'看来旅行者您有点神志不清哦(,下次再问{NICKNAME}吧') + MessageSegment.face(146)) + +@total_wiki.got('choice', parameterless=cancel) +async def _(state: T_State, matches: dict = Arg('matches'), choice: str = ArgPlainText('choice'), + type: str = Arg('type')): + if choice.isdigit() and 1 <= int(choice) <= sum(len(value) for value in matches.values()): + choice_num = int(choice) + else: + choice_num = None + final_name = None + for key, value in matches.items(): + if choice_num: + if len(value) >= choice_num: + final_name = value[choice_num - 1] + if type in {'材料', '攻略', '图鉴'}: + type = f'{key}图鉴' if key != '角色' else f'{key}{type}' + break + else: + choice_num -= len(value) + elif choice in value: + final_name = choice + if type in {'材料', '攻略', '图鉴'}: + type = f'{key}图鉴' if key != '角色' else f'{key}{type}' + break + if final_name: try: - await maps.finish(MessageSegment.image(state['img_url'].format( - state['img_url'].format(config.github_proxy, choice) if not state['img_url'].startswith( - 'http') else state['img_url'].format(choice)))) + await total_wiki.finish( + MessageSegment.image(API[type].format(proxy=config.github_proxy, name=final_name))) except ActionFailed: - await maps.finish(MessageBuild.Text(f'没有找到{choice}的图鉴')) - - -create_wiki_matcher(r'(?P\w{0,7})(?P(原魔|怪物)(图鉴|攻略))(?P\w{0,7})', '原魔图鉴', '原魔') -create_wiki_matcher(r'(?P\w{0,7})(?P武器(图鉴|攻略))(?P\w{0,7})', '武器图鉴', '武器') -create_wiki_matcher(r'(?P\w{0,7})(?P圣遗物(图鉴|攻略))(?P\w{0,7})', '圣遗物图鉴', '圣遗物') -create_wiki_matcher(r'(?P\w{0,7})(?P角色攻略)(?P\w{0,7})', '角色攻略', '角色') -create_wiki_matcher(r'(?P\w{0,7})(?P角色材料)(?P\w{0,7})', '角色材料', '角色') -create_wiki_matcher(r'(?P\w{0,7})(?P角色图鉴)(?P\w{0,7})', '角色图鉴', '角色') -create_wiki_matcher(r'(?P\w{0,7})(?P收益曲线)(?P\w{0,7})', '收益曲线', '角色') -create_wiki_matcher(r'(?P\w{0,7})(?P参考面板)(?P\w{0,7})', '参考面板', '角色') - - -@card_wiki.handle() -async def _(state: T_State, msg: Message = CommandArg()): - state['name'] = msg - - -@card_wiki.got('name', prompt='你想查询哪张卡牌的图鉴呢?') -async def _(state: T_State, name: str = ArgPlainText('name')): - if not (matches := await get_match_card(name)): - await card_wiki.finish(MessageBuild.Text(f'暂时没有{name}的卡牌图鉴')) - if name in matches: - await card_wiki.finish(MessageSegment.image(CARD_API.format(config.github_proxy, name))) - if len(matches) == 1: - await card_wiki.finish(MessageSegment.image(CARD_API.format(config.github_proxy, matches[0]))) - if 'choice' not in state: - msg = f'你要查询的卡牌是:\n' - msg += '\n'.join([f'{int(i) + 1}. {name}' for i, name in enumerate(matches)]) - await card_wiki.send(msg + '\n回答\"取消\"来取消查询', at_sender=True) - state['matches'] = matches - - -@card_wiki.got('choice', parameterless=[HandleCancellation(f'好吧,有需要再找{NICKNAME}')]) -async def _(state: T_State, choice: str = ArgPlainText('choice')): - matches = state['matches'] - if choice.isdigit() and (1 <= int(choice) <= len(matches)): - try: - await card_wiki.finish(MessageSegment.image(CARD_API.format(config.github_proxy, matches[int(choice) - 1]))) - except ActionFailed: - await card_wiki.finish( - MessageBuild.Text(f'获取{matches[int(choice) - 1]}的卡牌图鉴失败,请检查网络或更换资源地址')) - if choice not in matches: - state['times'] = state['times'] + 1 if 'times' in state else 1 - if state['times'] == 1: - await card_wiki.reject(f'请旅行者从上面的卡牌中选一个问{NICKNAME}\n回答\"取消\"可以取消查询') - - elif state['times'] == 2: - await card_wiki.reject(f'别调戏{NICKNAME}啦,快选一个吧,不想问了请回答\"取消\"!') - elif state['times'] >= 3: - await card_wiki.finish( - MessageSegment.text(f'看来旅行者您有点神志不清哦(,下次再问{NICKNAME}吧') + MessageSegment.face(146)) - try: - await card_wiki.finish(MessageSegment.image(CARD_API.format(config.github_proxy, choice))) - except ActionFailed: - await card_wiki.finish(MessageBuild.Text(f'获取{choice}的卡牌图鉴失败,请检查网络或更换资源地址')) + await total_wiki.finish( + MessageBuild.Text(f'{final_name}的{type}发送失败,可能是网络问题或者不存在该资源')) + elif state['times'] == 2: + await total_wiki.finish(f'旅行者似乎不太能理解,下次再问我吧{MessageSegment.face(146)}') + else: + state['times'] = state['times'] + 1 + await total_wiki.reject(f'请旅行者从上面的{type}中选一个问{NICKNAME}或回答\"取消\"可以取消查询', at_sender=True) @card_wiki_list.handle() diff --git a/LittlePaimon/plugins/Paimon_Wiki/card/__init__.py b/LittlePaimon/plugins/Paimon_Wiki/card/__init__.py new file mode 100644 index 0000000..c09f4c5 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Wiki/card/__init__.py @@ -0,0 +1 @@ +from .api import get_card_resources, get_match_card, CARD_API \ No newline at end of file diff --git a/LittlePaimon/plugins/Paimon_Wiki/card.py b/LittlePaimon/plugins/Paimon_Wiki/card/api.py similarity index 68% rename from LittlePaimon/plugins/Paimon_Wiki/card.py rename to LittlePaimon/plugins/Paimon_Wiki/card/api.py index be5ac82..badfaec 100644 --- a/LittlePaimon/plugins/Paimon_Wiki/card.py +++ b/LittlePaimon/plugins/Paimon_Wiki/card/api.py @@ -6,13 +6,13 @@ from ruamel import yaml from LittlePaimon.config import config from LittlePaimon.utils.requests import aiorequests -CARD_RESOURCES_API = '{}https://raw.githubusercontent.com/Nwflower/Atlas/master/resource/text/card.yaml' -CARD_API = '{}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/card/{}.png' +CARD_RESOURCES_API = '{proxy}https://raw.githubusercontent.com/Nwflower/Atlas/master/resource/text/card.yaml' +CARD_API = '{proxy}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/card/{name}.png' async def get_card_resources() -> Optional[dict]: with contextlib.suppress(Exception): - resp = await aiorequests.get(CARD_RESOURCES_API.format(config.github_proxy)) + resp = await aiorequests.get(CARD_RESOURCES_API.format(proxy=config.github_proxy)) data = yaml.load(resp.content, Loader=yaml.Loader) data.pop('召唤') return data diff --git a/LittlePaimon/plugins/Paimon_Wiki/draw_map.py b/LittlePaimon/plugins/Paimon_Wiki/draw_map.py index ec925e0..36680f7 100644 --- a/LittlePaimon/plugins/Paimon_Wiki/draw_map.py +++ b/LittlePaimon/plugins/Paimon_Wiki/draw_map.py @@ -15,13 +15,13 @@ ImageFile.LOAD_TRUNCATED_IMAGES = True Image.MAX_IMAGE_PIXELS = None map_name = { - 'teyvat': '提瓦特', + 'teyvat': '提瓦特', 'enkanomiya': '渊下宫', - 'chasm': '层岩巨渊' + 'chasm': '层岩巨渊' } map_name_reverse = { - '提瓦特': 'teyvat', - '渊下宫': 'enkanomiya', + '提瓦特': 'teyvat', + '渊下宫': 'enkanomiya', '层岩巨渊': 'chasm' } @@ -81,9 +81,9 @@ async def draw_map(name: str, map_: str): y1_temp = int(points[0].y) - 700 y2_temp = int(points[0].y) + 700 group_point = [( - models.XYPoint(x1_temp, y1_temp), - models.XYPoint(x2_temp, y2_temp), - points)] + models.XYPoint(x1_temp, y1_temp), + models.XYPoint(x2_temp, y2_temp), + points)] map_img = (await load_image(RESOURCE_BASE_PATH / 'genshin_map' / 'results' / f'{map_id.name}.png')).copy() lt_point = group_point[0][0] rb_point = group_point[0][1] @@ -111,8 +111,10 @@ async def draw_map(name: str, map_: str): if 'source' in info: des += '\n推荐采集地点:' + ','.join(info['source']).replace('推荐:', '') if des: - await total_img.text_box(des.replace('\n', '^'), (482, 1010), (281, 520), fm.get('SourceHanSansCN-Bold.otf', 30), '#3c3c3c') - await total_img.text('CREATED BY LITTLEPAIMON', (0, total_img.width), total_img.height - 45, fm.get('bahnschrift_bold', 36, 'Bold'), '#3c3c3c', align='center') + await total_img.text_box(des.replace('\n', '^'), (482, 1010), (281, 520), + fm.get('SourceHanSansCN-Bold.otf', 30), '#3c3c3c') + await total_img.text('CREATED BY LITTLEPAIMON', (0, total_img.width), total_img.height - 45, + fm.get('bahnschrift_bold', 36, 'Bold'), '#3c3c3c', align='center') total_img.save(RESOURCE_BASE_PATH / 'genshin_map' / 'results' / f'{map_}_{name}.png') return MessageBuild.Image(total_img, mode='RGB', quality=85) @@ -144,20 +146,19 @@ async def get_full_map(names: List[str], map_: str): return MessageBuild.Text(f'{map_}未查找到材料{"、".join(names)},请尝试其他地图') map_img = (await load_image(RESOURCE_BASE_PATH / 'genshin_map' / 'results' / f'{map_id.name}.png')).copy() box_icon = await load_image(RESOURCE_BASE_PATH / 'genshin_map' / 'point_box.png') - i = 0 max_point = XYPoint(x=0, y=0) - min_point = XYPoint(x=16384, y=12288) - for points in resources_points: + min_point = XYPoint(x=map_img.width, y=map_img.height) + for i, points in enumerate(resources_points): resource_icon = box_icon.copy() resource_icon.alpha_composite(await aiorequests.get_img(resources[i].icon, size=(90, 90)), (28, 15)) resource_icon = resource_icon.resize((48, 48), Image.ANTIALIAS) if len(points) >= 3: - group_point = img.k_means_points(points, 16000) + group_point = img.k_means_points(points, 2000) else: - x1_temp = int(points[0].x) - 16000 - x2_temp = int(points[0].x) + 16000 - y1_temp = int(points[0].y) - 16000 - y2_temp = int(points[0].y) + 16000 + x1_temp = int(points[0].x) - 2000 + x2_temp = int(points[0].x) + 2000 + y1_temp = int(points[0].y) - 2000 + y2_temp = int(points[0].y) + 2000 group_point = [( models.XYPoint(x1_temp, y1_temp), models.XYPoint(x2_temp, y2_temp), @@ -169,13 +170,9 @@ async def get_full_map(names: List[str], map_: str): for point in group_point[0][2]: point_trans = (int(point.x), int(point.y)) map_img.paste(resource_icon, (point_trans[0] - 24, point_trans[1] - 48), resource_icon) - i += 1 map_img = map_img.crop((int(min_point.x) - 50, int(min_point.y) - 50, int(max_point.x) + 50, int(max_point.y) + 50)) if resources_not: - return MessageBuild.Text(f'{map_}未找到材料{"、".join(resources_not)},请尝试其他地图\n') + MessageBuild.Image(map_img) + return MessageBuild.Text(f'{map_}未找到材料{"、".join(resources_not)},请尝试其他地图\n') + MessageBuild.Image( + map_img) else: return MessageBuild.Image(map_img) - - - - diff --git a/LittlePaimon/plugins/Paimon_Wiki/wiki_api.py b/LittlePaimon/plugins/Paimon_Wiki/wiki_api.py new file mode 100644 index 0000000..4463e69 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Wiki/wiki_api.py @@ -0,0 +1,14 @@ +from typing import Dict +from .card import CARD_API + +API: Dict[str, str] = { + '角色图鉴': '{proxy}https://raw.githubusercontent.com/CMHopeSunshine/GenshinWikiMap/master/results/character_map/{name}.jpg', + '角色攻略': 'https://static.cherishmoon.fun/LittlePaimon/XFGuide/{name}.jpg', + '角色材料': '{proxy}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/material%20for%20role/{name}.png', + '收益曲线': 'https://static.cherishmoon.fun/LittlePaimon/blue/{name}.jpg', + '参考面板': 'https://static.cherishmoon.fun/LittlePaimon/blueRefer/{name}.jpg', + '武器图鉴': '{proxy}https://raw.githubusercontent.com/Nwflower/genshin-atlas/master/weapon/{name}.png', + '圣遗物图鉴': 'https://static.cherishmoon.fun/LittlePaimon/ArtifactMaps/{name}.jpg', + '原魔图鉴': 'https://static.cherishmoon.fun/LittlePaimon/MonsterMaps/{name}.jpg', + '七圣召唤图鉴': CARD_API +} \ No newline at end of file diff --git a/LittlePaimon/utils/__init__.py b/LittlePaimon/utils/__init__.py index 01fb868..91de169 100644 --- a/LittlePaimon/utils/__init__.py +++ b/LittlePaimon/utils/__init__.py @@ -4,7 +4,7 @@ from nonebot import get_driver from .logger import logger from .scheduler import scheduler -__version__ = '3.0.0rc6' +__version__ = '3.0.0rc7' DRIVER = get_driver() try: diff --git a/LittlePaimon/utils/alias.py b/LittlePaimon/utils/alias.py index 54dbac8..ff8489c 100644 --- a/LittlePaimon/utils/alias.py +++ b/LittlePaimon/utils/alias.py @@ -1,5 +1,5 @@ -import difflib -from typing import Union, Literal, List, Optional +from difflib import get_close_matches +from typing import Union, Literal, List, Optional, Dict from .files import load_json from .path import JSON_DATA @@ -11,7 +11,7 @@ weapon_file = load_json(JSON_DATA / 'weapon.json') def get_id_by_name(name: str) -> Optional[str]: """ - 根据角色名字获取角色的id + 根据角色名字获取角色的id :param name: 角色名 :return: id字符串 """ @@ -23,7 +23,7 @@ def get_id_by_name(name: str) -> Optional[str]: def get_name_by_id(role_id: Union[str, int]) -> Optional[str]: """ - 根据角色id获取角色名 + 根据角色id获取角色名 :param role_id: 角色id :return: 角色名字符串 """ @@ -35,7 +35,7 @@ def get_name_by_id(role_id: Union[str, int]) -> Optional[str]: def get_alias_by_name(name: str) -> Optional[List[str]]: """ - 根据角色名字获取角色的别名 + 根据角色名字获取角色的别名 :param name: 角色名 :return: 别名列表 """ @@ -43,42 +43,49 @@ def get_alias_by_name(name: str) -> Optional[List[str]]: return next((r for r in name_list.values() if name in r), None) -def get_match_alias(msg: str, type: Literal['角色', '武器', '原魔', '圣遗物'] = '角色', single_to_dict: bool = False) -> Union[ - str, list, dict]: +def get_match_alias(name: str, types: List[Literal['角色', '武器', '原魔', '圣遗物']] = None, + one_to_list: bool = False) -> Union[ + Dict[str, List[str]], List[str]]: """ - 根据字符串消息,获取与之相似或匹配的角色、武器、原魔名 - :param msg: 消息 - :param type: 匹配类型,有roles、weapons、monsters - :param single_to_dict: 是否将角色单结果也转换成{角色:id}字典 - :return: 匹配的字符串、列表或字典 + 根据字符串消息,获取与之相似或匹配的角色、武器、原魔名 + :param name: 名称 + :param types: 匹配类型,有'角色', '武器', '原魔', '圣遗物' + :param one_to_list: 只有一种匹配结果时是否直接返回其列表 + :return: 匹配结果 """ - alias_list = alias_file[type] - if msg in {'风主', '岩主', '雷主', '草主'}: - return msg - elif type == '角色': - possible = {} - for role_id, alias in alias_list.items(): - match_list = difflib.get_close_matches(msg, alias, cutoff=0.6, n=3) - if msg in match_list: - return {alias[0]: role_id} if single_to_dict else alias[0] - elif match_list: - possible[alias[0]] = role_id - if len(possible) == 1: - return {list(possible.keys())[0]: possible[list(possible.keys())[0]]} if single_to_dict else \ - list(possible.keys())[0] - return possible - elif type in {'武器', '圣遗物'}: - possible = [] - for name, alias in alias_list.items(): - match_list = difflib.get_close_matches(msg, alias, cutoff=0.4, n=3) - if msg in match_list: - return name - elif match_list: - possible.append(name) - return possible - elif type == '原魔': - match_list = difflib.get_close_matches(msg, alias_list, cutoff=0.4, n=5) - return match_list[0] if len(match_list) == 1 else match_list + if types is None: + types = ['角色'] + matches = {} + for type in types: + alias_list = alias_file[type] + matches[type] = [] + if type == '角色': + if name.startswith(('风', '岩', '雷', '草', '水', '火', '冰')) and name.endswith( + ('主', '主角', '空', '荧', '旅行者')): + matches[type].append(name if name.endswith(('空', '荧')) else f'{name[0]}荧') + continue + for alias in alias_list.values(): + if name in alias: + matches[type].append(alias[0]) + break + if get_close_matches(name, alias, cutoff=0.6, n=3): + matches[type].append(alias[0]) + elif type in {'武器', '圣遗物'}: + for raw_name, alias in alias_list.items(): + if name in alias: + matches[type].append(raw_name) + break + else: + if get_close_matches(name, alias, cutoff=0.6, n=3): + matches[type].append(raw_name) + elif type == '原魔': + matches[type] = get_close_matches(name, alias_list, cutoff=0.4, n=5) + if not matches[type]: + del matches[type] + if one_to_list and len(matches) == 1: + return list(matches.values())[0] + else: + return matches def get_chara_icon(name: Optional[str] = None, chara_id: Optional[int] = None, diff --git a/LittlePaimon/utils/message.py b/LittlePaimon/utils/message.py index 0b21a6a..39eea66 100644 --- a/LittlePaimon/utils/message.py +++ b/LittlePaimon/utils/message.py @@ -210,7 +210,7 @@ def CommandCharacter(limit: int = 3) -> List[str]: character_list.append(random.choice(FEMALE_CHARACTERS)) msg.replace(character_name, '') # 如果有匹配别名 - elif character_match := get_match_alias(character_name, '角色', True): + elif character_match := get_match_alias(character_name, ['角色'], True): character_list.append(list(character_match.keys())[0]) msg.replace(character_name, '') # 没有匹配到角色时,结束事件 diff --git a/LittlePaimon/utils/typing.py b/LittlePaimon/utils/typing.py index 8681dd1..15f1966 100644 --- a/LittlePaimon/utils/typing.py +++ b/LittlePaimon/utils/typing.py @@ -7,6 +7,11 @@ try: except ImportError: import json +from . import DRIVER + +command_start = list(DRIVER.config.command_start) +COMMAND_START_RE = '^' + '|'.join(command_start) if command_start else '^' + ElementType = Literal['火', '水', '冰', '雷', '风', '岩', '草', '物理'] WeaponType = Literal['单手剑', '双手剑', '长柄武器', '弓', '法器']