ForcePilot/backend/package/yuxi/channel/extensions/twitter/monitor.py
Kris 1c590097be feat(channel): 添加 Twitter 和 Viber 渠道扩展
新增 Twitter 和 Viber 两个渠道扩展。

Twitter 渠道扩展功能模块:
- auth: OAuth 认证管理
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- tweets: 推文管理
- social: 社交互动
- reactions: 表情反应
- media: 媒体资源处理

Viber 渠道扩展功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- rate_limiter: 速率限制
- media: 媒体资源处理
2026-05-21 11:57:22 +08:00

171 lines
5.2 KiB
Python

from __future__ import annotations
import logging
from datetime import UTC, datetime
logger = logging.getLogger(__name__)
def convert_dm_event_to_unified(event: dict, account_id: str) -> dict | None:
event_id = str(event.get("id", ""))
event_type = event.get("event_type", "")
if event_type != "MessageCreate":
return None
text = event.get("text", "")
sender_id = str(event.get("sender_id", ""))
conversation_id = str(event.get("dm_conversation_id", ""))
participant_ids = event.get("participant_ids", [])
chat_type = "direct" if len(participant_ids) <= 2 else "group"
attachments = event.get("attachments", [])
media_urls: list[str] = []
attachment_media_id = None
attachment_type = None
if attachments:
att = attachments[0]
attachment_media_id = att.get("media_key", "")
attachment_type = att.get("type", "")
if attachment_media_id:
media_urls.append(attachment_media_id)
created_at = event.get("created_at", "")
timestamp = None
if created_at:
try:
timestamp = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
except ValueError:
pass
referenced_tweets = event.get("referenced_tweets", [])
reply_to_id = None
for ref in referenced_tweets:
if ref.get("type") == "replied_to":
reply_to_id = str(ref.get("id", ""))
break
return {
"msg_id": f"tw:{event_id}",
"channel_type": "twitter",
"account_id": account_id,
"content": text,
"message_type": (
"IMAGE"
if attachment_type == "photo"
else ("VIDEO" if attachment_type == "video" else "TEXT")
),
"media_urls": media_urls,
"sender": {
"id": sender_id,
"display_name": f"twitter:{sender_id}",
"kind": "DIRECT" if chat_type == "direct" else "GROUP",
},
"group": (
{
"id": conversation_id,
"kind": chat_type,
}
if chat_type == "group"
else None
),
"timestamp": timestamp,
"reply_to_id": reply_to_id,
"thread_id": None,
"raw_payload": event,
}
def convert_group_event_to_unified(event: dict, account_id: str) -> dict | None:
event_type = event.get("event_type", "")
if event_type not in ("ParticipantsJoin", "ParticipantsLeave"):
return None
event_id = str(event.get("id", ""))
conversation_id = str(event.get("dm_conversation_id", ""))
participant_ids = [str(pid) for pid in event.get("participant_ids", [])]
action = "joined" if event_type == "ParticipantsJoin" else "left"
description = f"Users {action} the group DM"
created_at = event.get("created_at", "")
timestamp = None
if created_at:
try:
timestamp = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
except ValueError:
pass
return {
"msg_id": f"tw:{event_id}",
"channel_type": "twitter",
"account_id": account_id,
"content": description,
"message_type": "EVENT",
"media_urls": [],
"sender": {
"id": participant_ids[0] if participant_ids else "",
"display_name": f"twitter:{participant_ids[0]}" if participant_ids else "",
"kind": "GROUP",
},
"group": {"id": conversation_id, "kind": "group"},
"timestamp": timestamp,
"reply_to_id": None,
"thread_id": None,
"raw_payload": event,
"metadata": {
"event_type": event_type,
"participant_ids": participant_ids,
},
}
def convert_webhook_event_to_unified(
dm_event: dict, for_user_id: str, account_id: str
) -> dict | None:
event_type = dm_event.get("type", "")
if event_type != "message_create":
return None
event_id = str(dm_event.get("id", ""))
msg_data = dm_event.get("message_create", {})
sender_id = str(msg_data.get("sender_id", ""))
message_data = msg_data.get("message_data", {})
text = message_data.get("text", "")
attachment = message_data.get("attachment", {})
media_urls: list[str] = []
if attachment.get("type") == "media":
media = attachment.get("media", {})
media_url = media.get("media_url_https", "")
if media_url:
media_urls.append(media_url)
created_timestamp = dm_event.get("created_timestamp", "")
timestamp = None
if created_timestamp:
try:
ts = int(created_timestamp) / 1000
timestamp = datetime.fromtimestamp(ts, tz=UTC)
except (ValueError, OSError):
pass
return {
"msg_id": f"tw:{event_id}",
"channel_type": "twitter",
"account_id": account_id,
"content": text,
"message_type": "IMAGE" if media_urls else "TEXT",
"media_urls": media_urls,
"sender": {
"id": sender_id,
"display_name": f"twitter:{sender_id}",
"kind": "DIRECT",
},
"group": None,
"timestamp": timestamp,
"reply_to_id": None,
"thread_id": None,
"raw_payload": dm_event,
}