ForcePilot/backend/package/yuxi/channel/extensions/signal/identity.py
Kris 6713dc2d0d feat(channel): 添加 Signal 渠道扩展
新增 Signal 渠道扩展,支持在 Yuxi 平台中集成 Signal 加密即时通讯渠道。

包含以下功能模块:
- client: Signal 客户端封装
- daemon: signald 守护进程管理
- config_schema: 配置模式
- send: 消息发送
- accounts: 账户管理
- account_management: 账户综合管理
- access_policy: 访问策略
- identity: 身份管理
- profiles: 用户资料
- groups: 群组管理
- format: 消息格式转换
- normalize: 消息规范化
- dedupe: 消息去重
- monitor: 渠道状态监控
- probe: 健康探测
- sse_reconnect: SSE 重连机制
2026-05-21 11:41:55 +08:00

94 lines
2.6 KiB
Python

import re
from dataclasses import dataclass
from typing import Literal
@dataclass
class SignalSenderPhone:
kind: Literal["phone"] = "phone"
raw: str = ""
e164: str = ""
@dataclass
class SignalSenderUuid:
kind: Literal["uuid"] = "uuid"
raw: str = ""
SignalSender = SignalSenderPhone | SignalSenderUuid
UUID_RE = re.compile(
r"^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$",
re.IGNORECASE,
)
def resolve_signal_sender(source_number: str | None, source_uuid: str | None) -> SignalSender | None:
if source_number:
return SignalSenderPhone(raw=source_number, e164=normalize_e164(source_number))
if source_uuid:
return SignalSenderUuid(raw=source_uuid)
return None
def normalize_e164(raw: str) -> str:
value = raw.strip()
if not value.startswith("+"):
return f"+{value}"
return value
def format_signal_sender_id(sender: SignalSender) -> str:
if isinstance(sender, SignalSenderUuid):
return f"uuid:{sender.raw}"
return sender.e164 or sender.raw
def format_signal_sender_display(sender: SignalSender) -> str:
return format_signal_sender_id(sender)
def format_signal_pairing_id_line(sender: SignalSender) -> str:
if isinstance(sender, SignalSenderUuid):
return f"Your Signal UUID: {sender.raw}"
return f"Your Signal number: {sender.e164 or sender.raw}"
def resolve_signal_recipient(sender: SignalSender) -> str:
if isinstance(sender, SignalSenderUuid):
return sender.raw
return sender.e164 or sender.raw
def resolve_signal_peer_id(sender: SignalSender) -> str:
return format_signal_sender_id(sender)
def match_signal_allow_entry(peer_id: str, allow_entry: str) -> bool:
if allow_entry == "*":
return True
lowered_entry = allow_entry.lower().strip()
if lowered_entry.startswith("uuid:"):
uuid_part = lowered_entry[len("uuid:") :].replace("-", "").lower()
peer_uuid = peer_id.replace("-", "").lower()
if peer_uuid.startswith("uuid:"):
peer_uuid = peer_uuid[len("uuid:") :]
return peer_uuid == uuid_part
peer_normalized = peer_id.strip()
if peer_normalized.startswith("signal:"):
peer_normalized = peer_normalized[len("signal:") :]
if not peer_normalized.startswith("+"):
peer_normalized = f"+{peer_normalized}"
entry_normalized = lowered_entry
if entry_normalized.startswith("signal:"):
entry_normalized = entry_normalized[len("signal:") :]
if not entry_normalized.startswith("+"):
entry_normalized = f"+{entry_normalized}"
return peer_normalized == entry_normalized