emusks 2.0.0 → 2.0.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 +2 -2
- package/package.json +1 -1
- package/src/helpers/media.js +0 -16
- package/src/helpers/tweets.js +0 -2
- package/src/parsers/timeline.js +0 -36
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<img src="
|
|
1
|
+
<img src="https://emusks.tiago.zip/logo.svg" width="120">
|
|
2
2
|
<h1>emusks: Reverse-engineered Twitter API client</h1>
|
|
3
3
|
|
|
4
4
|
Log in and interact with the unofficial X API using any client identity — web, Android, iOS, or TweetDeck
|
|
5
5
|
|
|
6
|
-
[Learn more →](https://emusks.
|
|
6
|
+
[Learn more →](https://emusks.tiago.zip)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emusks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Reverse-engineered Twitter API client. Log in and interact with the unofficial X API using any client identity — web, Android, iOS, or TweetDeck",
|
|
5
5
|
"keywords": ["twitter", "twitter-api", "x", "x-api", "reverse-engineering", "client"],
|
|
6
6
|
"homepage": "https://github.com/tiagozip/cap",
|
package/src/helpers/media.js
CHANGED
|
@@ -127,20 +127,9 @@ async function makeUploadRequest(
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export default (client) => ({
|
|
130
|
-
/**
|
|
131
|
-
* Upload media (image, GIF, or video) to Twitter.
|
|
132
|
-
*
|
|
133
|
-
* @param {string|Buffer|Uint8Array|ArrayBuffer|Blob} source - file path, Buffer, Uint8Array, ArrayBuffer, or Blob
|
|
134
|
-
* @param {object} [opts]
|
|
135
|
-
* @param {string} [opts.alt_text] - alt text for accessibility
|
|
136
|
-
* @param {string} [opts.mediaType] - explicit MIME type (e.g. "image/png"); auto-detected when omitted
|
|
137
|
-
* @param {"tweet"|"dm"} [opts.type] - upload context; defaults to "tweet"
|
|
138
|
-
* @returns {{ media_id: string }}
|
|
139
|
-
*/
|
|
140
130
|
async create(source, opts = {}) {
|
|
141
131
|
if (!client.auth) throw new Error("you must be logged in to upload media");
|
|
142
132
|
|
|
143
|
-
// ── normalise input to Buffer ──────────────────────────────────────
|
|
144
133
|
let buf;
|
|
145
134
|
let mediaType = opts.mediaType;
|
|
146
135
|
|
|
@@ -174,7 +163,6 @@ export default (client) => ({
|
|
|
174
163
|
const category = getMediaCategory(mediaType, uploadType);
|
|
175
164
|
checkMediaSize(category, totalBytes);
|
|
176
165
|
|
|
177
|
-
// ── INIT ───────────────────────────────────────────────────────────
|
|
178
166
|
const initRes = await makeUploadRequest(client, "post", {
|
|
179
167
|
command: "INIT",
|
|
180
168
|
media_type: mediaType,
|
|
@@ -188,7 +176,6 @@ export default (client) => ({
|
|
|
188
176
|
}
|
|
189
177
|
const mediaId = initData.media_id_string;
|
|
190
178
|
|
|
191
|
-
// ── APPEND (chunked, base64) ───────────────────────────────────────
|
|
192
179
|
let segmentIndex = 0;
|
|
193
180
|
for (let offset = 0; offset < totalBytes; offset += CHUNK_SIZE) {
|
|
194
181
|
const chunk = buf.slice(
|
|
@@ -211,7 +198,6 @@ export default (client) => ({
|
|
|
211
198
|
segmentIndex++;
|
|
212
199
|
}
|
|
213
200
|
|
|
214
|
-
// ── FINALIZE ───────────────────────────────────────────────────────
|
|
215
201
|
const finalizeRes = await makeUploadRequest(client, "post", {
|
|
216
202
|
command: "FINALIZE",
|
|
217
203
|
media_id: mediaId,
|
|
@@ -225,7 +211,6 @@ export default (client) => ({
|
|
|
225
211
|
);
|
|
226
212
|
}
|
|
227
213
|
|
|
228
|
-
// ── poll for async processing (videos / gifs) ──────────────────────
|
|
229
214
|
let processingInfo = finalizeData?.processing_info;
|
|
230
215
|
while (processingInfo) {
|
|
231
216
|
if (processingInfo.error) {
|
|
@@ -251,7 +236,6 @@ export default (client) => ({
|
|
|
251
236
|
processingInfo = statusData?.processing_info;
|
|
252
237
|
}
|
|
253
238
|
|
|
254
|
-
// ── alt text ───────────────────────────────────────────────────────
|
|
255
239
|
if (opts.alt_text) {
|
|
256
240
|
await client.v1_1("media/metadata/create", {
|
|
257
241
|
body: JSON.stringify({
|
package/src/helpers/tweets.js
CHANGED
|
@@ -37,7 +37,6 @@ async function createPollCard(client, poll) {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
if (hasImages) {
|
|
40
|
-
// media poll — uses a different card type and includes image media IDs
|
|
41
40
|
cardObj["twitter:card"] = `poll_choice_images`;
|
|
42
41
|
for (let i = 0; i < poll.choices.length; i++) {
|
|
43
42
|
cardObj[`twitter:string:choice${i + 1}_label`] = labels[i];
|
|
@@ -45,7 +44,6 @@ async function createPollCard(client, poll) {
|
|
|
45
44
|
`mis://${poll.choices[i].image}`;
|
|
46
45
|
}
|
|
47
46
|
} else {
|
|
48
|
-
// text-only poll
|
|
49
47
|
cardObj["twitter:card"] = `poll${poll.choices.length}choice_text_only`;
|
|
50
48
|
for (let i = 0; i < labels.length; i++) {
|
|
51
49
|
cardObj[`twitter:string:choice${i + 1}_label`] = labels[i];
|
package/src/parsers/timeline.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import parseTweet from "./tweet.js";
|
|
2
2
|
import parseUser from "./user.js";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Recursively find the `instructions` array from any Twitter timeline response.
|
|
6
|
-
*
|
|
7
|
-
* Known response shapes:
|
|
8
|
-
* data.home.home_timeline_urt.instructions
|
|
9
|
-
* data.user.result.timeline_v2.timeline.instructions
|
|
10
|
-
* data.search_by_raw_query.search_timeline.timeline.instructions
|
|
11
|
-
* data.bookmark_timeline_v2.timeline.instructions
|
|
12
|
-
* data.bookmark_search_timeline.timeline.instructions
|
|
13
|
-
* data.list.tweets_timeline.timeline.instructions
|
|
14
|
-
* data.timeline_by_id.timeline.instructions
|
|
15
|
-
* ... and many more
|
|
16
|
-
*/
|
|
17
4
|
function findInstructions(obj) {
|
|
18
5
|
if (!obj || typeof obj !== "object") return null;
|
|
19
6
|
|
|
@@ -32,11 +19,7 @@ function findInstructions(obj) {
|
|
|
32
19
|
return null;
|
|
33
20
|
}
|
|
34
21
|
|
|
35
|
-
/**
|
|
36
|
-
* Extract tweet results from a single timeline entry.
|
|
37
|
-
*/
|
|
38
22
|
function extractTweetFromEntry(entry) {
|
|
39
|
-
// Standard tweet entry
|
|
40
23
|
const tweetResult =
|
|
41
24
|
entry?.content?.itemContent?.tweet_results?.result ||
|
|
42
25
|
entry?.item?.itemContent?.tweet_results?.result;
|
|
@@ -46,9 +29,6 @@ function extractTweetFromEntry(entry) {
|
|
|
46
29
|
return null;
|
|
47
30
|
}
|
|
48
31
|
|
|
49
|
-
/**
|
|
50
|
-
* Extract user results from a single timeline entry.
|
|
51
|
-
*/
|
|
52
32
|
function extractUserFromEntry(entry) {
|
|
53
33
|
const userResult =
|
|
54
34
|
entry?.content?.itemContent?.user_results?.result ||
|
|
@@ -59,12 +39,6 @@ function extractUserFromEntry(entry) {
|
|
|
59
39
|
return null;
|
|
60
40
|
}
|
|
61
41
|
|
|
62
|
-
/**
|
|
63
|
-
* Parse a raw Twitter timeline/search/bookmark GraphQL response into
|
|
64
|
-
* `{ tweets, users, nextCursor, previousCursor }`.
|
|
65
|
-
*
|
|
66
|
-
* Works universally across all timeline-shaped endpoints.
|
|
67
|
-
*/
|
|
68
42
|
export default function parseTimeline(raw) {
|
|
69
43
|
const instructions = findInstructions(raw?.data || raw);
|
|
70
44
|
|
|
@@ -78,17 +52,10 @@ export default function parseTimeline(raw) {
|
|
|
78
52
|
let previousCursor = null;
|
|
79
53
|
|
|
80
54
|
for (const instruction of instructions) {
|
|
81
|
-
const entries =
|
|
82
|
-
instruction.entries || // TimelineAddEntries
|
|
83
|
-
instruction.entry // TimelineReplaceEntry (single)
|
|
84
|
-
? [instruction.entry]
|
|
85
|
-
: null;
|
|
86
|
-
|
|
87
55
|
if (instruction.type === "TimelineAddEntries" || instruction.entries) {
|
|
88
56
|
for (const entry of instruction.entries || []) {
|
|
89
57
|
processEntry(entry, tweets, users);
|
|
90
58
|
|
|
91
|
-
// Extract cursors
|
|
92
59
|
if (entry.entryId?.startsWith("cursor-bottom") || entry.entryId?.includes("cursor-bottom")) {
|
|
93
60
|
nextCursor =
|
|
94
61
|
entry.content?.value || entry.content?.itemContent?.value || null;
|
|
@@ -115,21 +82,18 @@ export default function parseTimeline(raw) {
|
|
|
115
82
|
}
|
|
116
83
|
|
|
117
84
|
function processEntry(entry, tweets, users) {
|
|
118
|
-
// Direct tweet entry (tweet-*)
|
|
119
85
|
const tweet = extractTweetFromEntry(entry);
|
|
120
86
|
if (tweet) {
|
|
121
87
|
tweets.push(tweet);
|
|
122
88
|
return;
|
|
123
89
|
}
|
|
124
90
|
|
|
125
|
-
// Direct user entry (user-*)
|
|
126
91
|
const user = extractUserFromEntry(entry);
|
|
127
92
|
if (user) {
|
|
128
93
|
users.push(user);
|
|
129
94
|
return;
|
|
130
95
|
}
|
|
131
96
|
|
|
132
|
-
// Module entries (conversationthread-*, profile-grid-*, etc.)
|
|
133
97
|
if (entry.content?.items) {
|
|
134
98
|
for (const item of entry.content.items) {
|
|
135
99
|
const t = extractTweetFromEntry(item);
|