From 91e0cfb7c1909914cba9e1721b09c75bd6d9962e Mon Sep 17 00:00:00 2001 From: MuXinCG <202322130196@mail.sdu.edu.cn> Date: Sun, 15 Feb 2026 20:57:21 +0800 Subject: [PATCH] pass linter and pytest --- EvoScientist/channels/base.py | 51 ++++++++++++++++++----- EvoScientist/channels/telegram/channel.py | 8 ++-- tests/test_wechat_channel.py | 17 +++++++- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/EvoScientist/channels/base.py b/EvoScientist/channels/base.py index 25e59b6..fbe938d 100644 --- a/EvoScientist/channels/base.py +++ b/EvoScientist/channels/base.py @@ -715,11 +715,12 @@ class Channel(ChannelPlugin, ABC): """Decide whether to process a message based on mention gating.""" if self.require_mention == "off": return True + # Both "always" and "group" allow DMs through unconditionally + if not raw.is_group: + return True if self.require_mention == "always": return raw.was_mentioned # "group" — require mention only in groups - if not raw.is_group: - return True return raw.was_mentioned _mention_pattern: str | None = None @@ -757,6 +758,42 @@ class Channel(ChannelPlugin, ABC): # ── Inbound message pipeline ────────────────────────────────────── + async def _build_inbound_async(self, raw: RawIncoming) -> InboundMessage | None: + """Async version: run *raw* through inbound middlewares and convert.""" + context: dict = {"channel": self} + current: RawIncoming | None = raw + for mw in self._inbound_middlewares: + if current is None: + return None + current = await mw.process_inbound(current, context) + if current is None: + return None + return self._raw_to_inbound(current) + + def _build_inbound(self, raw: RawIncoming) -> InboundMessage | None: + """Run *raw* through inbound middlewares and convert to InboundMessage. + + Synchronous wrapper around :meth:`_build_inbound_async`. Safe to + call from both sync and async contexts. + """ + import asyncio + import concurrent.futures + + try: + asyncio.get_running_loop() + # Inside a running loop — run in a worker thread + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + return pool.submit( + lambda: asyncio.run(self._build_inbound_async(raw)) + ).result() + except RuntimeError: + # No running loop — safe to create one + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(self._build_inbound_async(raw)) + finally: + loop.close() + def _raw_to_inbound(self, raw: RawIncoming) -> InboundMessage | None: """Convert a RawIncoming to InboundMessage (pure transformation, no filtering). @@ -785,15 +822,7 @@ class Channel(ChannelPlugin, ABC): Convenience method for subclass ``_on_message`` handlers. """ - context: dict = {"channel": self} - current: RawIncoming | None = raw - for mw in self._inbound_middlewares: - if current is None: - return - current = await mw.process_inbound(current, context) - if current is None: - return - msg = self._raw_to_inbound(current) + msg = self._build_inbound(raw) if msg is None: return if raw.message_id: diff --git a/EvoScientist/channels/telegram/channel.py b/EvoScientist/channels/telegram/channel.py index 173aa36..7731c46 100644 --- a/EvoScientist/channels/telegram/channel.py +++ b/EvoScientist/channels/telegram/channel.py @@ -1,8 +1,6 @@ """Telegram channel implementation using python-telegram-bot.""" -import asyncio import logging -import re from dataclasses import dataclass from datetime import datetime from pathlib import Path @@ -37,6 +35,9 @@ class TelegramChannel(Channel): self._bot_username: str = "" async def start(self) -> None: + if not self.config.bot_token: + raise ChannelError("Telegram bot token is required") + try: from telegram.ext import ( ApplicationBuilder, @@ -49,9 +50,6 @@ class TelegramChannel(Channel): "Install with: pip install evoscientist[telegram]" ) - if not self.config.bot_token: - raise ChannelError("Telegram bot token is required") - builder = ApplicationBuilder().token(self.config.bot_token) if self.config.proxy: builder = builder.proxy(self.config.proxy).get_updates_proxy(self.config.proxy) diff --git a/tests/test_wechat_channel.py b/tests/test_wechat_channel.py index 6a5a33d..e0ae457 100644 --- a/tests/test_wechat_channel.py +++ b/tests/test_wechat_channel.py @@ -2,7 +2,6 @@ import asyncio import hashlib -import json import time import xml.etree.ElementTree as ET @@ -240,6 +239,22 @@ class TestWeChatCrypto: Uses a deterministic 43-char EncodingAESKey. """ + # Skip encryption tests when no crypto backend is available + _has_crypto = False + try: + from Crypto.Cipher import AES as _aes # noqa: F401 + _has_crypto = True + except ImportError: + try: + import pyaes as _pyaes # noqa: F401 + _has_crypto = True + except ImportError: + pass + pytestmark = pytest.mark.skipif( + not _has_crypto, + reason="pycryptodome or pyaes required for encryption tests", + ) + @pytest.fixture def crypto(self): # 43 base64 chars → 32 bytes AES key