wechat-robot-skills/skills/create-memory/scripts/create_memory.py
2026-08-23 15:08:29 +08:00

144 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from typing import Any, NoReturn
sys.stderr = sys.stdout
class SkillArgumentParser(argparse.ArgumentParser):
def error(self, message: str) -> NoReturn:
raise ValueError(f"参数错误:{message}")
def _parse_args(argv: list[str]) -> argparse.Namespace:
parser = SkillArgumentParser(description="创建当前微信会话的长期记忆")
parser.add_argument("--dry-run", action="store_true")
return parser.parse_args(argv)
def _require_env(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
raise ValueError(f"环境变量 {name} 未配置")
return value
def _positive_int_env(name: str) -> int:
raw = _require_env(name)
try:
value = int(raw)
except ValueError as exc:
raise ValueError(f"环境变量 {name} 必须是整数") from exc
if value < 1:
raise ValueError(f"环境变量 {name} 必须是正整数")
return value
def _optional_positive_int_env(name: str) -> int | None:
raw = os.environ.get(name, "").strip()
if not raw:
return None
try:
value = int(raw)
except ValueError as exc:
raise ValueError(f"环境变量 {name} 必须是整数") from exc
if value < 0:
raise ValueError(f"环境变量 {name} 不能是负数")
return value or None
def _client_port() -> int:
raw = _require_env("ROBOT_WECHAT_CLIENT_PORT")
try:
port = int(raw)
except ValueError as exc:
raise ValueError("机器人客户端端口必须是整数") from exc
if port < 1 or port > 65535:
raise ValueError("机器人客户端端口必须在 1 到 65535 之间")
return port
def _request_body() -> dict[str, Any]:
body: dict[str, Any] = {
"message_id": _positive_int_env("ROBOT_MESSAGE_ID"),
}
referenced_message_id = _optional_positive_int_env("ROBOT_REF_MESSAGE_ID")
if referenced_message_id is not None:
body["referenced_message_id"] = referenced_message_id
return body
def _post_json(url: str, body: dict[str, Any]) -> dict[str, Any]:
request = urllib.request.Request(
url,
data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=300) as response:
text = response.read().decode("utf-8")
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace").strip()
raise RuntimeError(f"客户端返回 HTTP {exc.code}:{detail or exc.reason}") from exc
except urllib.error.URLError as exc:
if isinstance(exc.reason, TimeoutError):
raise RuntimeError(
"请求超时,记忆可能已经保存,请先查询后再决定是否重试"
) from exc
raise RuntimeError(f"无法连接机器人客户端:{exc.reason}") from exc
except TimeoutError as exc:
raise RuntimeError("请求超时,记忆可能已经保存,请先查询后再决定是否重试") from exc
except OSError as exc:
raise RuntimeError(
f"请求中断,记忆可能已经保存,请先查询后再决定是否重试:{exc}"
) from exc
try:
payload = json.loads(text)
except json.JSONDecodeError as exc:
raise RuntimeError("机器人客户端返回了无效 JSON") from exc
if not isinstance(payload, dict):
raise RuntimeError("机器人客户端返回格式错误")
return payload
def _validate_success(payload: dict[str, Any]) -> None:
if payload.get("code") != 200:
message = str(payload.get("message") or "未知业务错误").strip()
raise RuntimeError(message)
def main() -> int:
try:
args = _parse_args(sys.argv[1:])
body = _request_body()
port = _client_port()
if args.dry_run:
output = {"ok": True, "dry_run": True, "request": body}
else:
url = f"http://127.0.0.1:{port}/api/v1/robot/memories"
payload = _post_json(url, body)
_validate_success(payload)
output = {
"ok": True,
"message": "长期记忆创建成功",
}
sys.stdout.write(json.dumps(output, ensure_ascii=False) + "\n")
return 0
except (ValueError, RuntimeError) as exc:
sys.stdout.write(f"创建记忆失败:{exc}\n")
return 1
if __name__ == "__main__":
raise SystemExit(main())