@acedatacloud/skills 2026.822.0 → 2026.822.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/skills/_shared/tencentcloud.md +1 -1
- package/skills/_shared/wecom.md +1 -1
- package/skills/acedatacloud/SKILL.md +1 -1
- package/skills/bilibili/SKILL.md +1 -1
- package/skills/bilibili/scripts/bilibili.py +3 -3
- package/skills/cnblogs/scripts/cnblogs.py +1 -1
- package/skills/csdn/SKILL.md +2 -2
- package/skills/csdn/scripts/csdn.py +4 -4
- package/skills/cto51/SKILL.md +1 -1
- package/skills/cto51/scripts/cto51.py +7 -7
- package/skills/didi-ride/SKILL.md +1 -1
- package/skills/didi-ride/scripts/didi.py +2 -2
- package/skills/discord/SKILL.md +2 -2
- package/skills/discord/scripts/discord_user.py +3 -3
- package/skills/discordbot/SKILL.md +1 -1
- package/skills/discordbot/scripts/discordbot.py +1 -1
- package/skills/github/SKILL.md +3 -3
- package/skills/habr/SKILL.md +1 -1
- package/skills/hashnode/SKILL.md +1 -1
- package/skills/hashnode/scripts/hashnode.py +3 -3
- package/skills/juejin/SKILL.md +1 -1
- package/skills/juejin/scripts/juejin.py +2 -2
- package/skills/medium/SKILL.md +1 -1
- package/skills/medium/scripts/medium.py +4 -4
- package/skills/oschina/SKILL.md +1 -1
- package/skills/oschina/scripts/oschina.py +3 -3
- package/skills/reddit/SKILL.md +1 -1
- package/skills/reddit/scripts/reddit.py +3 -3
- package/skills/segmentfault/SKILL.md +1 -1
- package/skills/segmentfault/scripts/segmentfault.py +5 -5
- package/skills/slack/SKILL.md +1 -1
- package/skills/substack/SKILL.md +1 -1
- package/skills/substack/scripts/substack.py +2 -2
- package/skills/telegram/SKILL.md +1 -1
- package/skills/tencent-docs/SKILL.md +1 -1
- package/skills/toutiao/SKILL.md +2 -2
- package/skills/toutiao/scripts/toutiao.py +4 -4
- package/skills/weibo/SKILL.md +1 -1
- package/skills/weibo/scripts/weibo.py +4 -4
- package/skills/x/SKILL.md +1 -1
- package/skills/x/scripts/x.py +4 -4
- package/skills/yuque/SKILL.md +1 -1
- package/skills/yuque/scripts/yuque.py +1 -1
- package/skills/yuque/tests/test_yuque.py +1 -1
- package/skills/zhihu/SKILL.md +2 -2
- package/skills/zhihu/scripts/blog.py +2 -2
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Compatible with **30+ AI coding agents** via the [agentskills.io](https://agents
|
|
|
59
59
|
|
|
60
60
|
### Connectors
|
|
61
61
|
|
|
62
|
-
These skills drive third-party connectors users wire up at [
|
|
62
|
+
These skills drive third-party connectors users wire up at [studio.acedata.cloud/console/connectors](https://studio.acedata.cloud/console/connectors). Each declares the OAuth / BYOC connection it needs in `connections:` frontmatter; the [aichat2](https://chat.acedata.cloud) runtime injects the matching access token into the sandbox before the skill's `Bash` calls run.
|
|
63
63
|
|
|
64
64
|
| Skill | Description | Connection |
|
|
65
65
|
|-------|-------------|------------|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.822.
|
|
3
|
+
"version": "2026.822.1",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -36,7 +36,7 @@ set -a; source .env; set +a
|
|
|
36
36
|
|
|
37
37
|
> ⚠️ **Important:** Add `.env` to your `.gitignore` — never commit credentials to git.
|
|
38
38
|
|
|
39
|
-
**Agent usage** (Claude / Studio / etc.): If you've installed the [腾讯云 connector](https://
|
|
39
|
+
**Agent usage** (Claude / Studio / etc.): If you've installed the [腾讯云 connector](https://studio.acedata.cloud/console/connectors) on AceDataCloud, your encrypted credentials are auto-injected as the env vars above when the agent runs any `tencentcloud-*` skill — no manual `.env` setup needed.
|
|
40
40
|
|
|
41
41
|
## Common regions
|
|
42
42
|
|
package/skills/_shared/wecom.md
CHANGED
|
@@ -62,7 +62,7 @@ set -a; source .env; set +a
|
|
|
62
62
|
> ⚠️ **Never commit `.env`** — the Secret is equivalent to full app access. Add it to `.gitignore`.
|
|
63
63
|
|
|
64
64
|
**Agent usage** (Claude / Studio / etc.): install the
|
|
65
|
-
[企业微信 connector](https://
|
|
65
|
+
[企业微信 connector](https://studio.acedata.cloud/console/connectors) on AceDataCloud once. Your
|
|
66
66
|
CorpID / Secret / AgentId are AES-256-GCM encrypted at rest and injected as the env vars above
|
|
67
67
|
only inside the sandbox while the skill runs — no manual `.env` setup needed. Revoke anytime from
|
|
68
68
|
the connections page.
|
|
@@ -39,7 +39,7 @@ user token), **not** the per-service API token used for `api.acedata.cloud`.
|
|
|
39
39
|
(or `POST /api/v1/platform-tokens/`). It starts with `platform-` and never expires.
|
|
40
40
|
2. Provide it one of two ways:
|
|
41
41
|
- **Connector (recommended in studio / chat):** install the **AceDataCloud**
|
|
42
|
-
connector at [
|
|
42
|
+
connector at [studio.acedata.cloud/console/connectors](https://studio.acedata.cloud/console/connectors)
|
|
43
43
|
and paste the token once — the runtime injects `ACEDATACLOUD_PLATFORM_TOKEN`
|
|
44
44
|
into the sandbox automatically (this skill declares `connections: [acedatacloud]`).
|
|
45
45
|
- **Local `.env`:**
|
package/skills/bilibili/SKILL.md
CHANGED
|
@@ -58,7 +58,7 @@ python3 "$BILI" whoami
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
On a not-logged-in / auth error the cookie is expired — have the user reconnect
|
|
61
|
-
at <https://
|
|
61
|
+
at <https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
62
62
|
|
|
63
63
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
64
64
|
|
|
@@ -74,7 +74,7 @@ def load_cookies() -> list:
|
|
|
74
74
|
raw = os.environ.get(env)
|
|
75
75
|
if not raw:
|
|
76
76
|
die(f"{env} is not set — connect Bilibili at "
|
|
77
|
-
f"https://
|
|
77
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
78
78
|
try:
|
|
79
79
|
jar = json.loads(raw)
|
|
80
80
|
except json.JSONDecodeError as e:
|
|
@@ -202,7 +202,7 @@ def bili_nav(jar):
|
|
|
202
202
|
data = nav.get("data") or {}
|
|
203
203
|
if not data.get("isLogin"):
|
|
204
204
|
die("not logged in (cookie expired?) — reconnect at "
|
|
205
|
-
"https://
|
|
205
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
206
206
|
return data
|
|
207
207
|
|
|
208
208
|
|
|
@@ -780,7 +780,7 @@ def cmd_drafts(jar, args):
|
|
|
780
780
|
jar, referer="https://member.bilibili.com/")
|
|
781
781
|
if d.get("code"): # 0 = ok; anything truthy is an auth/API error
|
|
782
782
|
die(f"draft list error (code={d.get('code')}): {d.get('message')} — "
|
|
783
|
-
f"cookie may be expired; reconnect at https://
|
|
783
|
+
f"cookie may be expired; reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
784
784
|
items = _drafts_of(d)
|
|
785
785
|
out({"page": args.page, "count": len(items), "drafts": [{
|
|
786
786
|
"aid": x.get("id"),
|
package/skills/csdn/SKILL.md
CHANGED
|
@@ -57,7 +57,7 @@ python3 "$CSDN" whoami
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
On a WAF 403 / auth error the cookie is expired — tell the user to reconnect at
|
|
60
|
-
<https://
|
|
60
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** retry in a loop.
|
|
61
61
|
|
|
62
62
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
63
63
|
|
|
@@ -89,7 +89,7 @@ image that fails to upload keeps its original URL (never blocks the post).
|
|
|
89
89
|
## Gotchas — surface before the user is surprised
|
|
90
90
|
|
|
91
91
|
- **This is the user's real CSDN account.** Confirm before any publish.
|
|
92
|
-
- **Cookie expiry / WAF 403**: reconnect at
|
|
92
|
+
- **Cookie expiry / WAF 403**: reconnect at studio.acedata.cloud/console/connectors —
|
|
93
93
|
never loop-retry a WAF block.
|
|
94
94
|
- The editor save endpoint is signed with an HMAC key baked into CSDN's web
|
|
95
95
|
bundle; if CSDN rotates it, publish fails loudly (reads still work).
|
|
@@ -72,7 +72,7 @@ def load_cookies() -> list:
|
|
|
72
72
|
raw = os.environ.get(env)
|
|
73
73
|
if not raw:
|
|
74
74
|
die(f"{env} is not set — connect CSDN at "
|
|
75
|
-
f"https://
|
|
75
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
76
76
|
try:
|
|
77
77
|
jar = json.loads(raw)
|
|
78
78
|
except json.JSONDecodeError as e:
|
|
@@ -171,10 +171,10 @@ def get_json(url: str, jar: list, *, referer, origin):
|
|
|
171
171
|
if "WAF" in text and "403" in text:
|
|
172
172
|
die("CSDN WAF blocked the request (403). The cookie may be expired or "
|
|
173
173
|
"the account needs to re-pass CSDN's captcha; reconnect at "
|
|
174
|
-
"https://
|
|
174
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
175
175
|
if status in (401, 403):
|
|
176
176
|
die(f"auth failed ({status}) on {url} — cookie likely expired. "
|
|
177
|
-
f"Reconnect at https://
|
|
177
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
178
178
|
try:
|
|
179
179
|
return json.loads(text)
|
|
180
180
|
except json.JSONDecodeError:
|
|
@@ -209,7 +209,7 @@ def csdn_username(jar):
|
|
|
209
209
|
u = cookie_value(jar, "UserName")
|
|
210
210
|
if not u:
|
|
211
211
|
die("no UserName cookie — reconnect CSDN at "
|
|
212
|
-
"https://
|
|
212
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
213
213
|
return u
|
|
214
214
|
|
|
215
215
|
|
package/skills/cto51/SKILL.md
CHANGED
|
@@ -43,7 +43,7 @@ python3 "$SKILL_DIR/scripts/cto51.py" whoami
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
If this fails with a redirect or auth error, the cookie has expired. Ask the
|
|
46
|
-
user to reconnect at `https://
|
|
46
|
+
user to reconnect at `https://studio.acedata.cloud/console/connectors` rather than
|
|
47
47
|
retrying.
|
|
48
48
|
|
|
49
49
|
## Create a draft — GATED
|
|
@@ -110,7 +110,7 @@ def load_cookies() -> list:
|
|
|
110
110
|
raw = os.environ.get(env)
|
|
111
111
|
if not raw:
|
|
112
112
|
die(f"{env} is not set — connect 51CTO at "
|
|
113
|
-
f"https://
|
|
113
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
114
114
|
try:
|
|
115
115
|
jar = json.loads(raw)
|
|
116
116
|
except json.JSONDecodeError as e:
|
|
@@ -149,13 +149,13 @@ def cookie_header(jar: list, url: str) -> str:
|
|
|
149
149
|
pair = f"{name}={value}"
|
|
150
150
|
if any(ch in pair for ch in "\r\n\x00"):
|
|
151
151
|
die("a cookie in the jar contains a line break and cannot be sent — "
|
|
152
|
-
"reconnect at https://
|
|
152
|
+
"reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
153
153
|
try:
|
|
154
154
|
pair.encode("latin-1")
|
|
155
155
|
except UnicodeEncodeError:
|
|
156
156
|
die("a cookie in the jar contains characters that cannot be sent in "
|
|
157
157
|
"an HTTP header — reconnect at "
|
|
158
|
-
"https://
|
|
158
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
159
159
|
parts.append(pair)
|
|
160
160
|
return "; ".join(parts)
|
|
161
161
|
|
|
@@ -197,7 +197,7 @@ def request(method: str, url: str, jar: list, *, headers=None, form=None,
|
|
|
197
197
|
if e.code in (301, 302, 303, 307, 308):
|
|
198
198
|
die("51CTO redirected the request — not followed, so no credential "
|
|
199
199
|
"left 51cto.com. You are most likely logged out; reconnect at "
|
|
200
|
-
"https://
|
|
200
|
+
"https://studio.acedata.cloud/console/connectors."
|
|
201
201
|
+ (" This was a WRITE: its outcome is UNKNOWN — check your "
|
|
202
202
|
"drafts before retrying." if write else ""))
|
|
203
203
|
# Draining the error body can itself raise (IncompleteRead / reset).
|
|
@@ -228,7 +228,7 @@ def publish_page(jar: list) -> tuple[str, dict]:
|
|
|
228
228
|
status, html = request("GET", PUBLISH_PAGE, jar)
|
|
229
229
|
if status in (401, 403):
|
|
230
230
|
die("auth failed — cookie expired or invalid. Reconnect at "
|
|
231
|
-
"https://
|
|
231
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
232
232
|
if status != 200:
|
|
233
233
|
die(f"unexpected status {status} loading the 51CTO publish page")
|
|
234
234
|
m = _USER_RE.search(html)
|
|
@@ -238,10 +238,10 @@ def publish_page(jar: list) -> tuple[str, dict]:
|
|
|
238
238
|
# changed" instead of always blaming the cookie.
|
|
239
239
|
if "/user/login" in html or "home.51cto.com/login" in html:
|
|
240
240
|
die("not logged in to 51CTO — reconnect at "
|
|
241
|
-
"https://
|
|
241
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
242
242
|
die("could not confirm the 51CTO session from the publish page — either "
|
|
243
243
|
"the cookie expired or 51CTO changed its markup. Try reconnecting "
|
|
244
|
-
"at https://
|
|
244
|
+
"at https://studio.acedata.cloud/console/connectors; if that does not "
|
|
245
245
|
"help, this skill needs updating.")
|
|
246
246
|
link, avatar = m.group(1), m.group(2)
|
|
247
247
|
csrf_m = _CSRF_RE.search(html)
|
|
@@ -27,7 +27,7 @@ The `didi` BYOC connector injects one env var into the sandbox:
|
|
|
27
27
|
- `DIDI_MCP_KEY` — the user's DiDi MCP key. **Secret — never echo, print, or log it.**
|
|
28
28
|
|
|
29
29
|
If `DIDI_MCP_KEY` is missing, tell the user to connect the DiDi connector at
|
|
30
|
-
[
|
|
30
|
+
[studio.acedata.cloud/console/connectors](https://studio.acedata.cloud/console/connectors)
|
|
31
31
|
(they get the key by scanning the QR in the 滴滴出行 App or via
|
|
32
32
|
<https://mcp.didichuxing.com/claw>).
|
|
33
33
|
|
|
@@ -73,7 +73,7 @@ class MCPClient:
|
|
|
73
73
|
"error": f"HTTP {exc.code} from DiDi MCP",
|
|
74
74
|
"detail": detail,
|
|
75
75
|
"hint": "If 401/403, the DiDi connector key is missing or expired — "
|
|
76
|
-
"reconnect at https://
|
|
76
|
+
"reconnect at https://studio.acedata.cloud/console/connectors",
|
|
77
77
|
}
|
|
78
78
|
)
|
|
79
79
|
except urllib.error.URLError as exc:
|
|
@@ -208,7 +208,7 @@ def main() -> None:
|
|
|
208
208
|
{
|
|
209
209
|
"error": "DIDI_MCP_KEY not set",
|
|
210
210
|
"hint": "Connect the DiDi connector at "
|
|
211
|
-
"https://
|
|
211
|
+
"https://studio.acedata.cloud/console/connectors to inject the key.",
|
|
212
212
|
}
|
|
213
213
|
)
|
|
214
214
|
|
package/skills/discord/SKILL.md
CHANGED
|
@@ -31,12 +31,12 @@ connector injected. Check the environment first and follow the matching section:
|
|
|
31
31
|
```sh
|
|
32
32
|
if [ -n "$DISCORD_USER_TOKEN" ]; then echo "mode: user-token (full)"; \
|
|
33
33
|
elif [ -n "$DISCORD_TOKEN" ]; then echo "mode: oauth (read-only)"; \
|
|
34
|
-
else echo "no Discord connection — connect at https://
|
|
34
|
+
else echo "no Discord connection — connect at https://studio.acedata.cloud/console/connectors"; fi
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Both tokens are **secret — full account access. Never echo or print them.** On
|
|
38
38
|
`401`, the token expired or was revoked — tell the user to reconnect at
|
|
39
|
-
`
|
|
39
|
+
`studio.acedata.cloud/console/connectors`. On `429`, sleep the `retry_after`
|
|
40
40
|
seconds, then retry; never parallelize.
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -54,7 +54,7 @@ def load_token() -> str:
|
|
|
54
54
|
tok = os.environ.get("DISCORD_USER_TOKEN")
|
|
55
55
|
if not tok:
|
|
56
56
|
die("DISCORD_USER_TOKEN is not set — connect Discord (User Token) at "
|
|
57
|
-
"https://
|
|
57
|
+
"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
58
58
|
return tok.strip()
|
|
59
59
|
|
|
60
60
|
|
|
@@ -208,7 +208,7 @@ def main() -> None:
|
|
|
208
208
|
asyncio.run(run(args))
|
|
209
209
|
except LoginFailure as e:
|
|
210
210
|
die("auth failed — the Discord user token is wrong or expired. Reconnect "
|
|
211
|
-
f"at https://
|
|
211
|
+
f"at https://studio.acedata.cloud/console/connectors. ({e})")
|
|
212
212
|
except Forbidden as e:
|
|
213
213
|
die(f"forbidden by Discord (no access to that channel/server, or blocked): {e}")
|
|
214
214
|
except NotFound as e:
|
|
@@ -222,7 +222,7 @@ def main() -> None:
|
|
|
222
222
|
die(f"Discord API error (HTTP {status}): {e}")
|
|
223
223
|
except Exception as e:
|
|
224
224
|
die(f"Discord request failed ({type(e).__name__}: {e}). Likely an expired "
|
|
225
|
-
"token — reconnect at https://
|
|
225
|
+
"token — reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
if __name__ == "__main__":
|
|
@@ -30,7 +30,7 @@ than retrying.
|
|
|
30
30
|
|
|
31
31
|
Errors are JSON `{"code": <n>, "message": "<reason>"}`. A `401` means the
|
|
32
32
|
bot token is wrong/reset — ask the user to re-paste it at
|
|
33
|
-
`
|
|
33
|
+
`studio.acedata.cloud/console/connectors`. A `429` carries `retry_after`
|
|
34
34
|
(seconds) — sleep that long, then retry; never parallelize.
|
|
35
35
|
|
|
36
36
|
## Sending safety
|
|
@@ -62,7 +62,7 @@ def api_request(method: str, path: str, payload: dict[str, Any] | None = None) -
|
|
|
62
62
|
if not token:
|
|
63
63
|
raise DiscordBotError(
|
|
64
64
|
"DISCORDBOT_TOKEN is not set; connect Discord Bot at "
|
|
65
|
-
"https://
|
|
65
|
+
"https://studio.acedata.cloud/console/connectors"
|
|
66
66
|
)
|
|
67
67
|
body = json.dumps(payload).encode() if payload is not None else None
|
|
68
68
|
request = urllib.request.Request(
|
package/skills/github/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ diagnosing a `403`:
|
|
|
25
25
|
```sh
|
|
26
26
|
if [ -n "$GITHUB_TOKEN" ]; then echo "mode: pat (user-created token)"; \
|
|
27
27
|
elif [ -n "$GH_TOKEN" ]; then echo "mode: oauth"; \
|
|
28
|
-
else echo "no GitHub connection — connect at https://
|
|
28
|
+
else echo "no GitHub connection — connect at https://studio.acedata.cloud/console/connectors"; fi
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Both are **secret — full account access within their scope. Never echo or
|
|
@@ -58,7 +58,7 @@ how you phrase the call:
|
|
|
58
58
|
Users pick scopes at install time and every box is optional, so even the
|
|
59
59
|
five above may be partially granted. A `404` on something you know exists,
|
|
60
60
|
or a `403`, usually means a missing scope — not a wrong URL. Say so plainly
|
|
61
|
-
and point the user at `
|
|
61
|
+
and point the user at `studio.acedata.cloud/console/connectors` to reconnect
|
|
62
62
|
with the box ticked (OAuth) or to paste a token with wider permissions (PAT).
|
|
63
63
|
|
|
64
64
|
## Two ways to call gh — prefer subcommands
|
|
@@ -359,7 +359,7 @@ so those queries return `INSUFFICIENT_SCOPES`. Don't build recipes on it.
|
|
|
359
359
|
## Notes
|
|
360
360
|
|
|
361
361
|
- For private repos the user MUST have granted `repo` scope when they
|
|
362
|
-
authorized the connection at `
|
|
362
|
+
authorized the connection at `studio.acedata.cloud/console/connectors`.
|
|
363
363
|
A 404 on a repo you know exists usually means missing scope, not a
|
|
364
364
|
wrong URL.
|
|
365
365
|
- When `--json` rejects a field name, gh prints the full list of valid
|
package/skills/habr/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ python3 "$H" get --id ARTICLE_ID > draft.json
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
An auth error means the Cookie expired. Ask the user to reconnect Habr at
|
|
35
|
-
<https://
|
|
35
|
+
<https://studio.acedata.cloud/console/connectors>; never retry in a loop.
|
|
36
36
|
|
|
37
37
|
## Save or preview a draft
|
|
38
38
|
|
package/skills/hashnode/SKILL.md
CHANGED
|
@@ -40,7 +40,7 @@ python3 "$H" publications # list the user's blogs (verifies the cookie)
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
On an auth error the cookie is expired — have the user reconnect at
|
|
43
|
-
<https://
|
|
43
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
44
44
|
|
|
45
45
|
## ⚠️ Non-Pro blogs are automoderated — write EDITORIAL, not ads
|
|
46
46
|
|
|
@@ -74,7 +74,7 @@ def load_cookies() -> list:
|
|
|
74
74
|
raw = os.environ.get("HASHNODE_COOKIES")
|
|
75
75
|
if not raw:
|
|
76
76
|
die("HASHNODE_COOKIES is not set — connect Hashnode at "
|
|
77
|
-
"https://
|
|
77
|
+
"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
78
78
|
try:
|
|
79
79
|
jar = json.loads(raw)
|
|
80
80
|
except json.JSONDecodeError as e:
|
|
@@ -84,7 +84,7 @@ def load_cookies() -> list:
|
|
|
84
84
|
if not any(c.get("name") == "hashnode-session" for c in jar):
|
|
85
85
|
die("HASHNODE_COOKIES is missing the 'hashnode-session' cookie — re-capture "
|
|
86
86
|
"on hashnode.com with the ACE extension, then reconnect at "
|
|
87
|
-
"https://
|
|
87
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
88
88
|
return jar
|
|
89
89
|
|
|
90
90
|
|
|
@@ -163,7 +163,7 @@ def api(method, path, jar, *, body=None, _retried=False):
|
|
|
163
163
|
return api(method, path, jar, body=body, _retried=True)
|
|
164
164
|
if status in (401, 403):
|
|
165
165
|
die(f"auth failed ({status}) on {path} — cookie likely expired. "
|
|
166
|
-
f"Reconnect at https://
|
|
166
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
167
167
|
# A hashnode.com/api path that 404s with an HTML body means the route/id is
|
|
168
168
|
# wrong; a JSON {success:false} is a real API error.
|
|
169
169
|
stripped = text.lstrip()
|
package/skills/juejin/SKILL.md
CHANGED
|
@@ -51,7 +51,7 @@ python3 "$JJ" whoami
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
On an auth error (`err_no` 401 / "请登录") the cookie is expired — have the user
|
|
54
|
-
reconnect at <https://
|
|
54
|
+
reconnect at <https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
55
55
|
|
|
56
56
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
57
57
|
|
|
@@ -58,7 +58,7 @@ def load_cookies() -> list:
|
|
|
58
58
|
raw = os.environ.get(env)
|
|
59
59
|
if not raw:
|
|
60
60
|
die(f"{env} is not set — connect 掘金 at "
|
|
61
|
-
f"https://
|
|
61
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
62
62
|
try:
|
|
63
63
|
jar = json.loads(raw)
|
|
64
64
|
except json.JSONDecodeError as e:
|
|
@@ -145,7 +145,7 @@ def api_env(method: str, path: str, jar: list, *, body=None) -> dict:
|
|
|
145
145
|
msg = env.get("err_msg", "")
|
|
146
146
|
if err in (401, 403) or "登录" in str(msg):
|
|
147
147
|
die(f"auth failed (err_no={err}: {msg}) — cookie likely expired. "
|
|
148
|
-
f"Reconnect at https://
|
|
148
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
149
149
|
die(f"juejin API error on {path} (err_no={err}): {msg}")
|
|
150
150
|
return env
|
|
151
151
|
|
package/skills/medium/SKILL.md
CHANGED
|
@@ -52,7 +52,7 @@ python3 "$MED" whoami
|
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
On an auth error the cookie is expired — have the user reconnect at
|
|
55
|
-
<https://
|
|
55
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
56
56
|
|
|
57
57
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ def load_cookies() -> list:
|
|
|
68
68
|
raw = os.environ.get(env)
|
|
69
69
|
if not raw:
|
|
70
70
|
die(f"{env} is not set — connect Medium at "
|
|
71
|
-
f"https://
|
|
71
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
72
72
|
try:
|
|
73
73
|
jar = json.loads(raw)
|
|
74
74
|
except json.JSONDecodeError as e:
|
|
@@ -163,7 +163,7 @@ def api(method, url, jar, *, body=None, _retried=False):
|
|
|
163
163
|
return api(method, url, jar, body=body, _retried=True)
|
|
164
164
|
if status in (401, 403):
|
|
165
165
|
die(f"auth failed ({status}) on {url} — cookie likely expired. "
|
|
166
|
-
f"Reconnect at https://
|
|
166
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
167
167
|
clean = _PREFIX.sub("", text)
|
|
168
168
|
try:
|
|
169
169
|
d = json.loads(clean)
|
|
@@ -206,7 +206,7 @@ def md_me(jar):
|
|
|
206
206
|
uid = cookie_value(jar, "uid")
|
|
207
207
|
if not uid:
|
|
208
208
|
die("no uid cookie — reconnect Medium at "
|
|
209
|
-
"https://
|
|
209
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
210
210
|
d = api("GET", f"{BASE}/_/api/users/{uid}", jar)
|
|
211
211
|
val = (d.get("payload") or {}).get("value") or {}
|
|
212
212
|
if not val.get("userId"):
|
|
@@ -606,7 +606,7 @@ def cmd_publish(jar, args):
|
|
|
606
606
|
prime_xsrf(jar)
|
|
607
607
|
if not cookie_value(jar, "xsrf"):
|
|
608
608
|
die("could not obtain an xsrf token from Medium (cookie may be expired) — "
|
|
609
|
-
"reconnect at https://
|
|
609
|
+
"reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
610
610
|
|
|
611
611
|
# 1. create empty draft
|
|
612
612
|
d = api("POST", f"{BASE}/new-story", jar, body={})
|
package/skills/oschina/SKILL.md
CHANGED
|
@@ -47,7 +47,7 @@ python3 "$SKILL_DIR/scripts/oschina.py" whoami
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
If this fails with an auth error, the cookie has expired. Ask the user to
|
|
50
|
-
reconnect at `https://
|
|
50
|
+
reconnect at `https://studio.acedata.cloud/console/connectors` rather than retrying.
|
|
51
51
|
|
|
52
52
|
## Create a draft — GATED
|
|
53
53
|
|
|
@@ -77,7 +77,7 @@ def load_cookies() -> list:
|
|
|
77
77
|
raw = os.environ.get(env)
|
|
78
78
|
if not raw:
|
|
79
79
|
die(f"{env} is not set — connect 开源中国 at "
|
|
80
|
-
f"https://
|
|
80
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
81
81
|
try:
|
|
82
82
|
jar = json.loads(raw)
|
|
83
83
|
except json.JSONDecodeError as e:
|
|
@@ -141,7 +141,7 @@ def request(method: str, url: str, jar: list, *, headers=None, body=None, write:
|
|
|
141
141
|
if e.code in (301, 302, 303, 307, 308):
|
|
142
142
|
die(f"开源中国 redirected {method} {url} — not followed, so no "
|
|
143
143
|
f"credential left oschina.net. You are most likely logged out; "
|
|
144
|
-
f"reconnect at https://
|
|
144
|
+
f"reconnect at https://studio.acedata.cloud/console/connectors."
|
|
145
145
|
+ (" This was a WRITE: its outcome is UNKNOWN — check your "
|
|
146
146
|
"drafts before retrying." if write else ""))
|
|
147
147
|
raw = e.read()
|
|
@@ -179,7 +179,7 @@ def api_call(method: str, path: str, jar: list, *, body=None, write: bool = Fals
|
|
|
179
179
|
code = env.get("code")
|
|
180
180
|
if code in (40001, 401, 403) or "未登录" in msg or "登录" in msg:
|
|
181
181
|
die(f"auth failed (code={code}: {msg}) — cookie likely expired. "
|
|
182
|
-
f"Reconnect at https://
|
|
182
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
183
183
|
die(f"开源中国 API error on {path} (code={code}): {msg}")
|
|
184
184
|
return env.get("result")
|
|
185
185
|
|
package/skills/reddit/SKILL.md
CHANGED
|
@@ -42,7 +42,7 @@ python3 "$R" whoami
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
If authentication fails, ask the user to reconnect at
|
|
45
|
-
<https://
|
|
45
|
+
<https://studio.acedata.cloud/console/connectors>. Do not loop-retry a blocked or
|
|
46
46
|
expired session.
|
|
47
47
|
|
|
48
48
|
## Read
|
|
@@ -100,7 +100,7 @@ def parse_cookie_jar(raw: str) -> list[dict]:
|
|
|
100
100
|
die(
|
|
101
101
|
"REDDIT_COOKIES is missing a valid reddit_session — log in on reddit.com, "
|
|
102
102
|
"re-capture with the ACE extension, then reconnect at "
|
|
103
|
-
"https://
|
|
103
|
+
"https://studio.acedata.cloud/console/connectors."
|
|
104
104
|
)
|
|
105
105
|
return normalized
|
|
106
106
|
|
|
@@ -308,7 +308,7 @@ class RedditClient:
|
|
|
308
308
|
return cls("cookie", cookies=parse_cookie_jar(raw_cookies))
|
|
309
309
|
die(
|
|
310
310
|
"No Reddit credential is available — connect Reddit with Cookie or OAuth at "
|
|
311
|
-
"https://
|
|
311
|
+
"https://studio.acedata.cloud/console/connectors."
|
|
312
312
|
)
|
|
313
313
|
|
|
314
314
|
@property
|
|
@@ -375,7 +375,7 @@ class RedditClient:
|
|
|
375
375
|
if status in {401, 403}:
|
|
376
376
|
die(
|
|
377
377
|
f"Reddit authentication failed ({status}) — the credential may be expired or blocked. "
|
|
378
|
-
"Reconnect at https://
|
|
378
|
+
"Reconnect at https://studio.acedata.cloud/console/connectors."
|
|
379
379
|
)
|
|
380
380
|
if status == 429:
|
|
381
381
|
die("Reddit rate limit reached (429). Wait before retrying; do not loop-retry.")
|
|
@@ -45,7 +45,7 @@ python3 "$SKILL_DIR/scripts/segmentfault.py" whoami
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
If this fails with an auth error, the cookie has expired. Ask the user to
|
|
48
|
-
reconnect at `https://
|
|
48
|
+
reconnect at `https://studio.acedata.cloud/console/connectors` rather than retrying.
|
|
49
49
|
|
|
50
50
|
## Create a draft — GATED
|
|
51
51
|
|
|
@@ -79,7 +79,7 @@ def load_cookies() -> list:
|
|
|
79
79
|
raw = os.environ.get(env)
|
|
80
80
|
if not raw:
|
|
81
81
|
die(f"{env} is not set — connect SegmentFault at "
|
|
82
|
-
f"https://
|
|
82
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
83
83
|
try:
|
|
84
84
|
jar = json.loads(raw)
|
|
85
85
|
except json.JSONDecodeError as e:
|
|
@@ -143,7 +143,7 @@ def request(method: str, url: str, jar: list, *, headers=None, body=None, write:
|
|
|
143
143
|
if e.code in (301, 302, 303, 307, 308):
|
|
144
144
|
die(f"SegmentFault redirected {method} {url} — not followed, so no "
|
|
145
145
|
f"credential left segmentfault.com. You are most likely logged "
|
|
146
|
-
f"out; reconnect at https://
|
|
146
|
+
f"out; reconnect at https://studio.acedata.cloud/console/connectors."
|
|
147
147
|
+ (" This was a WRITE: its outcome is UNKNOWN — check your "
|
|
148
148
|
"drafts before retrying." if write else ""))
|
|
149
149
|
raw = e.read()
|
|
@@ -164,7 +164,7 @@ def request(method: str, url: str, jar: list, *, headers=None, body=None, write:
|
|
|
164
164
|
def _auth_died(status: int, text: str) -> None:
|
|
165
165
|
if status in (401, 403) or text.strip('"') == "Unauthorized":
|
|
166
166
|
die("auth failed — cookie expired or invalid. Reconnect at "
|
|
167
|
-
"https://
|
|
167
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
def session_token(jar: list) -> str:
|
|
@@ -175,7 +175,7 @@ def session_token(jar: list) -> str:
|
|
|
175
175
|
if not m:
|
|
176
176
|
die("could not find the SegmentFault session token on /write — you are "
|
|
177
177
|
"probably logged out. Reconnect at "
|
|
178
|
-
"https://
|
|
178
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
179
179
|
return m.group(1)
|
|
180
180
|
|
|
181
181
|
|
|
@@ -192,7 +192,7 @@ def cmd_whoami(jar, _args):
|
|
|
192
192
|
)
|
|
193
193
|
if not name and "登录" in html:
|
|
194
194
|
die("not logged in — cookie expired. Reconnect at "
|
|
195
|
-
"https://
|
|
195
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
196
196
|
out({
|
|
197
197
|
"platform": PLATFORM,
|
|
198
198
|
"name": name.group(1) if name else None,
|
package/skills/slack/SKILL.md
CHANGED
|
@@ -122,7 +122,7 @@ curl -sS -X POST https://slack.com/api/files.completeUploadExternal \
|
|
|
122
122
|
`conversations.join` first (which also takes the channel id), then
|
|
123
123
|
retry. Private channels and DMs need a manual invite — ask the user.
|
|
124
124
|
- Always check `.ok` on the JSON response. `not_authed` / `invalid_auth`
|
|
125
|
-
→ ask the user to re-authorize at `
|
|
125
|
+
→ ask the user to re-authorize at `studio.acedata.cloud/console/connectors`.
|
|
126
126
|
- Channel ids start with `C` (channels), `D` (DMs), `G` (private). Don't
|
|
127
127
|
invent ids — always look them up via `conversations.list` or
|
|
128
128
|
`users.lookupByEmail`.
|
package/skills/substack/SKILL.md
CHANGED
|
@@ -51,7 +51,7 @@ python3 "$SUB" whoami
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
On an auth error the cookie is expired — have the user reconnect at
|
|
54
|
-
<https://
|
|
54
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
55
55
|
|
|
56
56
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
57
57
|
|
|
@@ -62,7 +62,7 @@ def load_cookies() -> list:
|
|
|
62
62
|
raw = os.environ.get(env)
|
|
63
63
|
if not raw:
|
|
64
64
|
die(f"{env} is not set — connect Substack at "
|
|
65
|
-
f"https://
|
|
65
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
66
66
|
try:
|
|
67
67
|
jar = json.loads(raw)
|
|
68
68
|
except json.JSONDecodeError as e:
|
|
@@ -137,7 +137,7 @@ def api(method, url, jar, *, body=None, _retried=False):
|
|
|
137
137
|
return api(method, url, jar, body=body, _retried=True)
|
|
138
138
|
if status in (401, 403):
|
|
139
139
|
die(f"auth failed ({status}) on {url} — cookie likely expired. "
|
|
140
|
-
f"Reconnect at https://
|
|
140
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
141
141
|
try:
|
|
142
142
|
d = json.loads(text) if text.strip() else {}
|
|
143
143
|
except json.JSONDecodeError:
|
package/skills/telegram/SKILL.md
CHANGED
|
@@ -56,7 +56,7 @@ Common error codes:
|
|
|
56
56
|
|
|
57
57
|
| code | meaning | what to do |
|
|
58
58
|
|---|---|---|
|
|
59
|
-
| `400006` | access token invalid / expired | tell the user to reconnect 腾讯文档 at <https://
|
|
59
|
+
| `400006` | access token invalid / expired | tell the user to reconnect 腾讯文档 at <https://studio.acedata.cloud/console/connectors> — do **not** loop-retry |
|
|
60
60
|
| `400007` | VIP (超级会员) privilege required | the requested capability needs a Tencent Docs 超级会员; tell the user, link <https://docs.qq.com/vip> |
|
|
61
61
|
| `400008` | 积分 (credits) insufficient | AI-generation quota is exhausted; tell the user to top up |
|
|
62
62
|
| `11607` / `-32603` | bad request params | recheck `fileID` / `type` / body fields against the recipe below |
|
package/skills/toutiao/SKILL.md
CHANGED
|
@@ -58,7 +58,7 @@ python3 "$TT" whoami
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
On an auth error the cookie is expired — tell the user to reconnect at
|
|
61
|
-
<https://
|
|
61
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** retry in a loop.
|
|
62
62
|
|
|
63
63
|
## Publishing — GATED (dry-run unless trailing `--confirm`)
|
|
64
64
|
|
|
@@ -104,7 +104,7 @@ unless the body already carries 头条-hosted images).
|
|
|
104
104
|
publish with 头条's own message — relay it, don't retry in a loop.
|
|
105
105
|
- **14-day edit window**: 头条 refuses edits to articles published more than 14
|
|
106
106
|
days ago, so this skill does not offer an edit command.
|
|
107
|
-
- **Cookie expiry**: reconnect at
|
|
107
|
+
- **Cookie expiry**: reconnect at studio.acedata.cloud/console/connectors.
|
|
108
108
|
- **Never print `TOUTIAO_COOKIES`** — it is full account access.
|
|
109
109
|
- **ToS**: cookie automation acts only on the user's own account with their own
|
|
110
110
|
captured cookie; the user owns that risk.
|
|
@@ -68,7 +68,7 @@ def load_cookies() -> list:
|
|
|
68
68
|
raw = os.environ.get(env)
|
|
69
69
|
if not raw:
|
|
70
70
|
die(f"{env} is not set — connect 今日头条 at "
|
|
71
|
-
f"https://
|
|
71
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
72
72
|
try:
|
|
73
73
|
jar = json.loads(raw)
|
|
74
74
|
except json.JSONDecodeError as e:
|
|
@@ -173,7 +173,7 @@ def api_envelope(method: str, path: str, jar: list, *, referer=None, body=None,
|
|
|
173
173
|
nonfatal=nonfatal)
|
|
174
174
|
if status in (401, 403) or "/auth/page/login" in text:
|
|
175
175
|
msg = (f"auth failed ({status}) on {path} — cookie likely expired. "
|
|
176
|
-
f"Reconnect 今日头条 at https://
|
|
176
|
+
f"Reconnect 今日头条 at https://studio.acedata.cloud/console/connectors.")
|
|
177
177
|
if nonfatal:
|
|
178
178
|
raise RuntimeError(msg)
|
|
179
179
|
die(msg)
|
|
@@ -196,7 +196,7 @@ def api_envelope(method: str, path: str, jar: list, *, referer=None, body=None,
|
|
|
196
196
|
msg = env.get("message") or env.get("reason") or ""
|
|
197
197
|
if code in (401, 403) or "登录" in str(msg):
|
|
198
198
|
auth_msg = (f"auth failed (code={code}: {msg}) — cookie likely expired. "
|
|
199
|
-
f"Reconnect at https://
|
|
199
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
200
200
|
if nonfatal:
|
|
201
201
|
raise RuntimeError(auth_msg)
|
|
202
202
|
die(auth_msg)
|
|
@@ -689,7 +689,7 @@ def cmd_publish(jar, args):
|
|
|
689
689
|
# deep in its API with an opaque code instead of "reconnect".
|
|
690
690
|
if not cookie_value(jar, "csrftoken"):
|
|
691
691
|
die("the 今日头条 cookie jar has no `csrftoken` — it is incomplete or "
|
|
692
|
-
"expired. Reconnect at https://
|
|
692
|
+
"expired. Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
693
693
|
|
|
694
694
|
# Render to HTML FIRST, then rewrite <img> tags — 头条 rejects the whole
|
|
695
695
|
# article (7115) if any image isn't hosted on its own CDN with web_uri.
|
package/skills/weibo/SKILL.md
CHANGED
|
@@ -59,7 +59,7 @@ python3 "$WB" whoami
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
On an auth error the cookie is expired — have the user reconnect at
|
|
62
|
-
<https://
|
|
62
|
+
<https://studio.acedata.cloud/console/connectors>. Do **not** loop-retry.
|
|
63
63
|
|
|
64
64
|
## Posting — GATED (dry-run unless trailing `--confirm`)
|
|
65
65
|
|
|
@@ -61,7 +61,7 @@ def load_cookies() -> list:
|
|
|
61
61
|
raw = os.environ.get(env)
|
|
62
62
|
if not raw:
|
|
63
63
|
die(f"{env} is not set — connect 微博 at "
|
|
64
|
-
f"https://
|
|
64
|
+
f"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
65
65
|
try:
|
|
66
66
|
jar = json.loads(raw)
|
|
67
67
|
except json.JSONDecodeError as e:
|
|
@@ -142,7 +142,7 @@ def get_json(url, jar):
|
|
|
142
142
|
status, text = request("GET", url, jar)
|
|
143
143
|
if status in (401, 403):
|
|
144
144
|
die(f"auth failed ({status}) on {url} — cookie likely expired. "
|
|
145
|
-
f"Reconnect at https://
|
|
145
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors.")
|
|
146
146
|
try:
|
|
147
147
|
return json.loads(text)
|
|
148
148
|
except json.JSONDecodeError:
|
|
@@ -153,7 +153,7 @@ def st_token(jar):
|
|
|
153
153
|
tok = cookie_value(jar, "XSRF-TOKEN")
|
|
154
154
|
if not tok:
|
|
155
155
|
die("no XSRF-TOKEN cookie — reconnect 微博 at "
|
|
156
|
-
"https://
|
|
156
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
157
157
|
return tok
|
|
158
158
|
|
|
159
159
|
|
|
@@ -165,7 +165,7 @@ def wb_uid(jar):
|
|
|
165
165
|
uid = data.get("uid")
|
|
166
166
|
if not uid:
|
|
167
167
|
die("not logged in or uid unavailable (cookie expired?) — reconnect 微博 "
|
|
168
|
-
"at https://
|
|
168
|
+
"at https://studio.acedata.cloud/console/connectors.")
|
|
169
169
|
return uid
|
|
170
170
|
|
|
171
171
|
|
package/skills/x/SKILL.md
CHANGED
|
@@ -79,7 +79,7 @@ belongs to someone else. Do not invent an expected handle — plain `whoami` is
|
|
|
79
79
|
the default.
|
|
80
80
|
|
|
81
81
|
On an actual auth error the cookie is expired — have the user reconnect at
|
|
82
|
-
<https://
|
|
82
|
+
<https://studio.acedata.cloud/console/connectors>. A Cloudflare block is different:
|
|
83
83
|
reconnecting cookies does not fix it. Use `whoami` for identity checks; other
|
|
84
84
|
blocked endpoints need `X_PROXY` or the official X API. Do **not** loop-retry a
|
|
85
85
|
Cloudflare block or an auth error.
|
package/skills/x/scripts/x.py
CHANGED
|
@@ -71,7 +71,7 @@ def load_cookie_dict() -> dict:
|
|
|
71
71
|
raw = os.environ.get("X_COOKIES")
|
|
72
72
|
if not raw:
|
|
73
73
|
die("X_COOKIES is not set — connect X (Twitter) at "
|
|
74
|
-
"https://
|
|
74
|
+
"https://studio.acedata.cloud/console/connectors, then retry.")
|
|
75
75
|
try:
|
|
76
76
|
jar = json.loads(raw)
|
|
77
77
|
except json.JSONDecodeError as e:
|
|
@@ -86,7 +86,7 @@ def load_cookie_dict() -> dict:
|
|
|
86
86
|
if "auth_token" not in cookies or "ct0" not in cookies:
|
|
87
87
|
die("X_COOKIES is missing auth_token / ct0 — re-capture the cookie on "
|
|
88
88
|
"x.com with the ACE extension, then reconnect at "
|
|
89
|
-
"https://
|
|
89
|
+
"https://studio.acedata.cloud/console/connectors.")
|
|
90
90
|
return cookies
|
|
91
91
|
|
|
92
92
|
|
|
@@ -595,7 +595,7 @@ def main() -> None:
|
|
|
595
595
|
asyncio.run(run(args))
|
|
596
596
|
except (Unauthorized,) as e:
|
|
597
597
|
die(f"auth failed — cookie likely expired. Reconnect X at "
|
|
598
|
-
f"https://
|
|
598
|
+
f"https://studio.acedata.cloud/console/connectors. ({e})")
|
|
599
599
|
except (AccountLocked, AccountSuspended) as e:
|
|
600
600
|
die(f"account locked/suspended by X: {e}")
|
|
601
601
|
except TooManyRequests as e:
|
|
@@ -635,7 +635,7 @@ def main() -> None:
|
|
|
635
635
|
"cookie. Retry after twikit/X compatibility is fixed."
|
|
636
636
|
)
|
|
637
637
|
die(f"X request failed ({type(e).__name__}: {e}). Likely an expired "
|
|
638
|
-
f"cookie — reconnect at https://
|
|
638
|
+
f"cookie — reconnect at https://studio.acedata.cloud/console/connectors "
|
|
639
639
|
f"— or twikit drift vs X's internal API.")
|
|
640
640
|
|
|
641
641
|
|
package/skills/yuque/SKILL.md
CHANGED
|
@@ -49,7 +49,7 @@ python3 "$Y" whoami
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
On an auth error, ask the user to reconnect at
|
|
52
|
-
<https://
|
|
52
|
+
<https://studio.acedata.cloud/console/connectors>. Never ask for their password,
|
|
53
53
|
and never ask them to paste a Cookie into the chat.
|
|
54
54
|
|
|
55
55
|
## Read
|
|
@@ -42,7 +42,7 @@ TOKEN_ONLY_COMMANDS = {"update", "delete"}
|
|
|
42
42
|
MAX_CONTENT_BYTES = 10 * 1024 * 1024
|
|
43
43
|
MAX_ITEMS = 100
|
|
44
44
|
|
|
45
|
-
RECONNECT = "https://
|
|
45
|
+
RECONNECT = "https://studio.acedata.cloud/console/connectors"
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def output(value) -> None:
|
|
@@ -55,7 +55,7 @@ class ModeSelection(unittest.TestCase):
|
|
|
55
55
|
def test_no_credential_dies_with_a_reconnect_hint(self):
|
|
56
56
|
code, payload = run(["whoami"], {})
|
|
57
57
|
self.assertEqual(code, 1)
|
|
58
|
-
self.assertIn("
|
|
58
|
+
self.assertIn("console/connectors", payload["error"])
|
|
59
59
|
|
|
60
60
|
def test_cookie_jar_without_ctoken_is_rejected(self):
|
|
61
61
|
jar = [c for c in COOKIES if c["name"] != "yuque_ctoken"]
|
package/skills/zhihu/SKILL.md
CHANGED
|
@@ -142,7 +142,7 @@ python3 "$ZDIR/scripts/blog.py" whoami
|
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
On a `401`/`403` the cookie is expired — tell the user to reconnect at
|
|
145
|
-
<https://
|
|
145
|
+
<https://studio.acedata.cloud/console/connectors> (re-capture with the ACE
|
|
146
146
|
extension). Do **not** retry in a loop.
|
|
147
147
|
|
|
148
148
|
## Reading recipes
|
|
@@ -234,7 +234,7 @@ python3 "$BLOG" edit-answer --id <answer-id> --content-file ans.html --confirm
|
|
|
234
234
|
- **This is the user's real Zhihu account.** Confirm before any publish / answer /
|
|
235
235
|
edit; reading exposes their own private drafts.
|
|
236
236
|
- **Cookie expiry**: Zhihu cookies are short-lived. A `401`/`403` means
|
|
237
|
-
reconnect at
|
|
237
|
+
reconnect at studio.acedata.cloud/console/connectors — never loop-retry.
|
|
238
238
|
- **ToS**: cookie automation is against most platforms' terms. This only ever
|
|
239
239
|
acts on the user's own account with their own captured cookie; the user owns
|
|
240
240
|
that risk. Never use it to scrape other people's content at scale.
|
|
@@ -73,7 +73,7 @@ def load_cookies(platform: str) -> list[dict]:
|
|
|
73
73
|
if not raw:
|
|
74
74
|
die(
|
|
75
75
|
f"{env} is not set — connect the {platform} account at "
|
|
76
|
-
f"https://
|
|
76
|
+
f"https://studio.acedata.cloud/console/connectors, then retry."
|
|
77
77
|
)
|
|
78
78
|
try:
|
|
79
79
|
jar = json.loads(raw)
|
|
@@ -156,7 +156,7 @@ def get_json(url: str, jar: list[dict], **kw):
|
|
|
156
156
|
if status == 401 or status == 403:
|
|
157
157
|
die(
|
|
158
158
|
f"auth failed ({status}) on {url} — the cookie is likely expired. "
|
|
159
|
-
f"Reconnect at https://
|
|
159
|
+
f"Reconnect at https://studio.acedata.cloud/console/connectors."
|
|
160
160
|
)
|
|
161
161
|
try:
|
|
162
162
|
return status, json.loads(text)
|