#!/usr/bin/env python3 from __future__ import annotations import argparse import json from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--app-id", required=True) parser.add_argument("--app-secret", required=True) parser.add_argument("--chat-id", required=True) parser.add_argument("--workflow-name", required=True) parser.add_argument("--target-env", required=True) parser.add_argument("--target-url", required=True) parser.add_argument("--run-url", required=True) parser.add_argument( "--output-json", default="", help="Path to the inspection CLI output JSON (carries .summary).", ) return parser.parse_args() def post_json(url: str, payload: dict[str, object], headers: dict[str, str] | None = None) -> dict[str, object]: merged_headers = {"Content-Type": "application/json; charset=utf-8"} if headers: merged_headers.update(headers) request = Request( url, data=json.dumps(payload).encode("utf-8"), headers=merged_headers, method="POST", ) try: with urlopen(request, timeout=30) as response: return json.loads(response.read().decode("utf-8")) except HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") raise RuntimeError(f"HTTP {exc.code} calling {url}: {body}") from exc except URLError as exc: raise RuntimeError(f"network error calling {url}: {exc}") from exc def fetch_tenant_access_token(app_id: str, app_secret: str) -> str: payload = post_json( "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", { "app_id": app_id, "app_secret": app_secret, }, ) token = str(payload.get("tenant_access_token", "")).strip() if not token: raise RuntimeError(f"missing tenant_access_token in Feishu response: {json.dumps(payload, ensure_ascii=True)}") return token def load_summary(output_json_path: str) -> dict[str, object]: if not output_json_path: return {} try: with open(output_json_path, "r", encoding="utf-8") as handle: payload = json.load(handle) except (OSError, ValueError): return {} summary = payload.get("summary") return summary if isinstance(summary, dict) else {} def build_message(args: argparse.Namespace) -> str: summary = load_summary(args.output_json) failed = int(summary.get("failed", 0) or 0) passed = int(summary.get("passed", 0) or 0) diagnostic = int(summary.get("diagnostic", 0) or 0) skipped = int(summary.get("skipped", 0) or 0) function_total = int( summary.get("functionTotal", passed + failed + diagnostic + skipped) or 0 ) role_total = int(summary.get("roleTotal", 0) or 0) module_total = int(summary.get("moduleTotal", 0) or 0) acceptance_status = summary.get("acceptanceStatus") if not summary: status = "error (no report)" elif acceptance_status == "not_run": status = "not run" elif function_total == 0: status = "error (no report)" elif acceptance_status == "failed": status = "failed" elif acceptance_status == "passed" and failed > 0: status = "required acceptance passed; optional case failures" elif acceptance_status not in (None, "passed"): status = "error (unknown acceptance status)" elif failed == 0 and diagnostic > 0: status = "passed with diagnostics" elif failed == 0: status = "passed" else: status = "failed" lines = [ f"NexusCRM {args.target_env} inspection {status}", f"workflow: {args.workflow_name}", f"target_env: {args.target_env}", f"result: {passed} passed / {failed} failed / {diagnostic} diagnostic / {skipped} skipped (of {function_total})", ] functional = summary.get("functional") if isinstance(functional, dict): lines.append( "functional: " f"{int(functional.get('passed', 0) or 0)} passed / " f"{int(functional.get('failed', 0) or 0)} failed / " f"{int(functional.get('skipped', 0) or 0)} skipped " f"(of {int(functional.get('total', 0) or 0)})" ) performance = summary.get("performance") if isinstance(performance, dict): lines.append( "performance: " f"{int(performance.get('passed', 0) or 0)} passed / " f"{int(performance.get('failed', 0) or 0)} failed / " f"{int(performance.get('skipped', 0) or 0)} skipped " f"(of {int(performance.get('total', 0) or 0)})" ) lines.extend([ f"roles: {role_total}", f"modules: {module_total}", f"target_url: {args.target_url}", f"run_url: {args.run_url}", ]) return "\n".join(lines) def send_chat_message(token: str, chat_id: str, message: str) -> None: payload = { "receive_id": chat_id, "msg_type": "text", "content": json.dumps({"text": message}, ensure_ascii=False), } response = post_json( "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id", payload, headers={"Authorization": f"Bearer {token}"}, ) if response.get("code") not in (0, "0", None): raise RuntimeError(f"Feishu send message failed: {json.dumps(response, ensure_ascii=True)}") def main() -> int: args = parse_args() token = fetch_tenant_access_token(args.app_id, args.app_secret) send_chat_message(token, args.chat_id, build_message(args)) return 0 if __name__ == "__main__": raise SystemExit(main())