refactor(matrix): 整理代码导入顺序与冗余空行

1.  移除多个文件中多余的空行
2.  调整sync_store和allowlist的导入顺序
3.  重新排列monitor模块的导入分组
4.  重新排序__init__.py中的导出导入顺序,统一格式
5.  调整normalizer模块的导入顺序
This commit is contained in:
Kris 2026-05-13 16:11:41 +08:00
parent f736a0ebb3
commit f2387884a3
8 changed files with 213 additions and 83 deletions

View File

@ -1,89 +1,89 @@
from yuxi.channels.adapters.matrix.accounts import MatrixAccount, MultiAccountManager
from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding, ACPBindingManager
from yuxi.channels.adapters.matrix.adapter import MatrixAdapter
from yuxi.channels.adapters.matrix.reaction_utils import (
build_reaction,
extract_reaction,
summarize_reactions,
select_reaction,
build_batch_remove,
from yuxi.channels.adapters.matrix.cli import MatrixCLI, format_cli_help, get_cli_commands
from yuxi.channels.adapters.matrix.commands import (
detect_slash_command,
format_help_text,
get_command_list,
is_slash_command,
)
from yuxi.channels.adapters.matrix.concurrency import AccountDataWriteQueue, AsyncLock, StartupSerialLock
from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch, apply_patch, diff_config, merge_patches
from yuxi.channels.adapters.matrix.config_schema import get_config_schema, get_default_config, validate_config
from yuxi.channels.adapters.matrix.config_ui_hints import get_config_hints, get_group_hints, get_groups, get_hint
from yuxi.channels.adapters.matrix.credentials import CredentialStore, MatrixCredentials
from yuxi.channels.adapters.matrix.directory import (
list_public_rooms,
resolve_room_alias,
search_groups,
search_users,
)
from yuxi.channels.adapters.matrix.doctor import MatrixDoctor
from yuxi.channels.adapters.matrix.draft_stream import (
DraftStreamSession,
send_draft_update,
send_live_marker,
)
from yuxi.channels.adapters.matrix.encryption import (
check_crypto_store_ready,
format_encryption_unavailable_error,
resolve_encryption_config_path,
decrypt_event,
verify_device,
check_cross_signing,
check_crypto_store_ready,
decrypt_event,
format_encryption_unavailable_error,
get_crypto_bootstrap_guide,
resolve_encryption_config_path,
verify_device,
)
from yuxi.channels.adapters.matrix.concurrency import AsyncLock, AccountDataWriteQueue, StartupSerialLock
from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch, apply_patch, merge_patches, diff_config
from yuxi.channels.adapters.matrix.env_vars import get_env_var_descriptions, resolve_env_vars
from yuxi.channels.adapters.matrix.exec_approvals import ExecApprovalManager, ExecApprovalRequest
from yuxi.channels.adapters.matrix.group_mentions import GroupMentionStrategy, build_room_mention_config
from yuxi.channels.adapters.matrix.profile_sync import (
resolve_avatar_url,
resolve_display_name,
compare_profiles,
build_profile_update,
mxc_to_http,
from yuxi.channels.adapters.matrix.location import (
build_outbound_location,
extract_geo_coordinates,
format_location_text,
parse_location_event,
)
from yuxi.channels.adapters.matrix.media_meta import extract_duration_bytes, extract_media_metadata
from yuxi.channels.adapters.matrix.polls import (
create_poll,
end_poll,
extract_poll_response,
extract_poll_start,
is_poll_end,
send_poll_response,
summarize_poll_responses,
)
from yuxi.channels.adapters.matrix.probe import (
describe_backup_state,
is_backup_healthy,
probe_matrix,
)
from yuxi.channels.adapters.matrix.polls import (
create_poll,
send_poll_response,
end_poll,
summarize_poll_responses,
extract_poll_start,
extract_poll_response,
is_poll_end,
from yuxi.channels.adapters.matrix.profile_sync import (
build_profile_update,
compare_profiles,
mxc_to_http,
resolve_avatar_url,
resolve_display_name,
)
from yuxi.channels.adapters.matrix.location import (
parse_location_event,
format_location_text,
extract_geo_coordinates,
build_outbound_location,
from yuxi.channels.adapters.matrix.reaction_utils import (
build_batch_remove,
build_reaction,
extract_reaction,
select_reaction,
summarize_reactions,
)
from yuxi.channels.adapters.matrix.sent_cache import ResponseSizeLimiter, SentMessageCache, hash_content
from yuxi.channels.adapters.matrix.setup_wizard import MatrixSetupWizard
from yuxi.channels.adapters.matrix.sticker import (
build_outbound_sticker,
is_sticker_event,
parse_sticker,
build_outbound_sticker,
)
from yuxi.channels.adapters.matrix.accounts import MatrixAccount, MultiAccountManager
from yuxi.channels.adapters.matrix.thread_bindings import ThreadBinding, ThreadBindingManager
from yuxi.channels.adapters.matrix.directory import (
search_users,
search_groups,
resolve_room_alias,
list_public_rooms,
)
from yuxi.channels.adapters.matrix.env_vars import resolve_env_vars, get_env_var_descriptions
from yuxi.channels.adapters.matrix.sync_store import SyncTokenStore, SSRFGuard
from yuxi.channels.adapters.matrix.config_schema import validate_config, get_config_schema, get_default_config
from yuxi.channels.adapters.matrix.credentials import MatrixCredentials, CredentialStore
from yuxi.channels.adapters.matrix.sync_state import SyncState, SyncStateMachine
from yuxi.channels.adapters.matrix.sent_cache import SentMessageCache, ResponseSizeLimiter, hash_content
from yuxi.channels.adapters.matrix.voice import send_voice_note, send_tts_message, is_voice_message, extract_audio_info
from yuxi.channels.adapters.matrix.media_meta import extract_media_metadata, extract_duration_bytes
from yuxi.channels.adapters.matrix.setup_wizard import MatrixSetupWizard
from yuxi.channels.adapters.matrix.cli import MatrixCLI, get_cli_commands, format_cli_help
from yuxi.channels.adapters.matrix.doctor import MatrixDoctor
from yuxi.channels.adapters.matrix.subagent_hooks import SubagentLifecycle, build_subagent_event
from yuxi.channels.adapters.matrix.exec_approvals import ExecApprovalManager, ExecApprovalRequest
from yuxi.channels.adapters.matrix.commands import (
detect_slash_command,
is_slash_command,
get_command_list,
format_help_text,
)
from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding, ACPBindingManager
from yuxi.channels.adapters.matrix.draft_stream import (
DraftStreamSession,
send_draft_update,
send_live_marker,
)
from yuxi.channels.adapters.matrix.config_ui_hints import get_config_hints, get_hint, get_group_hints, get_groups
from yuxi.channels.adapters.matrix.sync_state import SyncState, SyncStateMachine
from yuxi.channels.adapters.matrix.sync_store import SSRFGuard, SyncTokenStore
from yuxi.channels.adapters.matrix.thread_bindings import ThreadBinding, ThreadBindingManager
from yuxi.channels.adapters.matrix.voice import extract_audio_info, is_voice_message, send_tts_message, send_voice_note
__all__ = [
"MatrixAdapter",

View File

@ -1,7 +1,106 @@
from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from enum import Enum, auto
from typing import Any
from loguru import logger
class ClientLifecycleState(Enum):
NONE = auto()
PREPARED = auto()
STARTED = auto()
STOPPED = auto()
ERROR = auto()
class ActiveClientTracker:
def __init__(self):
self._client_map: dict[str, Any] = {}
self._states: dict[str, ClientLifecycleState] = {}
self._sync_tasks: dict[str, asyncio.Task] = {}
self._lock = asyncio.Lock()
@property
def active_count(self) -> int:
return sum(1 for s in self._states.values() if s == ClientLifecycleState.STARTED)
def register(
self,
account_id: str,
client: Any,
state: ClientLifecycleState = ClientLifecycleState.PREPARED,
) -> None:
self._client_map[account_id] = client
self._states[account_id] = state
def unregister(self, account_id: str) -> None:
self._client_map.pop(account_id, None)
self._states.pop(account_id, None)
self._sync_tasks.pop(account_id, None)
def get_client(self, account_id: str) -> Any | None:
return self._client_map.get(account_id)
def get_state(self, account_id: str) -> ClientLifecycleState:
return self._states.get(account_id, ClientLifecycleState.NONE)
def transition(self, account_id: str, target: ClientLifecycleState) -> bool:
current = self.get_state(account_id)
valid_transitions = {
ClientLifecycleState.NONE: {ClientLifecycleState.PREPARED},
ClientLifecycleState.PREPARED: {ClientLifecycleState.STARTED, ClientLifecycleState.ERROR},
ClientLifecycleState.STARTED: {ClientLifecycleState.STOPPED, ClientLifecycleState.ERROR},
ClientLifecycleState.STOPPED: {ClientLifecycleState.PREPARED},
ClientLifecycleState.ERROR: {ClientLifecycleState.PREPARED, ClientLifecycleState.STOPPED},
}
if target not in valid_transitions.get(current, set()):
logger.warning(f"[Matrix] Invalid state transition for '{account_id}': {current.name} -> {target.name}")
return False
self._states[account_id] = target
return True
def set_sync_task(self, account_id: str, task: asyncio.Task) -> None:
self._sync_tasks[account_id] = task
def list_active(self) -> list[str]:
return [aid for aid, s in self._states.items() if s == ClientLifecycleState.STARTED]
def list_all_ids(self) -> list[str]:
return list(self._client_map.keys())
@asynccontextmanager
async def account_client_context(account_id: str, tracker: ActiveClientTracker, client_factory):
if not tracker.transition(account_id, ClientLifecycleState.PREPARED):
raise RuntimeError(f"Cannot prepare client for account '{account_id}'")
client = None
try:
client = client_factory(account_id)
tracker.register(account_id, client, ClientLifecycleState.PREPARED)
if not tracker.transition(account_id, ClientLifecycleState.STARTED):
raise RuntimeError(f"Cannot start client for account '{account_id}'")
yield client
except Exception:
tracker.transition(account_id, ClientLifecycleState.ERROR)
raise
finally:
try:
tracker.transition(account_id, ClientLifecycleState.STOPPED)
except Exception:
pass
if client is not None:
try:
await client.close()
except Exception:
pass
class MatrixAccount:
def __init__(self, account_id: str, config: dict[str, Any]):

View File

@ -6,20 +6,20 @@ from typing import Any, ClassVar
from nio import (
AsyncClient,
DownloadResponse,
LocalProtocolError,
LoginError,
LoginResponse,
LocalProtocolError,
SyncError,
)
from yuxi.channels.base import BaseChannelAdapter
from yuxi.channels.capabilities import ChannelCapabilities
from yuxi.channels.meta import ChannelMeta
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
from yuxi.channels.exceptions import (
ChannelAuthenticationError,
ChannelException,
)
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
from yuxi.channels.meta import ChannelMeta
from yuxi.channels.models import (
ChannelMessage,
ChannelResponse,
@ -32,10 +32,12 @@ from yuxi.channels.registry import register_builtin_adapter
from yuxi.utils.datetime_utils import utc_now_naive
from yuxi.utils.logging_config import logger
from .acp_bindings import ACPBindingManager
from .auto_join import should_auto_join
from .config_patch import MatrixAccountPatch, apply_patch
from .crypto import import_room_keys
from .encryption import check_crypto_store_ready, format_encryption_unavailable_error
from .exec_approvals import ExecApprovalManager
from .group_mentions import GroupMentionStrategy
from .monitor import MatrixMonitor
from .normalizer import MatrixNormalizer
@ -53,10 +55,8 @@ from .send import (
send_thread_message,
)
from .session import MatrixSessionHelper
from .sync_store import SSRFGuard, SyncTokenStore
from .acp_bindings import ACPBindingManager
from .subagent_hooks import SubagentLifecycle
from .exec_approvals import ExecApprovalManager
from .sync_store import SSRFGuard, SyncTokenStore
_DIRECT_ROOM_TYPE = "m.direct"
@ -115,6 +115,7 @@ class MatrixAdapter(BaseChannelAdapter):
self._exec_approvals = ExecApprovalManager(self.config)
crypto_store_dir = self.config.get("crypto_store_dir", "./matrix_crypto_store")
self._sync_token_store = SyncTokenStore(crypto_store_dir, self.config.get("user_id", "unknown"))
self._use_msc4357: bool = False
@property
def user_id(self) -> str:
@ -229,6 +230,8 @@ class MatrixAdapter(BaseChannelAdapter):
self._homeserver = homeserver
logger.info(f"Matrix adapter connected: {user_id} @ {homeserver}")
self._use_msc4357 = await self._detect_msc4357_support()
async def disconnect(self) -> None:
self._status = ChannelStatus.DISCONNECTED
self._ready_event.clear()
@ -412,6 +415,20 @@ class MatrixAdapter(BaseChannelAdapter):
if not self._client:
return DeliveryResult(success=False, error="Client not initialized")
if self._use_msc4357 and not finished:
from yuxi.channels.adapters.matrix.draft_stream import send_draft_update
async def _do_send():
return await send_draft_update(
self._client,
chat_id,
chunk,
draft_event_id=msg_id or None,
finished=finished,
)
return await self._send_with_circuit(_do_send)
async def _do_send():
return await send_stream_chunk(
self._client,
@ -424,6 +441,23 @@ class MatrixAdapter(BaseChannelAdapter):
return await self._send_with_circuit(_do_send)
async def _detect_msc4357_support(self) -> bool:
if not self._client:
return False
try:
resp = await self._client.versions()
unstable_features = resp.unstable_features or {}
supported = "org.matrix.msc4357.live" in unstable_features
if supported:
logger.info("[Matrix] MSC4357 live marker supported by server")
else:
logger.debug("[Matrix] MSC4357 not advertised by server, using m.replace fallback")
return supported
except Exception:
logger.debug("[Matrix] Failed to probe MSC4357, using m.replace fallback")
return False
async def send_reaction(self, chat_id: str, msg_id: str, emoji: str) -> DeliveryResult:
if not self._client:
return DeliveryResult(success=False, error="Client not initialized")

View File

@ -3,13 +3,12 @@ from __future__ import annotations
import os
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from nio import AsyncClient
from .config_schema import validate_config
from .encryption import check_crypto_store_ready
from .sync_store import SyncTokenStore, SSRFGuard
from .sync_store import SSRFGuard, SyncTokenStore
class MatrixDoctor:

View File

@ -1,10 +1,9 @@
from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from collections.abc import Callable, Awaitable
from datetime import datetime, UTC
from nio import (
RoomMemberEvent,
@ -24,12 +23,13 @@ from yuxi.channels.models import (
)
from yuxi.utils.logging_config import logger
from .allowlist import match_user_in_list
from .dedupe import EventDeduplicator
from .security import MatrixSecurityPolicy
from .allowlist import match_user_in_list
if TYPE_CHECKING:
from nio import AsyncClient
from yuxi.channels.adapters.matrix.adapter import MatrixAdapter
_MESSAGE_SYNC_FILTER = {

View File

@ -1,7 +1,7 @@
from __future__ import annotations
import re
from datetime import datetime, UTC
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from nio import (
@ -24,10 +24,10 @@ from yuxi.channels.models import (
MessageType,
)
from .rooms_config import get_room_require_mention
from .thread_utils import is_thread_event
from .location import parse_location_event
from .rooms_config import get_room_require_mention
from .sticker import is_sticker_event, parse_sticker
from .thread_utils import is_thread_event
if TYPE_CHECKING:
from yuxi.channels.adapters.matrix.adapter import MatrixAdapter

View File

@ -2,7 +2,6 @@ from __future__ import annotations
from typing import Any
_STICKER_CONTENT_KEY = "body"
_STICKER_INFO_KEY = "info"

View File

@ -1,6 +1,5 @@
from __future__ import annotations
_SYNC_TOKEN_FILE = "matrix_sync_token.json"