新增 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: 媒体资源处理
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import logging
|
|
|
|
from requests_oauthlib import OAuth1Session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
X_API_V1_BASE = "https://api.x.com"
|
|
|
|
|
|
def _build_oauth(account: dict) -> OAuth1Session:
|
|
return OAuth1Session(
|
|
account["api_key"],
|
|
client_secret=account["api_secret"],
|
|
resource_owner_key=account["access_token"],
|
|
resource_owner_secret=account["access_secret"],
|
|
)
|
|
|
|
|
|
def _resolve_webhook_env(account: dict) -> str:
|
|
return account.get("webhook_env", "dev")
|
|
|
|
|
|
def verify_crc_token(crc_token: str, consumer_secret: str) -> str:
|
|
digest = hmac.new(
|
|
consumer_secret.encode("utf-8"),
|
|
crc_token.encode("utf-8"),
|
|
hashlib.sha256,
|
|
).digest()
|
|
return "sha256=" + base64.b64encode(digest).decode()
|
|
|
|
|
|
def verify_webhook_signature(signature: str, body: bytes, consumer_secret: str) -> bool:
|
|
expected = hmac.new(
|
|
consumer_secret.encode("utf-8"),
|
|
body,
|
|
hashlib.sha256,
|
|
).digest()
|
|
expected_b64 = base64.b64encode(expected).decode()
|
|
return hmac.compare_digest(f"sha256={expected_b64}", signature)
|
|
|
|
|
|
async def register_webhook(account: dict, env_name: str | None = None) -> dict | None:
|
|
oauth = _build_oauth(account)
|
|
webhook_url = account.get("webhook_url", "")
|
|
if not webhook_url:
|
|
logger.error("Twitter webhook: no webhook_url configured")
|
|
return None
|
|
|
|
env_name = env_name or _resolve_webhook_env(account)
|
|
url = f"{X_API_V1_BASE}/1.1/account_activity/all/{env_name}/webhooks.json"
|
|
try:
|
|
resp = oauth.post(url, data={"url": webhook_url}, timeout=15)
|
|
if resp.status_code in (200, 201):
|
|
return resp.json()
|
|
logger.error(
|
|
"Twitter webhook register failed: HTTP %d %s",
|
|
resp.status_code,
|
|
resp.text[:200],
|
|
)
|
|
except Exception as e:
|
|
logger.error("Twitter webhook register error: %s", e)
|
|
return None
|
|
|
|
|
|
async def subscribe_account_activity(
|
|
account: dict, env_name: str | None = None
|
|
) -> bool:
|
|
oauth = _build_oauth(account)
|
|
env_name = env_name or _resolve_webhook_env(account)
|
|
url = f"{X_API_V1_BASE}/1.1/account_activity/all/{env_name}/subscriptions.json"
|
|
try:
|
|
resp = oauth.post(url, timeout=10)
|
|
if resp.status_code in (200, 204):
|
|
logger.info("Twitter account activity subscribed for env %s", env_name)
|
|
return True
|
|
logger.error(
|
|
"Twitter subscription failed: HTTP %d %s", resp.status_code, resp.text[:200]
|
|
)
|
|
except Exception as e:
|
|
logger.error("Twitter subscription error: %s", e)
|
|
return False
|
|
|
|
|
|
async def unsubscribe_account_activity(
|
|
account: dict, env_name: str | None = None
|
|
) -> bool:
|
|
oauth = _build_oauth(account)
|
|
user_id = account.get("user_id", "")
|
|
if not user_id:
|
|
return False
|
|
|
|
env_name = env_name or _resolve_webhook_env(account)
|
|
url = f"{X_API_V1_BASE}/1.1/account_activity/all/{env_name}/subscriptions/{user_id}.json"
|
|
try:
|
|
resp = oauth.delete(url, timeout=10)
|
|
if resp.status_code in (200, 204):
|
|
logger.info("Twitter account activity unsubscribed for user %s", user_id)
|
|
return True
|
|
except Exception as e:
|
|
logger.error("Twitter unsubscribe error: %s", e)
|
|
return False
|