diff --git a/bots/matrix/bot.py b/bots/matrix/bot.py index f31413815c..a2df205ec3 100644 --- a/bots/matrix/bot.py +++ b/bots/matrix/bot.py @@ -9,7 +9,7 @@ from bots.matrix import client from bots.matrix.client import bot from bots.matrix.info import client_name -from bots.matrix.message import MessageSession, FetchTarget +from bots.matrix.message import MessageSession, FetchTarget, ReactionMessageSession from core.builtins import PrivateAssets, Url from core.logger import Logger from core.parser.message import parser @@ -50,14 +50,14 @@ async def on_room_member(room: nio.MatrixRoom, event: nio.RoomMemberEvent): Logger.info(f"Left empty room {room.room_id}") -async def on_message(room: nio.MatrixRoom, event: nio.RoomMessageFormatted): +async def on_message(room: nio.MatrixRoom, event: nio.Event): if event.sender != bot.user_id and bot.olm: for device_id, olm_device in bot.device_store[event.sender].items(): if bot.olm.is_device_verified(olm_device): continue bot.verify_device(olm_device) Logger.info(f"trust olm device for device id {event.sender} -> {device_id}") - if event.source['content']['msgtype'] == 'm.notice': + if isinstance(event, nio.RoomMessageFormatted) and event.source['content']['msgtype'] == 'm.notice': # https://spec.matrix.org/v1.7/client-server-api/#mnotice return is_room = room.member_count != 2 or room.join_rule != 'invite' @@ -65,21 +65,30 @@ async def on_message(room: nio.MatrixRoom, event: nio.RoomMessageFormatted): reply_id = None if 'm.relates_to' in event.source['content'] and 'm.in_reply_to' in event.source['content']['m.relates_to']: reply_id = event.source['content']['m.relates_to']['m.in_reply_to']['event_id'] + resp = await bot.get_displayname(event.sender) if isinstance(resp, nio.ErrorResponse): Logger.error(f"Failed to get display name for {event.sender}") return sender_name = resp.displayname - msg = MessageSession(MsgInfo(target_id=f'Matrix|{target_id}', - sender_id=f'Matrix|{event.sender}', - target_from=f'Matrix', - sender_from='Matrix', - sender_name=sender_name, - client_name=client_name, - message_id=event.event_id, - reply_id=reply_id), - Session(message=event.source, target=room.room_id, sender=event.sender)) + target = MsgInfo(target_id=f'Matrix|{target_id}', + sender_id=f'Matrix|{event.sender}', + target_from=f'Matrix', + sender_from='Matrix', + sender_name=sender_name, + client_name=client_name, + message_id=event.event_id, + reply_id=reply_id) + session = Session(message=event.source, target=room.room_id, sender=event.sender) + + msg = None + if isinstance(event, nio.RoomMessageFormatted): + msg = MessageSession(target, session) + elif isinstance(event, nio.ReactionEvent): + msg = ReactionMessageSession(target, session) + else: + raise NotImplemented asyncio.create_task(parser(msg)) @@ -141,6 +150,7 @@ async def start(): bot.add_event_callback(on_invite, nio.InviteEvent) bot.add_event_callback(on_room_member, nio.RoomMemberEvent) bot.add_event_callback(on_message, nio.RoomMessageFormatted) + bot.add_event_callback(on_message, nio.ReactionEvent) bot.add_to_device_callback(on_verify, nio.KeyVerificationEvent) bot.add_event_callback(on_in_room_verify, nio.RoomMessageUnknown) diff --git a/bots/matrix/message.py b/bots/matrix/message.py index 3faf84a646..e5657fe22d 100644 --- a/bots/matrix/message.py +++ b/bots/matrix/message.py @@ -201,7 +201,7 @@ async def to_message_chain(self): async def delete(self): try: - await bot.room_redact(self.session.target, self.session.message['event_id']) + await bot.room_redact(self.session.target, self.target.message_id) except Exception: Logger.error(traceback.format_exc()) @@ -224,6 +224,37 @@ async def __aexit__(self, exc_type, exc_val, exc_tb): pass +class ReactionMessageSession(MessageSession): + class Feature(MessageSession.Feature): + pass + + class Typing(MessageSession.Typing): + pass + + def as_display(self, text_only=False): + if text_only: + return '' + return self.session.message['content']['m.relates_to']['key'] + + async def to_message_chain(self): + return MessageChain([]) + + def is_quick_confirm(self, target: Union[MessageSession, FinishedSession]) -> bool: + content = self.session.message['content']['m.relates_to'] + if content['rel_type'] == 'm.annotation': + if content['key'] in ['👍️', '✔️', '🎉']: # todo: move to config + if target is None: + return True + else: + msg = [target.target.message_id] if isinstance(target, MessageSession) else target.message_id + if content['event_id'] in msg: + return True + return False + + asDisplay = as_display + toMessageChain = to_message_chain + + class FetchedSession(Bot.FetchedSession): async def _resolve_matrix_room_(self): diff --git a/config/config.toml.example b/config/config.toml.example index 85a45122b4..a6dc0d10b2 100644 --- a/config/config.toml.example +++ b/config/config.toml.example @@ -31,6 +31,7 @@ debug = false cache_path = "./cache/" command_prefix = ["~", "~",] confirm_command = ["是", "对", "對", "yes", "Yes", "YES", "y", "Y",] +quick_confirm = true disabled_bots = locale = "zh_cn" timezone_offset = "+8" diff --git a/core/builtins/message/__init__.py b/core/builtins/message/__init__.py index 88b6d95b61..a93792e418 100644 --- a/core/builtins/message/__init__.py +++ b/core/builtins/message/__init__.py @@ -7,9 +7,9 @@ from core.builtins.message.internal import * from core.builtins.tasks import MessageTaskManager from core.builtins.temp import ExecutionLockList -from core.builtins.utils import confirm_command +from core.builtins.utils import confirm_command, quick_confirm from core.exceptions import WaitCancelException -from core.types.message import MessageSession as MessageSessionT, MsgInfo, Session +from core.types.message import MessageSession as MessageSessionT, FinishedSession, MsgInfo, Session from core.utils.i18n import Locale from core.utils.text import parse_time_string from database import BotDBUtil @@ -56,12 +56,14 @@ async def wait_confirm(self, message_chain=None, quote=True, delete=True, timeou await send.delete() if result.as_display(text_only=True) in confirm_command: return True + if quick_confirm and result.is_quick_confirm(send): + return True return False else: raise WaitCancelException async def wait_next_message(self, message_chain=None, quote=True, delete=False, timeout=120, - append_instruction=True) -> MessageSessionT: + append_instruction=True) -> (MessageSessionT, FinishedSession): sent = None ExecutionLockList.remove(self) if message_chain: @@ -79,7 +81,7 @@ async def wait_next_message(self, message_chain=None, quote=True, delete=False, if delete and sent: await sent.delete() if result: - return result + return (result, sent) else: raise WaitCancelException diff --git a/core/builtins/tasks.py b/core/builtins/tasks.py index a49970403f..f3ccddae61 100644 --- a/core/builtins/tasks.py +++ b/core/builtins/tasks.py @@ -29,7 +29,7 @@ def add_callback(cls, message_id, callback): cls._callback_list[message_id] = {'callback': callback, 'ts': datetime.now().timestamp()} @classmethod - def get_result(cls, session: MessageSession): + def get_result(cls, session: MessageSession) -> MessageSession: if 'result' in cls._list[session.target.target_id][session.target.sender_id][session]: return cls._list[session.target.target_id][session.target.sender_id][session]['result'] else: diff --git a/core/builtins/utils/__init__.py b/core/builtins/utils/__init__.py index 895a396c05..eb36408bae 100644 --- a/core/builtins/utils/__init__.py +++ b/core/builtins/utils/__init__.py @@ -3,6 +3,7 @@ confirm_command = Config('confirm_command', default=["是", "对", "對", "yes", "Yes", "YES", "y", "Y"]) +quick_confirm = Config('quick_confirm', default=True) command_prefix = Config('command_prefix', default=['~', '~']) # 消息前缀 @@ -10,4 +11,4 @@ class EnableDirtyWordCheck: status = False -__all__ = ["confirm_command", "command_prefix", "EnableDirtyWordCheck", "PrivateAssets", "Secret"] +__all__ = ["confirm_command", "quick_confirm", "command_prefix", "EnableDirtyWordCheck", "PrivateAssets", "Secret"] diff --git a/core/types/message/__init__.py b/core/types/message/__init__.py index 6e99dddd48..608cb0309e 100644 --- a/core/types/message/__init__.py +++ b/core/types/message/__init__.py @@ -1,5 +1,5 @@ import asyncio -from typing import List, Union, Dict, Coroutine +from typing import List, Union, Dict, Coroutine, Self from core.exceptions import FinishedException from .chain import MessageChain @@ -149,14 +149,14 @@ async def wait_confirm(self, message_chain=None, quote=True, delete=True, timeou raise NotImplementedError async def wait_next_message(self, message_chain=None, quote=True, delete=False, timeout=120, - append_instruction=True): + append_instruction=True) -> (Self, FinishedSession): """ 一次性模板,用于等待对象的下一条消息。 :param message_chain: 需要发送的确认消息,可不填 :param quote: 是否引用传入dict中的消息(默认为True) :param delete: 是否在触发后删除消息(默认为False) :param timeout: 超时时间 - :return: 下一条消息的MessageChain对象 + :return: 下一条消息的MessageChain对象和发出的提示消息 """ raise NotImplementedError @@ -215,6 +215,13 @@ async def check_native_permission(self): """ raise NotImplementedError + def is_quick_confirm(self, target: Union[Self, FinishedSession] = None) -> bool: + """ + 用于检查消息是否可用作快速确认事件。 + :param target: 确认的目标消息 + """ + return False + async def fake_forward_msg(self, nodelist): """ 用于发送假转发消息(QQ)。 diff --git a/modules/chemical_code/__init__.py b/modules/chemical_code/__init__.py index 44aa13b061..04a4780cd5 100644 --- a/modules/chemical_code/__init__.py +++ b/modules/chemical_code/__init__.py @@ -243,7 +243,7 @@ async def timer(start): await asyncio.gather(ans(msg, csr['name'], random_mode), timer(time_start)) else: - result = await msg.wait_next_message([Plain(msg.locale.t('chemical_code.message.showid', id=csr["id"])), + result, _ = await msg.wait_next_message([Plain(msg.locale.t('chemical_code.message.showid', id=csr["id"])), Image(newpath), Plain(msg.locale.t('chemical_code.message.captcha', times=set_timeout))], timeout=3600, append_instruction=False) if play_state[msg.target.target_id]['active']: diff --git a/modules/ncmusic/__init__.py b/modules/ncmusic/__init__.py index a5e6e8d386..9e6db98fbc 100644 --- a/modules/ncmusic/__init__.py +++ b/modules/ncmusic/__init__.py @@ -32,8 +32,8 @@ async def search(msg: Bot.MessageSession, keyword: str): f"{' / '.join(artist['name'] for artist in song['artists'])}", f"{song['album']['name']}" + (f" ({' / '.join(song['album']['transNames'])})" if 'transNames' in song['album'] else ''), f"{song['id']}" - ] for i, song in enumerate(songs, start=1) - ] + ] for i, song in enumerate(songs, start=1) + ] tables = ImageTable(data, [ msg.locale.t('ncmusic.message.search.table.header.id'), @@ -41,7 +41,7 @@ async def search(msg: Bot.MessageSession, keyword: str): msg.locale.t('ncmusic.message.search.table.header.artists'), msg.locale.t('ncmusic.message.search.table.header.album'), 'ID' - ]) + ]) img = await image_table_render(tables) if img: @@ -62,7 +62,7 @@ async def search(msg: Bot.MessageSession, keyword: str): else: send_msg.append(Plain(msg.locale.t('ncmusic.message.search.prompt'))) - query = await msg.wait_reply(send_msg) + query, _ = await msg.wait_next_message(send_msg) query = query.as_display(text_only=True) if query.isdigit(): @@ -89,21 +89,20 @@ async def search(msg: Bot.MessageSession, keyword: str): if 'transNames' in song['album']: send_msg += f"({' / '.join(song['album']['transNames'])})" send_msg += f"({song['id']})\n" - if song_count > 10: song_count = 10 send_msg += msg.locale.t("message.collapse", amount="10") if song_count == 1: send_msg += '\n' + msg.locale.t('ncmusic.message.search.confirm') - query = await msg.wait_confirm(send_msg, delete=False) + query, _ = await msg.wait_next_message(send_msg) if query: sid = result['result']['songs'][0]['id'] else: return else: send_msg += '\n' + msg.locale.t('ncmusic.message.search.prompt') - query = await msg.wait_reply(send_msg) + query, _ = await msg.wait_next_message(send_msg) query = query.as_display(text_only=True) if query.isdigit(): diff --git a/modules/summary/__init__.py b/modules/summary/__init__.py index 9f432934fd..bc9e725979 100644 --- a/modules/summary/__init__.py +++ b/modules/summary/__init__.py @@ -11,10 +11,10 @@ openai.api_key = Config('openai_api_key') -s = module('summary', - developers=['Dianliang233', 'OasisAkari'], - desc='{summary.help.desc}', - available_for=['QQ', 'QQ|Group']) +s = module('summary', + developers=['Dianliang233', 'OasisAkari'], + desc='{summary.help.desc}', + available_for=['QQ', 'QQ|Group']) @s.handle('{{summary.help}}') @@ -28,7 +28,7 @@ async def _(msg: Bot.MessageSession): qc = CoolDown('call_openai', msg) c = qc.check(60) if c == 0 or msg.target.target_from == 'TEST|Console' or is_superuser: - f_msg = await msg.wait_next_message(msg.locale.t('summary.message'), append_instruction=False) + f_msg, _ = await msg.wait_next_message(msg.locale.t('summary.message'), append_instruction=False) try: f = re.search(r'\[Ke:forward,id=(.*?)\]', f_msg.as_display()).group(1) except AttributeError: @@ -86,5 +86,3 @@ async def _(msg: Bot.MessageSession): await msg.finish(output, disable_secret_check=True) else: await msg.finish(msg.locale.t('message.cooldown', time=int(c), cd_time='60')) - - diff --git a/modules/twenty_four/__init__.py b/modules/twenty_four/__init__.py index 1294787bee..3c07a4e9fd 100644 --- a/modules/twenty_four/__init__.py +++ b/modules/twenty_four/__init__.py @@ -107,8 +107,8 @@ async def _(msg: Bot.MessageSession): numbers = [random.randint(1, 13) for _ in range(4)] has_solution_flag = await has_solution(numbers) - - answer = await msg.wait_next_message(msg.locale.t('twenty_four.message', numbers=numbers), timeout=3600, append_instruction=False) + + answer, _ = await msg.wait_next_message(msg.locale.t('twenty_four.message', numbers=numbers), timeout=3600, append_instruction=False) expression = answer.as_display(text_only=True) if play_state[msg.target.target_id]['active']: if expression.lower() in no_solution: diff --git a/modules/wiki/wiki.py b/modules/wiki/wiki.py index 102d2453a1..53617dab83 100644 --- a/modules/wiki/wiki.py +++ b/modules/wiki/wiki.py @@ -4,7 +4,7 @@ import filetype -from core.builtins import Bot, Plain, Image, Voice, Url, confirm_command +from core.builtins import Bot, Plain, Image, Voice, Url, confirm_command, quick_confirm from core.utils.image_table import image_table_render, ImageTable from core.component import module from core.exceptions import AbuseWarning @@ -377,11 +377,13 @@ async def image_and_voice(): async def wait_confirm(): if wait_msg_list and session.Feature.wait: - confirm = await session.wait_next_message(wait_msg_list, delete=True, append_instruction=False) + confirm, sent = await session.wait_next_message(wait_msg_list, delete=True, append_instruction=False) auto_index = False index = 0 if confirm.as_display(text_only=True) in confirm_command: auto_index = True + elif quick_confirm and confirm.is_quick_confirm(sent): + auto_index = True elif confirm.as_display(text_only=True).isdigit(): index = int(confirm.as_display()) - 1 else: diff --git a/poetry.lock b/poetry.lock index edbbd1af98..f9ba55db97 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2276,23 +2276,22 @@ python-dateutil = ">=2.7" [[package]] name = "matrix-nio" -version = "0.21.2" +version = "0.22.1" description = "A Python Matrix client library, designed according to sans I/O principles." optional = false python-versions = ">=3.8.0,<4.0.0" files = [ - {file = "matrix_nio-0.21.2-py3-none-any.whl", hash = "sha256:95bec84dd0d4eca4f0ce252d2f630d435edbc41bb405448d88916a3783479237"}, - {file = "matrix_nio-0.21.2.tar.gz", hash = "sha256:414301fc25662af3e3436de5b955980474c03b0f3001f1b041c49743ede73232"}, + {file = "matrix_nio-0.22.1-py3-none-any.whl", hash = "sha256:36a7175a41b145026db7f3bf004577aa8906d09ed8c53f276452ac06ed8635e4"}, + {file = "matrix_nio-0.22.1.tar.gz", hash = "sha256:65956252c516f0b42b359d5816fbb66e2617a1f2c02ae45f2730257b815656d8"}, ] [package.dependencies] aiofiles = ">=23.1.0,<24.0.0" aiohttp = ">=3.8.3,<4.0.0" aiohttp-socks = ">=0.7.0,<0.8.0" -future = ">=0.18.2,<0.19.0" h11 = ">=0.14.0,<0.15.0" h2 = ">=4.0.0,<5.0.0" -jsonschema = ">=4.4.0,<5.0.0" +jsonschema = ">=4.14.0,<5.0.0" pycryptodome = ">=3.10.1,<4.0.0" unpaddedbase64 = ">=2.1.0,<3.0.0" @@ -4525,4 +4524,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "831fc1e45a738ba83e525178c24db42d6d0833fa726c6d80d3abcc9738f310ee" +content-hash = "45b7149aa43113d2c3d981764613294d4eb50b7be221047c6e8c1e3ce7351197" diff --git a/pyproject.toml b/pyproject.toml index 693a1aa106..751800832b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ pycryptodome = "^3.18.0" langconv = "^0.2.0" toml = "^0.10.2" khl-py = "^0.3.16" -matrix-nio = "^0.21.2" +matrix-nio = "^0.22.0" attrs = "^23.1.0" uvicorn = {extras = ["standard"], version = "^0.23.2"} pyjwt = {extras = ["crypto"], version = "^2.8.0"} diff --git a/requirements.txt b/requirements.txt index 03ecfcd63a..8b361dca23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1145,9 +1145,9 @@ matplotlib==3.7.2 ; python_full_version >= "3.8.1" and python_full_version < "4. --hash=sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca \ --hash=sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a \ --hash=sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0 -matrix-nio==0.21.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" \ - --hash=sha256:414301fc25662af3e3436de5b955980474c03b0f3001f1b041c49743ede73232 \ - --hash=sha256:95bec84dd0d4eca4f0ce252d2f630d435edbc41bb405448d88916a3783479237 +matrix-nio==0.22.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" \ + --hash=sha256:36a7175a41b145026db7f3bf004577aa8906d09ed8c53f276452ac06ed8635e4 \ + --hash=sha256:65956252c516f0b42b359d5816fbb66e2617a1f2c02ae45f2730257b815656d8 monotonic==1.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" \ --hash=sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7 \ --hash=sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c