emusks 2.1.1 → 2.2.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/src/grok.js +77 -0
- package/src/helpers/articles.js +90 -0
- package/src/helpers/grok.js +139 -0
- package/src/helpers/index.js +4 -0
- package/src/index.js +2 -0
- package/src/parsers/grok.js +65 -0
- package/src/static/graphql.js +1 -1
- package/src/static/v1.1.js +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<img src="https://emusks.tiago.zip/icon.svg" width="120">
|
|
2
2
|
<h1>emusks: Reverse-engineered Twitter API client</h1>
|
|
3
3
|
|
|
4
|
-
Log in and interact with the unofficial X API using any client identity - web, Android, iOS, or TweetDeck. Covers tweets, users, DMs, communities, spaces, and XChat (X's end-to-end encrypted chat): send encrypted DMs in one call.
|
|
4
|
+
Log in and interact with the unofficial X API using any client identity - web, Android, iOS, or TweetDeck. Covers tweets, users, DMs, communities, spaces, articles, Grok (X's built-in AI), and XChat (X's end-to-end encrypted chat): send encrypted DMs in one call.
|
|
5
5
|
|
|
6
6
|
officially dmca'd by twitter™ 🏆 • includes a few leaked ads bearers
|
|
7
7
|
|
package/package.json
CHANGED
package/src/grok.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ClientTransaction, handleXMigration } from "x-client-transaction-id";
|
|
2
|
+
import getCycleTLS from "./cycletls.js";
|
|
3
|
+
|
|
4
|
+
const GROK_BASE = "https://api.x.com/2/grok";
|
|
5
|
+
|
|
6
|
+
export default async function grokApi(route, opts = {}) {
|
|
7
|
+
if (!this.auth) throw new Error("must be logged in before calling grok");
|
|
8
|
+
|
|
9
|
+
const { method = "POST", body, params, headers } = opts;
|
|
10
|
+
|
|
11
|
+
const cleanRoute = String(route).replace(/^\/+/, "");
|
|
12
|
+
let finalUrl = `${GROK_BASE}/${cleanRoute}`;
|
|
13
|
+
|
|
14
|
+
if (params && Object.keys(params).length) {
|
|
15
|
+
const searchParams = new URLSearchParams(params);
|
|
16
|
+
const separator = finalUrl.includes("?") ? "&" : "?";
|
|
17
|
+
finalUrl = `${finalUrl}${separator}${searchParams.toString()}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pathname = new URL(finalUrl).pathname;
|
|
21
|
+
|
|
22
|
+
if (!this.auth.generateTransactionId) {
|
|
23
|
+
const document = await handleXMigration();
|
|
24
|
+
const transaction = new ClientTransaction(document);
|
|
25
|
+
await transaction.initialize();
|
|
26
|
+
this.auth.generateTransactionId = transaction.generateTransactionId.bind(transaction);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const requestHeaders = {
|
|
30
|
+
accept: "*/*",
|
|
31
|
+
"accept-language": "en-US,en;q=0.9",
|
|
32
|
+
authorization: `Bearer ${this.auth.client.bearer}`,
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
"x-csrf-token": this.auth.csrfToken,
|
|
35
|
+
"x-twitter-active-user": "yes",
|
|
36
|
+
"x-twitter-auth-type": "OAuth2Session",
|
|
37
|
+
"x-twitter-client-language": "en",
|
|
38
|
+
"x-client-transaction-id": await this.auth.generateTransactionId(method.toUpperCase(), pathname),
|
|
39
|
+
priority: "u=1, i",
|
|
40
|
+
"sec-ch-ua": '"Not(A:Brand";v="8", "Chromium";v="144"',
|
|
41
|
+
"sec-ch-ua-mobile": "?0",
|
|
42
|
+
"sec-ch-ua-platform": '"macOS"',
|
|
43
|
+
"sec-fetch-dest": "empty",
|
|
44
|
+
"sec-fetch-mode": "cors",
|
|
45
|
+
"sec-fetch-site": "same-site",
|
|
46
|
+
"sec-gpc": "1",
|
|
47
|
+
cookie:
|
|
48
|
+
this.auth.client.headers.cookie + (this.elevatedCookies ? `; ${this.elevatedCookies}` : ""),
|
|
49
|
+
...headers,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const requestBody =
|
|
53
|
+
body == null ? undefined : typeof body === "string" ? body : JSON.stringify(body);
|
|
54
|
+
|
|
55
|
+
const cycleTLS = await getCycleTLS();
|
|
56
|
+
const res = await cycleTLS(
|
|
57
|
+
finalUrl,
|
|
58
|
+
{
|
|
59
|
+
headers: requestHeaders,
|
|
60
|
+
userAgent: this.auth.client.fingerprints.userAgent,
|
|
61
|
+
ja3: this.auth.client.fingerprints.ja3,
|
|
62
|
+
ja4r: this.auth.client.fingerprints.ja4r,
|
|
63
|
+
body: requestBody,
|
|
64
|
+
proxy: this.proxy || undefined,
|
|
65
|
+
referrer: "https://x.com/",
|
|
66
|
+
},
|
|
67
|
+
method,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
return {
|
|
72
|
+
status: res.status,
|
|
73
|
+
headers: res.headers,
|
|
74
|
+
text,
|
|
75
|
+
json: () => JSON.parse(text),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import parseTimeline from "../parsers/timeline.js";
|
|
2
|
+
|
|
3
|
+
export async function get(articleEntityId) {
|
|
4
|
+
if (!articleEntityId) throw new Error("get requires an articleEntityId");
|
|
5
|
+
const res = await this.graphql("ArticleEntityResultByRestId", {
|
|
6
|
+
variables: { articleEntityId },
|
|
7
|
+
});
|
|
8
|
+
return res?.data?.article_result_by_rest_id?.result ?? res;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function byUser(userId, opts = {}) {
|
|
12
|
+
if (!userId) throw new Error("byUser requires a userId");
|
|
13
|
+
const res = await this.graphql("UserArticlesTweets", {
|
|
14
|
+
variables: {
|
|
15
|
+
userId,
|
|
16
|
+
count: opts.count || 20,
|
|
17
|
+
cursor: opts.cursor,
|
|
18
|
+
includePromotedContent: false,
|
|
19
|
+
withVoice: false,
|
|
20
|
+
...opts.variables,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (opts.raw) return res;
|
|
24
|
+
return parseTimeline(res);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function slice(userId, lifecycle = "Published", opts = {}) {
|
|
28
|
+
if (!userId) throw new Error("slice requires a userId");
|
|
29
|
+
const res = await this.graphql("ArticleEntitiesSlice", {
|
|
30
|
+
variables: { userId, lifecycle, cursor: opts.cursor, ...opts.variables },
|
|
31
|
+
});
|
|
32
|
+
return res?.data ?? res;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function drafts(userId, opts = {}) {
|
|
36
|
+
return await slice.call(this, userId, "Draft", opts);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function published(userId, opts = {}) {
|
|
40
|
+
return await slice.call(this, userId, "Published", opts);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function createDraft(contentState, opts = {}) {
|
|
44
|
+
if (!contentState) throw new Error("createDraft requires a content_state");
|
|
45
|
+
return await this.graphql("ArticleEntityDraftCreate", {
|
|
46
|
+
body: { variables: { content_state: contentState, ...opts.variables } },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function updateTitle(articleEntityId, title) {
|
|
51
|
+
if (!articleEntityId) throw new Error("updateTitle requires an articleEntityId");
|
|
52
|
+
return await this.graphql("ArticleEntityUpdateTitle", {
|
|
53
|
+
body: { variables: { articleEntityId, title } },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function updateContent(articleEntityId, contentState) {
|
|
58
|
+
if (!articleEntityId) throw new Error("updateContent requires an articleEntityId");
|
|
59
|
+
return await this.graphql("ArticleEntityUpdateContent", {
|
|
60
|
+
body: { variables: { articleEntityId, content_state: contentState } },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function updateCoverMedia(articleEntityId, coverMediaId) {
|
|
65
|
+
if (!articleEntityId) throw new Error("updateCoverMedia requires an articleEntityId");
|
|
66
|
+
return await this.graphql("ArticleEntityUpdateCoverMedia", {
|
|
67
|
+
body: { variables: { articleEntityId, cover_media_id: coverMediaId } },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function publish(articleEntityId) {
|
|
72
|
+
if (!articleEntityId) throw new Error("publish requires an articleEntityId");
|
|
73
|
+
return await this.graphql("ArticleEntityPublish", {
|
|
74
|
+
body: { variables: { articleEntityId } },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function unpublish(articleEntityId) {
|
|
79
|
+
if (!articleEntityId) throw new Error("unpublish requires an articleEntityId");
|
|
80
|
+
return await this.graphql("ArticleEntityUnpublish", {
|
|
81
|
+
body: { variables: { articleEntityId } },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function remove(articleEntityId) {
|
|
86
|
+
if (!articleEntityId) throw new Error("remove requires an articleEntityId");
|
|
87
|
+
return await this.graphql("ArticleEntityDelete", {
|
|
88
|
+
body: { variables: { articleEntityId } },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import parseGrokStream from "../parsers/grok.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MODEL = "grok-3-latest";
|
|
4
|
+
|
|
5
|
+
export async function createConversation() {
|
|
6
|
+
const res = await this.graphql("CreateGrokConversation", { body: { variables: {} } });
|
|
7
|
+
return res?.data?.create_grok_conversation?.conversation_id ?? res;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function respond(conversationId, message, opts = {}) {
|
|
11
|
+
if (!conversationId) throw new Error("respond requires a conversationId");
|
|
12
|
+
|
|
13
|
+
const body = {
|
|
14
|
+
responses: [
|
|
15
|
+
{
|
|
16
|
+
message: message ?? "",
|
|
17
|
+
sender: 1,
|
|
18
|
+
promptSource: opts.promptSource ?? "",
|
|
19
|
+
fileAttachments: opts.fileAttachments || [],
|
|
20
|
+
},
|
|
21
|
+
...(opts.responses || []),
|
|
22
|
+
],
|
|
23
|
+
systemPromptName: opts.systemPromptName ?? "",
|
|
24
|
+
grokModelOptionId: opts.model || DEFAULT_MODEL,
|
|
25
|
+
conversationId,
|
|
26
|
+
returnSearchResults: opts.returnSearchResults ?? true,
|
|
27
|
+
returnCitations: opts.returnCitations ?? true,
|
|
28
|
+
promptMetadata: opts.promptMetadata || { promptSource: "NATURAL", action: "INPUT" },
|
|
29
|
+
imageGenerationCount: opts.imageGenerationCount ?? 4,
|
|
30
|
+
requestFeatures: opts.requestFeatures || { eagerTweets: true, serverHistory: true },
|
|
31
|
+
enableSideBySide: opts.enableSideBySide ?? true,
|
|
32
|
+
toolOverrides: opts.toolOverrides || {},
|
|
33
|
+
isDeepsearch: opts.deepsearch ?? false,
|
|
34
|
+
isReasoning: opts.reasoning ?? false,
|
|
35
|
+
...opts.body,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const res = await this.grokApi("add_response.json", { method: "POST", body });
|
|
39
|
+
if (res.status !== 200) {
|
|
40
|
+
let detail = res.text;
|
|
41
|
+
try {
|
|
42
|
+
detail = res.json()?.errors?.map((e) => e.message).join(", ") || res.text;
|
|
43
|
+
} catch {}
|
|
44
|
+
throw new Error(`grok add_response failed (${res.status}): ${detail}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (opts.raw) return res.text;
|
|
48
|
+
return parseGrokStream(res.text);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function ask(message, opts = {}) {
|
|
52
|
+
const conversationId = opts.conversationId || (await createConversation.call(this));
|
|
53
|
+
const result = await respond.call(this, conversationId, message, opts);
|
|
54
|
+
if (opts.raw) return result;
|
|
55
|
+
return { ...result, conversationId: result.conversationId || conversationId };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function home() {
|
|
59
|
+
const res = await this.graphql("GrokHome", { variables: {} });
|
|
60
|
+
return res?.data?.grok_home ?? res;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function models() {
|
|
64
|
+
const h = await home.call(this);
|
|
65
|
+
return h?.grok_model_options ?? [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function history(opts = {}) {
|
|
69
|
+
const res = await this.graphql("GrokHistory", {
|
|
70
|
+
variables: { count: opts.count, cursor: opts.cursor, ...opts.variables },
|
|
71
|
+
});
|
|
72
|
+
return res?.data?.grok_conversation_history ?? res;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function mediaHistory(opts = {}) {
|
|
76
|
+
const res = await this.graphql("GrokMediaHistory", {
|
|
77
|
+
variables: { count: opts.count, cursor: opts.cursor, ...opts.variables },
|
|
78
|
+
});
|
|
79
|
+
return res?.data ?? res;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function conversation(restId, opts = {}) {
|
|
83
|
+
if (!restId) throw new Error("conversation requires a rest id");
|
|
84
|
+
const res = await this.graphql("GrokConversationItemsByRestId", {
|
|
85
|
+
variables: { restId, ...opts.variables },
|
|
86
|
+
});
|
|
87
|
+
return res?.data ?? res;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function search(query, opts = {}) {
|
|
91
|
+
const res = await this.graphql("SearchGrokConversations", {
|
|
92
|
+
variables: { query, ...opts.variables },
|
|
93
|
+
});
|
|
94
|
+
return res?.data ?? res;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function pinned() {
|
|
98
|
+
const res = await this.graphql("GrokPinnedConversations", { variables: {} });
|
|
99
|
+
return res?.data?.grok_pinned_conversations ?? res;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function pin(conversationId) {
|
|
103
|
+
if (!conversationId) throw new Error("pin requires a conversationId");
|
|
104
|
+
const res = await this.graphql("GrokPinConversation", {
|
|
105
|
+
body: { variables: { conversationId } },
|
|
106
|
+
});
|
|
107
|
+
return res?.data?.grokconversation_pin ?? res;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function unpin(conversationId) {
|
|
111
|
+
if (!conversationId) throw new Error("unpin requires a conversationId");
|
|
112
|
+
const res = await this.graphql("GrokUnpinConversation", {
|
|
113
|
+
body: { variables: { conversationId } },
|
|
114
|
+
});
|
|
115
|
+
return res?.data?.grokconversation_unpin ?? res;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function deleteMessage(conversationId, chatItemId) {
|
|
119
|
+
if (!conversationId || !chatItemId) {
|
|
120
|
+
throw new Error("deleteMessage requires (conversationId, chatItemId)");
|
|
121
|
+
}
|
|
122
|
+
return await this.graphql("DeleteGrokMessage", {
|
|
123
|
+
body: { variables: { conversation_id: conversationId, chat_item_id: chatItemId } },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function clear() {
|
|
128
|
+
return await this.graphql("ClearGrokConversations", { body: { variables: {} } });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function share(shareId) {
|
|
132
|
+
if (!shareId) throw new Error("share requires a grok_share_id");
|
|
133
|
+
const res = await this.graphql("GrokShare", { variables: { grok_share_id: shareId } });
|
|
134
|
+
return res?.data ?? res;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function setPreferences(params = {}) {
|
|
138
|
+
return await this.graphql("SetGrokPreferences", { body: { variables: { ...params } } });
|
|
139
|
+
}
|
package/src/helpers/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as account from "./account.js";
|
|
2
|
+
import * as articles from "./articles.js";
|
|
2
3
|
import * as bookmarks from "./bookmarks.js";
|
|
3
4
|
import * as communities from "./communities.js";
|
|
4
5
|
import * as dms from "./dms.js";
|
|
6
|
+
import * as grok from "./grok.js";
|
|
5
7
|
import * as jetfuel from "./jetfuel.js";
|
|
6
8
|
import * as lists from "./lists.js";
|
|
7
9
|
import * as media from "./media.js";
|
|
@@ -52,4 +54,6 @@ export default function initHelpers(proto) {
|
|
|
52
54
|
namespace(proto, "syndication", syndication);
|
|
53
55
|
namespace(proto, "xchat", xchat);
|
|
54
56
|
namespace(proto, "jetfuel", jetfuel);
|
|
57
|
+
namespace(proto, "grok", grok);
|
|
58
|
+
namespace(proto, "articles", articles);
|
|
55
59
|
}
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import clients from "./clients.js";
|
|
|
3
3
|
import getCycleTLS from "./cycletls.js";
|
|
4
4
|
import flowLogin from "./flow.js";
|
|
5
5
|
import graphql, { GRAPHQL_ENDPOINTS } from "./graphql.js";
|
|
6
|
+
import grokApi from "./grok.js";
|
|
6
7
|
import initHelpers from "./helpers/index.js";
|
|
7
8
|
import jetfuel from "./jetfuel.js";
|
|
8
9
|
import parseUser from "./parsers/user.js";
|
|
@@ -195,4 +196,5 @@ Emusks.prototype.graphql = graphql;
|
|
|
195
196
|
Emusks.prototype.v1_1 = v1_1;
|
|
196
197
|
Emusks.prototype.v2 = v2;
|
|
197
198
|
Emusks.prototype.jf = jetfuel;
|
|
199
|
+
Emusks.prototype.grokApi = grokApi;
|
|
198
200
|
initHelpers(Emusks.prototype);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export default function parseGrokStream(text) {
|
|
2
|
+
const result = {
|
|
3
|
+
conversationId: null,
|
|
4
|
+
userChatItemId: null,
|
|
5
|
+
agentChatItemId: null,
|
|
6
|
+
message: "",
|
|
7
|
+
reasoning: "",
|
|
8
|
+
softStop: false,
|
|
9
|
+
followUpSuggestions: [],
|
|
10
|
+
webResults: [],
|
|
11
|
+
citations: [],
|
|
12
|
+
images: [],
|
|
13
|
+
cards: [],
|
|
14
|
+
requestEntities: null,
|
|
15
|
+
responseEntities: null,
|
|
16
|
+
chunks: [],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
for (const line of String(text).split("\n")) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed) continue;
|
|
22
|
+
|
|
23
|
+
let obj;
|
|
24
|
+
try {
|
|
25
|
+
obj = JSON.parse(trimmed);
|
|
26
|
+
} catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
result.chunks.push(obj);
|
|
30
|
+
|
|
31
|
+
if (obj.conversationId && !obj.result) {
|
|
32
|
+
result.conversationId = obj.conversationId;
|
|
33
|
+
result.userChatItemId = obj.userChatItemId ?? result.userChatItemId;
|
|
34
|
+
result.agentChatItemId = obj.agentChatItemId ?? result.agentChatItemId;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const r = obj.result;
|
|
39
|
+
if (!r) continue;
|
|
40
|
+
|
|
41
|
+
if (r.responseChatItemId) result.agentChatItemId = r.responseChatItemId;
|
|
42
|
+
if (r.isSoftStop) result.softStop = true;
|
|
43
|
+
if (Array.isArray(r.followUpSuggestions)) {
|
|
44
|
+
result.followUpSuggestions.push(...r.followUpSuggestions);
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(r.citations)) result.citations.push(...r.citations);
|
|
47
|
+
if (Array.isArray(r.webResults)) result.webResults.push(...r.webResults);
|
|
48
|
+
if (Array.isArray(r.cards)) result.cards.push(...r.cards);
|
|
49
|
+
if (r.requestEntities) result.requestEntities = r.requestEntities;
|
|
50
|
+
if (r.responseEntities) result.responseEntities = r.responseEntities;
|
|
51
|
+
|
|
52
|
+
const image = r.imageAttachment || r.attachment || r.generatedImage;
|
|
53
|
+
if (image) result.images.push(image);
|
|
54
|
+
|
|
55
|
+
if (typeof r.message === "string") {
|
|
56
|
+
if (r.isThinking || r.messageTag === "header") {
|
|
57
|
+
result.reasoning += r.message;
|
|
58
|
+
} else if (r.messageTag === "final" || (!r.messageTag && r.message)) {
|
|
59
|
+
result.message += r.message;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|