重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。

本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
This commit is contained in:
oliver 2026-04-25 01:24:23 +08:00
parent ba3836f00f
commit 4a23b715a2
498 changed files with 2760 additions and 2200 deletions

View file

@ -0,0 +1,36 @@
# WhatsApp 扩展(中文重写)
对应上游目录:`vendor/oclaw/extensions/whatsapp`
## 能力定位
- 作为 Oclaw 渠道插件接入 WhatsApp。
- 负责账号接入、消息目标标准化、群/目录策略、命令策略和运行时辅助能力。
## 关键入口
- `api.ts`:对外导出主要能力与工具函数(channel 插件、策略、target 规范化等)。
- `oclaw.plugin.json`:插件元信息(`id=whatsapp`,渠道声明、配置 schema)。
- `oclaw/`:具体实现(收发链路、访问控制、配置解析、运行时逻辑)。
## API 文件清单(根目录)
- `action-runtime-api.ts`
- `channel-config-api.ts`
- `channel-plugin-api.ts`
- `config-api.ts`
- `contract-api.ts`
- `directory-contract-api.ts`
- `doctor-contract-api.ts`
- `legacy-session-surface-api.ts`
- `legacy-state-migrations-api.ts`
- `light-runtime-api.ts`
- `login-qr-api.ts`
- `outbound-payload-test-api.ts`
- `runtime-api.ts`
- `secret-contract-api.ts`
- `security-contract-api.ts`
- `setup-plugin-api.ts`
- `test-api.ts`
## 运行关注点
- 群聊与私聊 target 的标准化与合法性判断。
- allowlist / group policy 等访问控制策略是否命中。
- 登录态与二维码链路是否稳定。

View file

@ -0,0 +1,18 @@
from .api import (
WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS,
is_whatsapp_group_jid,
is_whatsapp_user_target,
looks_like_whatsapp_target_id,
normalize_whatsapp_allow_from_entries,
normalize_whatsapp_target,
)
__all__ = [
"WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS",
"is_whatsapp_group_jid",
"is_whatsapp_user_target",
"looks_like_whatsapp_target_id",
"normalize_whatsapp_allow_from_entries",
"normalize_whatsapp_target",
]

View file

@ -0,0 +1,49 @@
from __future__ import annotations
import re
from typing import Iterable
WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS: tuple[str, ...] = ("whatsapp", "legacy_outbound_send")
_GROUP_SUFFIX = "@g.us"
_USER_SUFFIX = "@s.whatsapp.net"
def is_whatsapp_group_jid(value: str) -> bool:
return str(value or "").strip().lower().endswith(_GROUP_SUFFIX)
def is_whatsapp_user_target(value: str) -> bool:
v = str(value or "").strip().lower()
return v.endswith(_USER_SUFFIX) or bool(re.fullmatch(r"\+?\d{6,20}", v))
def looks_like_whatsapp_target_id(value: str) -> bool:
v = str(value or "").strip().lower()
return is_whatsapp_group_jid(v) or is_whatsapp_user_target(v)
def normalize_whatsapp_target(value: str) -> str:
raw = str(value or "").strip()
if not raw:
raise ValueError("whatsapp target is required")
low = raw.lower()
if low.endswith(_GROUP_SUFFIX) or low.endswith(_USER_SUFFIX):
return low
digits = re.sub(r"[^\d+]", "", raw)
if digits.startswith("+"):
digits = digits[1:]
if not digits:
raise ValueError(f"invalid whatsapp target: {value}")
return f"{digits}{_USER_SUFFIX}"
def normalize_whatsapp_allow_from_entries(entries: Iterable[str] | None) -> tuple[str, ...]:
out: list[str] = []
for item in entries or ():
try:
out.append(normalize_whatsapp_target(str(item)))
except Exception:
continue
return tuple(sorted(set(out)))