manus-skills/.kortix/opencode/skills/content/iflytek-text-proofread/scripts/text_proofread.py
zhukang 1c7abd81d1 All 310 Manus skill-hub community skills as a Kortix marketplace registry
- 310 registry:skill items, 1528 files, 7 categories (hub tags)
- registry.json generated by @kortix/registry buildRegistry
- nested SKILL.md in sn-da-* renamed SUBSKILL.md (no phantom skills)
- provenance + licenses: SOURCES.md
2026-09-15 13:37:20 +08:00

299 lines
9.3 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
iFlytek Official Document Proofreading (公文校对) — pure Python stdlib, no pip dependencies.
Env vars required:
IFLY_APP_ID - 讯飞 appId
IFLY_API_KEY - apiKey
IFLY_API_SECRET - apiSecret
Usage:
python3 text_proofread.py "要校对的文本内容"
echo "要校对的文本" | python3 text_proofread.py -
python3 text_proofread.py --file input.txt
"""
import argparse
import base64
import datetime
import hashlib
import hmac
import json
import os
import sys
import urllib.parse
import urllib.request
API_URL = "https://cn-huadong-1.xf-yun.com/v1/private/s37b42a45"
# Error category ID → description mapping
CATEGORY_MAP = {
9: "错别字、词",
31: "多字错误",
32: "少字错误",
35: "语义重复",
34: "语序错误",
39: "量和单位差错",
36: "数字差错",
20: "句式杂糅",
21: "标点符号差错",
24: "句子查重",
119: "重要讲话引用",
123: "地理名词",
19: "机构名称",
124: "专有名词及术语",
122: "媒体报道禁用词和慎用词",
6: "常识差错",
111: "涉低俗辱骂",
118: "其他敏感内容",
}
# Action ID → label
ACTION_MAP = {
1: "标记",
2: "替换",
4: "删除",
}
# ---------------------------------------------------------------------------
# Auth helpers (ported from official demo, using stdlib only)
# ---------------------------------------------------------------------------
def _format_date_rfc1123():
"""Return current UTC time in RFC 1123 format: 'Mon, 06 Mar 2026 06:50:00 GMT'."""
now = datetime.datetime.now(datetime.timezone.utc)
# Weekday and month abbreviations in English
weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
return "{}, {:02d} {} {} {:02d}:{:02d}:{:02d} GMT".format(
weekdays[now.weekday()], now.day, months[now.month], now.year,
now.hour, now.minute, now.second
)
def _parse_url(url):
"""Extract host, path, schema from URL."""
idx = url.index("://")
schema = url[:idx + 3]
rest = url[idx + 3:]
slash = rest.index("/")
host = rest[:slash]
path = rest[slash:]
return host, path, schema
def _build_auth_url(url, api_key, api_secret):
"""Build the authenticated request URL with HMAC-SHA256 signature."""
host, path, schema = _parse_url(url)
date = _format_date_rfc1123()
# signature_origin = "host: {host}\ndate: {date}\nPOST {path} HTTP/1.1"
signature_origin = "host: {}\ndate: {}\nPOST {} HTTP/1.1".format(host, date, path)
# HMAC-SHA256
signature_sha = hmac.new(
api_secret.encode("utf-8"),
signature_origin.encode("utf-8"),
digestmod=hashlib.sha256,
).digest()
signature = base64.b64encode(signature_sha).decode("utf-8")
# authorization_origin
authorization_origin = (
'api_key="{}", algorithm="hmac-sha256", headers="host date request-line", signature="{}"'
.format(api_key, signature)
)
authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode("utf-8")
params = {
"host": host,
"date": date,
"authorization": authorization,
}
return url + "?" + urllib.parse.urlencode(params)
# ---------------------------------------------------------------------------
# Request body & HTTP call
# ---------------------------------------------------------------------------
def _build_body(app_id, text):
"""Build the JSON request body per API spec."""
return {
"header": {
"app_id": app_id,
"status": 3,
},
"parameter": {
"midu_correct": {
"output_result": {
"encoding": "utf8",
"compress": "raw",
"format": "json",
}
}
},
"payload": {
"text": {
"encoding": "utf8",
"compress": "raw",
"format": "plain",
"status": 3,
"text": base64.b64encode(text.encode("utf-8")).decode("utf-8"),
}
},
}
def _http_post(url, body_dict, app_id, timeout=60):
data = json.dumps(body_dict).encode("utf-8")
headers = {
"Content-Type": "application/json",
"host": "api.xf-yun.com",
"app_id": app_id,
}
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
# ---------------------------------------------------------------------------
# Result parsing
# ---------------------------------------------------------------------------
def _parse_result(api_response):
"""Parse the API response, decode base64 text field, return structured result."""
header = api_response.get("header", {})
code = header.get("code", -1)
message = header.get("message", "")
if code != 0:
return {"error": True, "code": code, "message": message}
payload = api_response.get("payload", {})
output = payload.get("output_result", {})
text_b64 = output.get("text", "")
if not text_b64:
return {"error": True, "code": -1, "message": "No output_result.text in response"}
decoded = base64.b64decode(text_b64).decode("utf-8")
result = json.loads(decoded)
return result
def _format_output(result, raw=False, context_chars=0):
"""Format parsed result for human-readable output."""
if raw:
return json.dumps(result, ensure_ascii=False, indent=2)
if result.get("error"):
return "Error: code={}, message={}".format(result.get("code"), result.get("message"))
code = result.get("code", -1)
if code != 200:
return "Service error: code={}, msg={}".format(code, result.get("msg", ""))
data = result.get("data", {})
checklist = data.get("checklist", [])
if not checklist:
return "✅ 无错误 — 文本校对通过"
lines = ["🔍 发现 {} 处问题:".format(len(checklist)), ""]
for i, item in enumerate(checklist, 1):
word = item.get("word", "")
explanation = item.get("explanation", "")
context = item.get("context", "")
position = item.get("position", "")
length = item.get("length", "")
suggests = item.get("suggest", [])
type_info = item.get("type", {})
type_name = type_info.get("name", "")
belong_id = type_info.get("belongId", 0)
category = CATEGORY_MAP.get(belong_id, type_info.get("desc", ""))
action_info = item.get("action", {})
action_id = action_info.get("id", 0)
action_label = ACTION_MAP.get(action_id, str(action_id))
lines.append("{}. 「{}」".format(i, word))
if category:
lines.append(" 分类: {} ({})".format(category, type_name))
if explanation:
lines.append(" 说明: {}".format(explanation))
if context:
lines.append(" 上下文: {}".format(context))
lines.append(" 位置: {} (长度 {})".format(position, length))
lines.append(" 动作: {}".format(action_label))
if suggests:
lines.append(" 建议: {}".format(" / ".join(suggests)))
lines.append("")
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="iFlytek Official Document Proofreading (公文校对)"
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("text", nargs="?", default=None,
help="Text to proofread (use '-' to read from stdin)")
group.add_argument("--file", "-f", default=None,
help="Read text from a file")
parser.add_argument("--raw", action="store_true",
help="Output raw JSON response (decoded)")
args = parser.parse_args()
# --- credentials ---
app_id = os.environ.get("IFLY_APP_ID", "")
api_key = os.environ.get("IFLY_API_KEY", "")
api_secret = os.environ.get("IFLY_API_SECRET", "")
if not all([app_id, api_key, api_secret]):
print("Error: Set IFLY_APP_ID, IFLY_API_KEY, and IFLY_API_SECRET env vars.",
file=sys.stderr)
sys.exit(1)
# --- get text ---
if args.file:
with open(args.file, "r", encoding="utf-8") as f:
text = f.read()
elif args.text == "-":
text = sys.stdin.read()
else:
text = args.text
if not text or not text.strip():
print("Error: No text provided.", file=sys.stderr)
sys.exit(1)
# Limit check (220000 chars)
if len(text) > 220000:
print("Warning: Text length ({}) exceeds 220000 char limit, truncating.".format(len(text)),
file=sys.stderr)
text = text[:220000]
# --- call API ---
auth_url = _build_auth_url(API_URL, api_key, api_secret)
body = _build_body(app_id, text)
response = _http_post(auth_url, body, app_id)
# --- parse & output ---
result = _parse_result(response)
print(_format_output(result, raw=args.raw))
if __name__ == "__main__":
main()