emusks 2.1.2 → 2.3.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 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, Community Notes, delegate/act-as, 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emusks",
3
- "version": "2.1.2",
3
+ "version": "2.3.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": [
6
6
  "client",
package/src/graphql.js CHANGED
@@ -107,6 +107,7 @@ export default async function graphql(queryName, { variables, fieldToggles, body
107
107
  "sec-gpc": "1",
108
108
  cookie:
109
109
  this.auth.client.headers.cookie + (this.elevatedCookies ? `; ${this.elevatedCookies}` : ""),
110
+ ...(this.actingAs ? { "x-act-as-user-id": this.actingAs } : {}),
110
111
  ...headers,
111
112
  };
112
113
 
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,30 @@
1
+ const CONTRIBUTEES = ["get", "https://api.x.com/1.1/users/contributees.json"];
2
+ const CONTRIBUTORS = ["get", "https://api.x.com/1.1/users/contributors.json"];
3
+
4
+ export async function contributees(opts = {}) {
5
+ const params = { ...opts.params };
6
+ if (opts.userId) params.user_id = opts.userId;
7
+ if (opts.screenName) params.screen_name = opts.screenName;
8
+ const res = await this.v1_1(CONTRIBUTEES, { params });
9
+ return await res.json();
10
+ }
11
+
12
+ export async function contributors(opts = {}) {
13
+ const params = { ...opts.params };
14
+ if (opts.userId) params.user_id = opts.userId;
15
+ if (opts.screenName) params.screen_name = opts.screenName;
16
+ const res = await this.v1_1(CONTRIBUTORS, { params });
17
+ return await res.json();
18
+ }
19
+
20
+ export function actAs(userId) {
21
+ return this.setActingAs(userId);
22
+ }
23
+
24
+ export function stop() {
25
+ return this.setActingAs(null);
26
+ }
27
+
28
+ export function current() {
29
+ return this.actingAs;
30
+ }
@@ -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
+ }
@@ -1,10 +1,14 @@
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";
5
+ import * as delegates from "./delegates.js";
4
6
  import * as dms from "./dms.js";
7
+ import * as grok from "./grok.js";
5
8
  import * as jetfuel from "./jetfuel.js";
6
9
  import * as lists from "./lists.js";
7
10
  import * as media from "./media.js";
11
+ import * as notes from "./notes.js";
8
12
  import * as notifications from "./notifications.js";
9
13
  import * as search from "./search.js";
10
14
  import * as spaces from "./spaces.js";
@@ -52,4 +56,8 @@ export default function initHelpers(proto) {
52
56
  namespace(proto, "syndication", syndication);
53
57
  namespace(proto, "xchat", xchat);
54
58
  namespace(proto, "jetfuel", jetfuel);
59
+ namespace(proto, "grok", grok);
60
+ namespace(proto, "articles", articles);
61
+ namespace(proto, "notes", notes);
62
+ namespace(proto, "delegates", delegates);
55
63
  }
@@ -0,0 +1,211 @@
1
+ function data(res) {
2
+ return res?.data ?? res;
3
+ }
4
+
5
+ export async function forTweet(tweetId) {
6
+ if (!tweetId) throw new Error("forTweet requires a tweetId");
7
+ return data(await this.graphql("BirdwatchFetchNotes", { variables: { tweet_id: tweetId } }));
8
+ }
9
+
10
+ export async function get(noteId) {
11
+ if (!noteId) throw new Error("get requires a noteId");
12
+ const res = await this.graphql("BirdwatchFetchOneNote", { variables: { note_id: noteId } });
13
+ return res?.data?.birdwatch_note_by_rest_id ?? res;
14
+ }
15
+
16
+ export async function translation(noteId, opts = {}) {
17
+ if (!noteId) throw new Error("translation requires a noteId");
18
+ return data(
19
+ await this.graphql("BirdwatchFetchNoteTranslation", {
20
+ variables: { note_id: noteId, ...opts.variables },
21
+ }),
22
+ );
23
+ }
24
+
25
+ export async function myProfile() {
26
+ const res = await this.graphql("BirdwatchFetchAuthenticatedUserProfile", { variables: {} });
27
+ return res?.data?.authenticated_user_birdwatch_profile ?? res;
28
+ }
29
+
30
+ export async function profile(alias) {
31
+ if (!alias) throw new Error("profile requires an alias");
32
+ const res = await this.graphql("BirdwatchFetchBirdwatchProfile", { variables: { alias } });
33
+ return res?.data?.birdwatch_profile_by_alias ?? res;
34
+ }
35
+
36
+ export async function contributorNotes(alias, opts = {}) {
37
+ if (!alias) throw new Error("contributorNotes requires an alias");
38
+ return data(
39
+ await this.graphql("BirdwatchFetchContributorNotesSlice", {
40
+ variables: { alias, cursor: opts.cursor, ...opts.variables },
41
+ }),
42
+ );
43
+ }
44
+
45
+ export async function globalTimeline(opts = {}) {
46
+ return data(
47
+ await this.graphql("BirdwatchFetchGlobalTimeline", {
48
+ variables: { cursor: opts.cursor, ...opts.variables },
49
+ }),
50
+ );
51
+ }
52
+
53
+ export async function batSignal(tweetId) {
54
+ if (!tweetId) throw new Error("batSignal requires a tweetId");
55
+ return data(await this.graphql("BirdwatchFetchBatSignal", { variables: { tweet_id: tweetId } }));
56
+ }
57
+
58
+ export async function canBeMediaNote(tweetId) {
59
+ if (!tweetId) throw new Error("canBeMediaNote requires a tweetId");
60
+ return data(
61
+ await this.graphql("BirdwatchFetchCanTweetBeMediaNote", { variables: { tweetId } }),
62
+ );
63
+ }
64
+
65
+ export async function clusterData(tweetId) {
66
+ if (!tweetId) throw new Error("clusterData requires a tweetId");
67
+ return data(await this.graphql("BirdwatchFetchClusterData", { variables: { tweetId } }));
68
+ }
69
+
70
+ export async function mediaMatches(tweetId, opts = {}) {
71
+ if (!tweetId) throw new Error("mediaMatches requires a tweetId");
72
+ return data(
73
+ await this.graphql("BirdwatchFetchMediaMatchSlice", {
74
+ variables: { tweet_id: tweetId, cursor: opts.cursor, ...opts.variables },
75
+ }),
76
+ );
77
+ }
78
+
79
+ export async function prominentMediaMatches(tweetId, opts = {}) {
80
+ if (!tweetId) throw new Error("prominentMediaMatches requires a tweetId");
81
+ return data(
82
+ await this.graphql("BirdwatchFetchProminentMediaMatchSlice", {
83
+ variables: { tweet_id: tweetId, cursor: opts.cursor, ...opts.variables },
84
+ }),
85
+ );
86
+ }
87
+
88
+ export async function sourceLinks(tweetId, opts = {}) {
89
+ if (!tweetId) throw new Error("sourceLinks requires a tweetId");
90
+ return data(
91
+ await this.graphql("BirdwatchFetchSourceLinkSlice", {
92
+ variables: { tweet_id: tweetId, cursor: opts.cursor, ...opts.variables },
93
+ }),
94
+ );
95
+ }
96
+
97
+ export async function sourceLinkTweet(tweetId) {
98
+ if (!tweetId) throw new Error("sourceLinkTweet requires a tweetId");
99
+ return data(await this.graphql("BirdwatchFetchSourceLinkTweet", { variables: { tweetId } }));
100
+ }
101
+
102
+ export async function authenticatedMatch(noteId) {
103
+ if (!noteId) throw new Error("authenticatedMatch requires a noteId");
104
+ return data(
105
+ await this.graphql("BirdwatchFetchAuthenticatedBirdwatchMatchSlice", {
106
+ variables: { note_id: noteId },
107
+ }),
108
+ );
109
+ }
110
+
111
+ export async function aliasOptions() {
112
+ return data(await this.graphql("BirdwatchFetchAliasSelfSelectOptions", { variables: {} }));
113
+ }
114
+
115
+ export async function aliasStatus() {
116
+ const res = await this.graphql("BirdwatchFetchAliasSelfSelectStatus", { variables: {} });
117
+ return res?.data?.authenticated_user_birdwatch_alias_self_select_status ?? res;
118
+ }
119
+
120
+ export async function publicData() {
121
+ const res = await this.graphql("BirdwatchFetchPublicData", { variables: {} });
122
+ return res?.data?.birdwatch_latest_public_data_file_bundle ?? res;
123
+ }
124
+
125
+ export async function signUpEligibility() {
126
+ const res = await this.graphql("BirdwatchFetchSignUpEligiblity", { variables: {} });
127
+ return res?.data?.birdwatch_sign_up_eligibility ?? res;
128
+ }
129
+
130
+ export async function suggestionFeedback(suggestionId) {
131
+ if (!suggestionId) throw new Error("suggestionFeedback requires a suggestionId");
132
+ return data(
133
+ await this.graphql("BirdwatchFetchSuggestionFeedbackOverview", {
134
+ variables: { suggestionId },
135
+ }),
136
+ );
137
+ }
138
+
139
+ export async function create(tweetId, dataV1, opts = {}) {
140
+ if (!tweetId) throw new Error("create requires a tweetId");
141
+ if (!dataV1) throw new Error("create requires a data_v1 note payload");
142
+ return await this.graphql("BirdwatchCreateNote", {
143
+ body: { variables: { tweet_id: tweetId, data_v1: dataV1, ...opts.variables } },
144
+ });
145
+ }
146
+
147
+ export async function remove(noteId) {
148
+ if (!noteId) throw new Error("remove requires a noteId");
149
+ return await this.graphql("BirdwatchDeleteNote", { body: { variables: { note_id: noteId } } });
150
+ }
151
+
152
+ export async function rate(noteId, rating = {}) {
153
+ if (!noteId) throw new Error("rate requires a noteId");
154
+ return await this.graphql("BirdwatchCreateRating", {
155
+ body: { variables: { note_id: noteId, ...rating } },
156
+ });
157
+ }
158
+
159
+ export async function deleteRating(noteId) {
160
+ if (!noteId) throw new Error("deleteRating requires a noteId");
161
+ return await this.graphql("BirdwatchDeleteRating", {
162
+ body: { variables: { note_id: noteId } },
163
+ });
164
+ }
165
+
166
+ export async function appeal(noteId, opts = {}) {
167
+ if (!noteId) throw new Error("appeal requires a noteId");
168
+ return await this.graphql("BirdwatchCreateAppeal", {
169
+ body: { variables: { note_id: noteId, ...opts.variables } },
170
+ });
171
+ }
172
+
173
+ export async function request(tweetId) {
174
+ if (!tweetId) throw new Error("request requires a tweetId");
175
+ return await this.graphql("BirdwatchCreateBatSignal", {
176
+ body: { variables: { tweet_id: tweetId } },
177
+ });
178
+ }
179
+
180
+ export async function deleteRequest(tweetId) {
181
+ if (!tweetId) throw new Error("deleteRequest requires a tweetId");
182
+ return await this.graphql("BirdwatchDeleteBatSignal", {
183
+ body: { variables: { tweet_id: tweetId } },
184
+ });
185
+ }
186
+
187
+ export async function selectAlias(opts = {}) {
188
+ return await this.graphql("BirdwatchAliasSelect", { body: { variables: { ...opts.variables } } });
189
+ }
190
+
191
+ export async function editNotificationSettings(params = {}) {
192
+ return await this.graphql("BirdwatchEditNotificationSettings", {
193
+ body: { variables: { ...params } },
194
+ });
195
+ }
196
+
197
+ export async function editUserSettings(params = {}) {
198
+ return await this.graphql("BirdwatchEditUserSettings", { body: { variables: { ...params } } });
199
+ }
200
+
201
+ export async function acknowledgeEarnOut() {
202
+ return await this.graphql("BirdwatchProfileAcknowledgeEarnOut", { body: { variables: {} } });
203
+ }
204
+
205
+ export async function admitUser(opts = {}) {
206
+ return await this.graphql("BirdwatchAdmitUser", { body: { variables: { ...opts.variables } } });
207
+ }
208
+
209
+ export async function removeUser(opts = {}) {
210
+ return await this.graphql("BirdwatchRemoveUser", { body: { variables: { ...opts.variables } } });
211
+ }
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";
@@ -14,6 +15,12 @@ export default class Emusks {
14
15
  elevatedCookies = null;
15
16
  graphqlEndpoint = "web";
16
17
  transactionIds = undefined;
18
+ actingAs = null;
19
+
20
+ setActingAs(userId) {
21
+ this.actingAs = userId || null;
22
+ return this;
23
+ }
17
24
 
18
25
  async elevate(password) {
19
26
  if (!this.auth) throw new Error("must be logged in before calling elevate");
@@ -195,4 +202,5 @@ Emusks.prototype.graphql = graphql;
195
202
  Emusks.prototype.v1_1 = v1_1;
196
203
  Emusks.prototype.v2 = v2;
197
204
  Emusks.prototype.jf = jetfuel;
205
+ Emusks.prototype.grokApi = grokApi;
198
206
  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
+ }
package/src/v1.1.js CHANGED
@@ -2,7 +2,7 @@ import getCycleTLS from "./cycletls.js";
2
2
  import v1_1Api from "./static/v1.1.js";
3
3
 
4
4
  export default async function v1_1(queryName, { params, body, headers } = {}) {
5
- let entry = v1_1Api[queryName];
5
+ const entry = Array.isArray(queryName) ? queryName : v1_1Api[queryName];
6
6
  if (!entry) {
7
7
  throw new Error(`v1.1 endpoint ${queryName} not found`);
8
8
  }
@@ -62,6 +62,7 @@ export default async function v1_1(queryName, { params, body, headers } = {}) {
62
62
  "sec-gpc": "1",
63
63
  cookie:
64
64
  this.auth.client.headers.cookie + (this.elevatedCookies ? `; ${this.elevatedCookies}` : ""),
65
+ ...(this.actingAs ? { "x-act-as-user-id": this.actingAs } : {}),
65
66
  ...headers,
66
67
  };
67
68
 
@@ -95,7 +96,9 @@ export default async function v1_1(queryName, { params, body, headers } = {}) {
95
96
  const messages = bodyJson.errors
96
97
  .map((e) => e.message || (e.code != null ? `code ${e.code}` : "unknown"))
97
98
  .join("; ");
98
- throw new Error(`twitter v1.1 ${queryName} ${res.status}: ${messages}`);
99
+ throw new Error(
100
+ `twitter v1.1 ${Array.isArray(queryName) ? entry[1] : queryName} ${res.status}: ${messages}`,
101
+ );
99
102
  }
100
103
  return {
101
104
  status: res.status,
package/src/v2.js CHANGED
@@ -43,6 +43,7 @@ export default async function v2(queryName, { params, body, headers } = {}) {
43
43
  "sec-gpc": "1",
44
44
  cookie:
45
45
  this.auth.client.headers.cookie + (this.elevatedCookies ? `; ${this.elevatedCookies}` : ""),
46
+ ...(this.actingAs ? { "x-act-as-user-id": this.actingAs } : {}),
46
47
  ...headers,
47
48
  };
48
49