@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 +1 -1
- package/skills/x/SKILL.md +9 -7
- package/skills/x/scripts/x.py +89 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.706.
|
|
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
|
-
>
|
|
26
|
-
>
|
|
27
|
-
>
|
|
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.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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.
|
package/skills/x/scripts/x.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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)}
|