oclaw/interfaces/http/weixin_ilink_api.py
oliver 4a23b715a2 重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
2026-04-25 01:24:23 +08:00

95 lines
3.4 KiB
Python

from __future__ import annotations
import secrets
import time
from typing import Any
from fastapi import APIRouter, Header, HTTPException, Request
router = APIRouter()
def _require_ilink_auth(
*,
authorization_type: str | None,
authorization: str | None,
) -> None:
# Minimal contract required by oclaw-weixin:
# - AuthorizationType: ilink_bot_token
# - Authorization: Bearer <token>
if (authorization_type or "").strip() != "ilink_bot_token":
raise HTTPException(status_code=401, detail="invalid AuthorizationType")
auth = (authorization or "").strip()
if not auth.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="missing bearer token")
def _now_ms() -> int:
return int(time.time() * 1000)
@router.post("/ilink/bot/getupdates")
async def ilink_getupdates(
body: dict[str, Any],
request: Request,
authorizationtype: str | None = Header(default=None, alias="AuthorizationType"),
authorization: str | None = Header(default=None, alias="Authorization"),
) -> dict[str, Any]:
_require_ilink_auth(authorization_type=authorizationtype, authorization=authorization)
# TODO: implement real long-poll cursor + message queue.
# For now, return an empty poll result so the plugin can stay connected.
buf = str(body.get("get_updates_buf") or "")
_ = request
return {"ret": 0, "msgs": [], "get_updates_buf": buf, "longpolling_timeout_ms": 35000}
@router.post("/ilink/bot/sendmessage")
async def ilink_sendmessage(
body: dict[str, Any],
authorizationtype: str | None = Header(default=None, alias="AuthorizationType"),
authorization: str | None = Header(default=None, alias="Authorization"),
) -> dict[str, Any]:
_require_ilink_auth(authorization_type=authorizationtype, authorization=authorization)
# TODO: map outbound message into our channel sender (when implemented).
_ = body
return {"ret": 0}
@router.post("/ilink/bot/getuploadurl")
async def ilink_getuploadurl(
body: dict[str, Any],
authorizationtype: str | None = Header(default=None, alias="AuthorizationType"),
authorization: str | None = Header(default=None, alias="Authorization"),
) -> dict[str, Any]:
_require_ilink_auth(authorization_type=authorizationtype, authorization=authorization)
# TODO: implement AES-128-ECB upload flow + pre-signed URL integration.
_ = body
return {"ret": 0, "upload_param": "", "thumb_upload_param": ""}
@router.post("/ilink/bot/getconfig")
async def ilink_getconfig(
body: dict[str, Any],
authorizationtype: str | None = Header(default=None, alias="AuthorizationType"),
authorization: str | None = Header(default=None, alias="Authorization"),
) -> dict[str, Any]:
_require_ilink_auth(authorization_type=authorizationtype, authorization=authorization)
_ = body
# typing_ticket is required by sendtyping; we return a stable random-like token per request for now.
return {"ret": 0, "typing_ticket": secrets.token_urlsafe(24)}
@router.post("/ilink/bot/sendtyping")
async def ilink_sendtyping(
body: dict[str, Any],
authorizationtype: str | None = Header(default=None, alias="AuthorizationType"),
authorization: str | None = Header(default=None, alias="Authorization"),
) -> dict[str, Any]:
_require_ilink_auth(authorization_type=authorizationtype, authorization=authorization)
_ = body
return {"ret": 0}
__all__ = ["router"]