@acedatacloud/skills 2026.706.2 → 2026.706.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.706.2",
3
+ "version": "2026.706.3",
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",
package/skills/x/SKILL.md CHANGED
@@ -22,9 +22,10 @@ Drives the user's **real** X account through X's internal web API via
22
22
  [`twikit`](https://github.com/d60/twikit), authenticated by the login cookie they
23
23
  captured with the ACE extension. No official API key, no cost.
24
24
 
25
- > ⚠️ **Not yet E2E-verified.** Built against twikit's documented API but not run
26
- > against a live account at build time. The first live run is the verification —
27
- > if X's internal API drifted it surfaces as a clear error, not silent breakage.
25
+ > E2E-verified on 2026-07-06 with a real connected account for: cookie load,
26
+ > whoami, tweet search, user search, home timeline, trends, tweet detail, and
27
+ > post dry-run. Live writes still require explicit confirmation and were not
28
+ > executed in the verification run.
28
29
 
29
30
  The connector injects the cookie jar as an env var:
30
31
 
@@ -103,10 +104,11 @@ python3 $X delete --id 123456 --confirm # delete one of M
103
104
  immediate and public.
104
105
  - **Not E2E-verified** (see the warning above) — expect to validate the first run.
105
106
  - **twikit is a scraper of X's non-public API.** It can break when X changes its
106
- internal endpoints. A `Couldn't get KEY_BYTE indices` / transaction-id error
107
- means twikit's transaction-id bootstrap is currently broken against X; do NOT
108
- ask the user to reconnect cookies for that error. Report it as upstream drift
109
- and retry only after the twikit/X compatibility issue is fixed.
107
+ internal endpoints. The bundled script carries an AceDataCloud compatibility
108
+ patch for the current `ondemand.s` webpack chunk map used to generate
109
+ transaction IDs. If `Couldn't get KEY_BYTE indices` appears again, report it
110
+ as X/twikit upstream drift — do NOT ask the user to reconnect cookies for that
111
+ specific error.
110
112
  - **ToS / rate-limit / ban risk.** This acts through the web API, not the
111
113
  official API — high-frequency automation can get the account rate-limited or
112
114
  suspended. Keep volume human-like.
@@ -36,7 +36,9 @@ import argparse
36
36
  import asyncio
37
37
  import json
38
38
  import os
39
+ import re
39
40
  import sys
41
+ from urllib.parse import unquote
40
42
 
41
43
  UA = (
42
44
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
@@ -88,8 +90,17 @@ def load_cookie_dict() -> dict:
88
90
  return cookies
89
91
 
90
92
 
93
+ def current_user_id_from_cookies() -> str | None:
94
+ twid = load_cookie_dict().get("twid", "")
95
+ decoded = unquote(twid)
96
+ match = re.search(r"(?:^|[&?])u=(\d+)", decoded) or re.search(r"u=(\d+)", decoded)
97
+ return match.group(1) if match else None
98
+
99
+
91
100
  def make_client():
92
101
  from twikit import Client
102
+ patch_twikit_transaction_resolver()
103
+ patch_twikit_model_defaults()
93
104
  proxy = (
94
105
  os.environ.get("X_PROXY")
95
106
  or os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy")
@@ -101,6 +112,74 @@ def make_client():
101
112
  return client
102
113
 
103
114
 
115
+ def patch_twikit_transaction_resolver() -> None:
116
+ from twikit.x_client_transaction import transaction as tx
117
+
118
+ if getattr(tx.ClientTransaction, "_acedata_chunk_patch", False):
119
+ return
120
+ original_get_indices = tx.ClientTransaction.get_indices
121
+
122
+ async def get_indices(self, home_page_response, session, headers):
123
+ try:
124
+ return await original_get_indices(self, home_page_response, session, headers)
125
+ except Exception as exc:
126
+ if "KEY_BYTE indices" not in str(exc):
127
+ raise
128
+
129
+ response = self.validate_response(home_page_response) or self.home_page_response
130
+ html = str(response)
131
+ chunk_id_match = re.search(r'(\d+):"ondemand\.s"', html)
132
+ if not chunk_id_match:
133
+ raise Exception("Couldn't find ondemand.s chunk id")
134
+ chunk_id = chunk_id_match.group(1)
135
+ hash_match = re.search(rf'{chunk_id}:"([a-f0-9]+)"', html)
136
+ if not hash_match:
137
+ raise Exception("Couldn't find ondemand.s chunk hash")
138
+ url = f"https://abs.twimg.com/responsive-web/client-web/ondemand.s.{hash_match.group(1)}a.js"
139
+ js_response = await session.request(method="GET", url=url, headers=headers)
140
+ indices = [item.group(2) for item in tx.INDICES_REGEX.finditer(str(js_response.text))]
141
+ if not indices:
142
+ raise Exception("Couldn't get KEY_BYTE indices")
143
+ indices = list(map(int, indices))
144
+ return indices[0], indices[1:]
145
+
146
+ tx.ClientTransaction.get_indices = get_indices
147
+ tx.ClientTransaction._acedata_chunk_patch = True
148
+
149
+
150
+ def patch_twikit_model_defaults() -> None:
151
+ from twikit.tweet import Tweet
152
+ from twikit.user import User
153
+
154
+ if getattr(User, "_acedata_defaults_patch", False):
155
+ return
156
+
157
+ user_init = User.__init__
158
+ tweet_init = Tweet.__init__
159
+
160
+ def patched_user_init(self, client, data):
161
+ legacy = data.setdefault("legacy", {})
162
+ entities = legacy.setdefault("entities", {})
163
+ entities.setdefault("description", {}).setdefault("urls", [])
164
+ entities.setdefault("url", {}).setdefault("urls", [])
165
+ legacy.setdefault("pinned_tweet_ids_str", [])
166
+ legacy.setdefault("withheld_in_countries", [])
167
+ return user_init(self, client, data)
168
+
169
+ def patched_tweet_init(self, client, data, user=None):
170
+ legacy = data.setdefault("legacy", {})
171
+ entities = legacy.setdefault("entities", {})
172
+ entities.setdefault("urls", [])
173
+ entities.setdefault("hashtags", [])
174
+ entities.setdefault("media", [])
175
+ data.setdefault("edit_control", {})
176
+ return tweet_init(self, client, data, user)
177
+
178
+ User.__init__ = patched_user_init
179
+ Tweet.__init__ = patched_tweet_init
180
+ User._acedata_defaults_patch = True
181
+
182
+
104
183
  # ── formatting ──────────────────────────────────────────────────────
105
184
 
106
185
  def fmt_user(u) -> dict:
@@ -146,7 +225,8 @@ async def resolve_user(client, target: str):
146
225
  # ── read commands ───────────────────────────────────────────────────
147
226
 
148
227
  async def cmd_whoami(client, _args):
149
- u = await client.user()
228
+ user_id = current_user_id_from_cookies()
229
+ u = await client.get_user_by_id(user_id) if user_id else await client.user()
150
230
  out(fmt_user(u))
151
231
 
152
232
 
@@ -179,12 +259,18 @@ async def cmd_user_tweets(client, args):
179
259
 
180
260
 
181
261
  async def cmd_tweet(client, args):
182
- t = await client.get_tweet_by_id(args.id)
262
+ try:
263
+ tweets = await client.get_tweets_by_ids([args.id])
264
+ t = tweets[0] if tweets else None
265
+ except Exception:
266
+ t = None
267
+ if t is None:
268
+ t = await client.get_tweet_by_id(args.id)
183
269
  out(fmt_tweet(t))
184
270
 
185
271
 
186
272
  async def cmd_trends(client, args):
187
- trends = await client.get_trends(args.category, count=args.limit)
273
+ trends = await client.get_trends(args.category, count=args.limit, retry=False)
188
274
  out({"category": args.category,
189
275
  "trends": [{"name": getattr(x, "name", None),
190
276
  "tweets_count": getattr(x, "tweets_count", None)}