Download scripts/task_common.py from a3216/gcli2api: direct link, hf CLI and curl.
- Browser
- Download file 9.61 kB
-
https://huggingface.co/spaces/a3216/gcli2api/resolve/main/scripts/task_common.py
- Command line
-
hf download hf://spaces/a3216/gcli2api/scripts/task_common.py
-
curl -L -o task_common.py https://huggingface.co/spaces/a3216/gcli2api/resolve/main/scripts/task_common.py
9.61 kB
| #!/usr/bin/env python3 | |
| """一次性积分任务脚本的共享函数库. | |
| 与 probe_active.py 同风格:从 auths/ 读账号凭证,封装 growth 域 / report 域 | |
| 请求,供 task_*.py 复用。全部默认 dry-run(写动作由调用脚本显式 --yes 放行)。 | |
| 端点权威来源(Go 代码实测 + 本次实测确认): | |
| - chat 域(copilot.tencent.com):growth / tasks / buddy / streak / chat/completions | |
| - billing 域(www.codebuddy.cn):/v2/report | |
| - accept : POST /v2/activity/growth/tasks/accept {"task_codes":[code]} | |
| - claim : POST /v2/activity/growth/tasks/reward/claim {"task_code":code} | |
| """ | |
| import json, os, time, glob, urllib.request, urllib.error | |
| AUTHS = "/root/workbuddy2api/auths" | |
| CHAT_BASE = "https://copilot.tencent.com" # growth / tasks / buddy / streak / chat | |
| BILL_BASE = "https://www.codebuddy.cn" # report / billing | |
| # growth 域常量(travel.go / report.go 与本次实测对齐) | |
| PATH_LIST_TASKS = "/v2/activity/growth/tasks" | |
| PATH_ACCEPT_TASKS = "/v2/activity/growth/tasks/accept" | |
| PATH_CLAIM_REWARD = "/v2/activity/growth/tasks/reward/claim" | |
| PATH_BUDDY_FIRST = "/activity/growth/buddy/first" | |
| PATH_BUDDY_AGREEMENT = "/activity/growth/buddy/agreement" | |
| PATH_STREAK = "/activity/growth/streak" | |
| PATH_REPORT = "/v2/report" | |
| PATH_CHAT = "/v2/chat/completions" | |
| CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2" | |
| def load_auth(uid_or_file: str) -> dict: | |
| """从 auths/ 加载账号凭证,uid_or_file 为 uid 前缀或 auths 文件名。 | |
| 返回 {token, uid, domain, nick, file} 五元组。 | |
| """ | |
| if os.path.sep in uid_or_file or uid_or_file.endswith(".json"): | |
| p = uid_or_file | |
| if not os.path.isabs(p): | |
| p = os.path.join(AUTHS, p) | |
| else: | |
| pre = uid_or_file | |
| hits = glob.glob(os.path.join(AUTHS, f"workbuddy-{pre}*.json")) | |
| if not hits: | |
| raise SystemExit(f"no auth for {pre}") | |
| p = hits[0] | |
| # encoding 必须显式指定:Windows 上 open() 默认用 locale 代码页(中文系统为 | |
| # GBK),而 auth 文件是 UTF-8,非 ASCII 昵称会 UnicodeDecodeError 中断全部任务。 | |
| d = json.load(open(p, encoding="utf-8")) | |
| a, acc = d["auth"], d["account"] | |
| return {"token": a["accessToken"], "domain": a.get("domain") or "", | |
| "uid": acc["uid"], "nick": acc.get("nickname", ""), | |
| "file": os.path.basename(p)} | |
| def chat_base(auth: dict) -> str: | |
| return auth.get("chat_base") or CHAT_BASE | |
| def billing_base(auth: dict) -> str: | |
| return auth.get("billing_base") or BILL_BASE | |
| def _headers(auth: dict) -> dict: | |
| hdr = {"Authorization": "Bearer " + auth["token"], | |
| "Accept": "application/json", | |
| "Content-Type": "application/json", | |
| "User-Agent": CLIENT_UA, | |
| "Origin": "https://www.codebuddy.cn", | |
| "Referer": "https://www.codebuddy.cn/"} | |
| if auth.get("uid"): | |
| hdr["X-User-Id"] = auth["uid"] | |
| if auth.get("domain"): | |
| hdr["X-Domain"] = auth["domain"] | |
| return hdr | |
| def _request(auth, method, base, path, body=None, headers=None, timeout=30): | |
| url = path if path.startswith("http") else base + path | |
| hdr = _headers(auth) | |
| if headers: | |
| hdr.update(headers) | |
| data = json.dumps(body).encode() if body is not None else None | |
| req = urllib.request.Request(url, data=data, headers=hdr, method=method) | |
| try: | |
| with urllib.request.urlopen(req, timeout=timeout) as r: | |
| return r.status, json.loads(r.read().decode("utf-8", "replace")) | |
| except urllib.error.HTTPError as e: | |
| t = e.read().decode("utf-8", "replace") | |
| try: | |
| return e.code, json.loads(t) | |
| except Exception: | |
| return e.code, {"raw": t[:300]} | |
| except Exception as e: | |
| return -1, {"err": repr(e)} | |
| def do_get(auth, base, path, headers=None) -> tuple: | |
| """GET 读请求,返回 (status, dict)。""" | |
| return _request(auth, "GET", base, path, None, headers) | |
| def do_post(auth, base, path, body, headers=None) -> tuple: | |
| """POST 写请求,返回 (status, dict)。body 为 dict。""" | |
| return _request(auth, "POST", base, path, body, headers) | |
| def list_tasks(auth) -> list: | |
| """GET /v2/activity/growth/tasks 全量任务列表(元素为原始 dict)。""" | |
| st, d = do_get(auth, chat_base(auth), PATH_LIST_TASKS) | |
| if st != 200: | |
| raise RuntimeError(f"list_tasks http={st}") | |
| tasks = (d.get("data", {}) or {}).get("tasks") or [] | |
| return tasks | |
| def task_status(auth, task_code) -> dict | None: | |
| """查单个任务当前状态;找不到返回 None。""" | |
| for t in list_tasks(auth): | |
| if t.get("task_code") == task_code: | |
| return t | |
| return None | |
| def accept_tasks(auth, task_codes) -> tuple: | |
| """POST accept 任务(not_accepted → accepted)。返回 (status, resp)。""" | |
| return do_post(auth, chat_base(auth), PATH_ACCEPT_TASKS, | |
| {"task_codes": task_codes}) | |
| def claim_reward(auth, task_code) -> tuple: | |
| """POST claim 领取奖励(任务已 complete 后可领)。重复领返回业务错误,安全。""" | |
| return do_post(auth, chat_base(auth), PATH_CLAIM_REWARD, | |
| {"task_code": task_code}) | |
| def get_streak(auth) -> int: | |
| """GET /activity/growth/streak 连登天数(只读 oracle)。失败返回 -1 记日志。""" | |
| st, d = do_get(auth, chat_base(auth), PATH_STREAK) | |
| if st != 200: | |
| return -1 | |
| return (d.get("data", {}).get("streak", {}) or {}).get("days", 0) | |
| def chat_event(auth, conversation_id=None, model_id="deepseek-v4-flash", | |
| model_name="DeepSeek V4 Flash", mode="craft"): | |
| """客户端 chat_request_send 事件完整形状(照抄 report.go / probe_active.py)。 | |
| 必须带 userId(=账号 uid),缺失则服务端 200 但静默丢弃。 | |
| model_id/name 可换(如 GLM-5.2),供 model_chat 对齐实际模型。 | |
| """ | |
| now = int(time.time() * 1000) | |
| cid = conversation_id or f"task-{now}" | |
| return {"eventCode": "chat_request_send", "timestamp": now, "reportDelay": 0, | |
| "mode": mode, "conversationId": cid, "requestId": cid, | |
| "inputLength": 12, "requestModelId": model_id, | |
| "requestModelName": model_name, "isPlan": False, | |
| "isAutoExecuteTerminal": False, "isAutoModify": False, | |
| "codebaseEnable": False, "maxToken": 0, "maxSteps": 0, "temperature": 0, | |
| "maxRetries": 0, "mentionContexts": [], "knowledgeId": [], | |
| "knowledgeName": [], "codebaseId": "", "mentionContextCount": 0, | |
| "command": "", "expertId": "", "recommendId": "", "skillId": "", | |
| "skillCount": 0, "totalCount": 0, "fileUri": "", "presentAt": now, | |
| "traceId": "", "rootRequestId": cid, "parentConversationId": cid, | |
| "agentName": "default", "agentType": "conversation", "userId": auth["uid"]} | |
| def report_activity(auth, count=1, gap=1.05, model_id="deepseek-v4-flash", | |
| model_name="DeepSeek V4 Flash", mode="craft") -> list: | |
| """向 {billing}/v2/report 上报 count 条 chat_request_send。 | |
| 每次间隔 >= gap 秒(默认 1.05,匹配 probe_active.py 的同接口限速口径)。 | |
| 返回 [(status, code), ...] 汇总。 | |
| """ | |
| out = [] | |
| for i in range(count): | |
| ev = chat_event(auth, model_id=model_id, model_name=model_name, mode=mode) | |
| st, r = do_post(auth, billing_base(auth), PATH_REPORT, [ev]) | |
| out.append((st, r.get("code") if isinstance(r, dict) else None)) | |
| if i < count - 1: | |
| time.sleep(gap) | |
| return out | |
| def chat_completion(auth, model_id="glm-5.2", prompt="hi", max_tokens=32, | |
| timeout=60) -> tuple: | |
| """POST {chat}/v2/chat/completions 真实对话一次(stream:true)。 | |
| 服务端强制流式(payload.go 同款口径),这里逐行读 SSE 直到 done。 | |
| 返回 (status, first_content)。用于 Model_chat_GLM5.2 的“真实对话一次”。 | |
| """ | |
| body = {"model": model_id, "messages": [{"role": "user", "content": prompt}], | |
| "stream": True, "max_tokens": max_tokens} | |
| hdr = {"Accept": "text/event-stream"} # SSE | |
| url = chat_base(auth) + PATH_CHAT | |
| req_headers = _headers(auth) | |
| req_headers.update(hdr) | |
| req = urllib.request.Request(url, data=json.dumps(body).encode(), | |
| headers=req_headers, method="POST") | |
| first = "" | |
| try: | |
| with urllib.request.urlopen(req, timeout=timeout) as r: | |
| status = r.status | |
| for raw in r: | |
| line = raw.decode("utf-8", "replace") | |
| if line.startswith("data: "): | |
| payload = line[6:].strip() | |
| if payload in ("[DONE]", ""): | |
| continue | |
| try: | |
| obj = json.loads(payload) | |
| delta = (obj.get("choices") or [{}])[0].get("delta") or {} | |
| content = delta.get("content") or "" | |
| if content and not first: | |
| first = content | |
| except Exception: | |
| pass | |
| return status, first | |
| except urllib.error.HTTPError as e: | |
| t = e.read().decode("utf-8", "replace") | |
| return e.code, t[:200] | |
| except Exception as e: | |
| return -1, repr(e)[:200] |