@the-convocation/twitter-scraper 0.17.2 → 0.18.0

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.
@@ -790,13 +790,16 @@ class ApiRequest {
790
790
  toRequestUrl() {
791
791
  const params = new URLSearchParams();
792
792
  if (this.variables) {
793
- params.set("variables", stringify(this.variables));
793
+ const variablesStr = stringify(this.variables);
794
+ if (variablesStr) params.set("variables", variablesStr);
794
795
  }
795
796
  if (this.features) {
796
- params.set("features", stringify(this.features));
797
+ const featuresStr = stringify(this.features);
798
+ if (featuresStr) params.set("features", featuresStr);
797
799
  }
798
800
  if (this.fieldToggles) {
799
- params.set("fieldToggles", stringify(this.fieldToggles));
801
+ const fieldTogglesStr = stringify(this.fieldToggles);
802
+ if (fieldTogglesStr) params.set("fieldToggles", fieldTogglesStr);
800
803
  }
801
804
  return `${this.url}?${params.toString()}`;
802
805
  }
@@ -1522,9 +1525,12 @@ async function getSearchTimeline(query, maxItems, searchMode, auth, cursor) {
1522
1525
  break;
1523
1526
  }
1524
1527
  const params = new URLSearchParams();
1525
- params.set("features", stringify(features));
1526
- params.set("fieldToggles", stringify(fieldToggles));
1527
- params.set("variables", stringify(variables));
1528
+ const featuresStr = stringify(features);
1529
+ const fieldTogglesStr = stringify(fieldToggles);
1530
+ const variablesStr = stringify(variables);
1531
+ if (featuresStr) params.set("features", featuresStr);
1532
+ if (fieldTogglesStr) params.set("fieldToggles", fieldTogglesStr);
1533
+ if (variablesStr) params.set("variables", variablesStr);
1528
1534
  const res = await requestApi(
1529
1535
  `https://api.x.com/graphql/gkjsKepM6gl_HmFWoWKfgg/SearchTimeline?${params.toString()}`,
1530
1536
  auth
@@ -1638,8 +1644,10 @@ async function getFollowingTimeline(userId, maxItems, auth, cursor) {
1638
1644
  variables["cursor"] = cursor;
1639
1645
  }
1640
1646
  const params = new URLSearchParams();
1641
- params.set("features", stringify(features));
1642
- params.set("variables", stringify(variables));
1647
+ const featuresStr = stringify(features);
1648
+ const variablesStr = stringify(variables);
1649
+ if (featuresStr) params.set("features", featuresStr);
1650
+ if (variablesStr) params.set("variables", variablesStr);
1643
1651
  const res = await requestApi(
1644
1652
  `https://x.com/i/api/graphql/iSicc7LrzWGBgDPL0tM_TQ/Following?${params.toString()}`,
1645
1653
  auth
@@ -1673,8 +1681,10 @@ async function getFollowersTimeline(userId, maxItems, auth, cursor) {
1673
1681
  variables["cursor"] = cursor;
1674
1682
  }
1675
1683
  const params = new URLSearchParams();
1676
- params.set("features", stringify(features));
1677
- params.set("variables", stringify(variables));
1684
+ const featuresStr = stringify(features);
1685
+ const variablesStr = stringify(variables);
1686
+ if (featuresStr) params.set("features", featuresStr);
1687
+ if (variablesStr) params.set("variables", variablesStr);
1678
1688
  const res = await requestApi(
1679
1689
  `https://x.com/i/api/graphql/rRXFSG5vR6drKr5M37YOTw/Followers?${params.toString()}`,
1680
1690
  auth
@@ -1950,6 +1960,155 @@ async function getTweetAnonymous(id, auth) {
1950
1960
  return parseTimelineEntryItemContentRaw(res.value.data, id);
1951
1961
  }
1952
1962
 
1963
+ async function* getDmConversationMessagesGenerator(conversationId, maxMessages, initialCursor, fetchFunc) {
1964
+ let nMessages = 0;
1965
+ let cursor = initialCursor;
1966
+ while (nMessages < maxMessages) {
1967
+ const batch = await fetchFunc(
1968
+ conversationId,
1969
+ maxMessages,
1970
+ cursor
1971
+ );
1972
+ const { conversation, next } = batch;
1973
+ if (!conversation?.entries || conversation?.entries?.length === 0) {
1974
+ break;
1975
+ }
1976
+ for (const entry of conversation.entries) {
1977
+ if (nMessages < maxMessages) {
1978
+ yield entry;
1979
+ nMessages++;
1980
+ } else {
1981
+ break;
1982
+ }
1983
+ }
1984
+ cursor = next;
1985
+ if (conversation.status === "AT_END" || !next) {
1986
+ break;
1987
+ }
1988
+ await jitter(1e3);
1989
+ }
1990
+ }
1991
+
1992
+ async function fetchDmInbox(auth) {
1993
+ if (!await auth.isLoggedIn()) {
1994
+ throw new AuthenticationError(
1995
+ "Scraper is not logged-in for fetching direct messages."
1996
+ );
1997
+ }
1998
+ const params = new URLSearchParams();
1999
+ addApiParams(params, false);
2000
+ params.set("nsfw_filtering_enabled", "false");
2001
+ params.set("filter_low_quality", "true");
2002
+ params.set("include_quality", "all");
2003
+ params.set("include_ext_profile_image_shape", "1");
2004
+ params.set("dm_secret_conversations_enabled", "false");
2005
+ params.set("krs_registration_enabled", "false");
2006
+ params.set("include_ext_limited_action_results", "true");
2007
+ params.set("dm_users", "true");
2008
+ params.set("include_groups", "true");
2009
+ params.set("include_inbox_timelines", "true");
2010
+ params.set("supports_reactions", "true");
2011
+ params.set("supports_edit", "true");
2012
+ params.set("include_ext_edit_control", "true");
2013
+ params.set("include_ext_business_affiliations_label", "true");
2014
+ params.set("include_ext_parody_commentary_fan_label", "true");
2015
+ params.set(
2016
+ "ext",
2017
+ "mediaColor,altText,mediaStats,highlightedLabel,parodyCommentaryFanLabel,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl,article"
2018
+ );
2019
+ const res = await requestApi(
2020
+ `https://x.com/i/api/1.1/dm/inbox_initial_state.json?${params.toString()}`,
2021
+ auth
2022
+ );
2023
+ if (!res.success) {
2024
+ throw res.err;
2025
+ }
2026
+ return parseDmInbox(res.value);
2027
+ }
2028
+ async function parseDmInbox(inbox) {
2029
+ return inbox.inbox_initial_state;
2030
+ }
2031
+ async function getDmInbox(auth) {
2032
+ return await fetchDmInbox(auth);
2033
+ }
2034
+ async function fetchDmConversation(conversationId, cursor, auth) {
2035
+ if (!await auth.isLoggedIn()) {
2036
+ throw new AuthenticationError(
2037
+ "Scraper is not logged-in for fetching direct messages."
2038
+ );
2039
+ }
2040
+ const params = new URLSearchParams();
2041
+ addApiParams(params, false);
2042
+ params.set("context", "FETCH_DM_CONVERSATION_HISTORY");
2043
+ params.set("include_ext_profile_image_shape", "1");
2044
+ params.set("dm_secret_conversations_enabled", "false");
2045
+ params.set("krs_registration_enabled", "false");
2046
+ params.set("include_ext_limited_action_results", "true");
2047
+ params.set("dm_users", "true");
2048
+ params.set("include_groups", "true");
2049
+ params.set("include_inbox_timelines", "true");
2050
+ params.set("supports_reactions", "true");
2051
+ params.set("supports_edit", "true");
2052
+ params.set("include_conversation_info", "true");
2053
+ params.set(
2054
+ "ext",
2055
+ "mediaColor,altText,mediaStats,highlightedLabel,parodyCommentaryFanLabel,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl,article"
2056
+ );
2057
+ if (cursor) {
2058
+ if (cursor.maxId) {
2059
+ params.set("max_id", cursor.maxId);
2060
+ }
2061
+ if (cursor.minId) {
2062
+ params.set("min_id", cursor.minId);
2063
+ }
2064
+ }
2065
+ const url = `https://x.com/i/api/1.1/dm/conversation/${conversationId}.json?${params.toString()}`;
2066
+ const res = await requestApi(url, auth);
2067
+ if (!res.success) {
2068
+ throw res.err;
2069
+ }
2070
+ return parseDmConversation(res.value);
2071
+ }
2072
+ async function parseDmConversation(conversation) {
2073
+ return conversation.conversation_timeline;
2074
+ }
2075
+ async function getDmConversation(conversationId, cursor, auth) {
2076
+ return await fetchDmConversation(conversationId, cursor, auth);
2077
+ }
2078
+ function getDmMessages(conversationId, maxMessages, cursor, auth) {
2079
+ return getDmConversationMessagesGenerator(
2080
+ conversationId,
2081
+ maxMessages,
2082
+ cursor,
2083
+ async (id, _max, cursor2) => {
2084
+ const conversation = await fetchDmConversation(id, cursor2, auth);
2085
+ let next = void 0;
2086
+ if (cursor2?.minId && conversation.max_entry_id) {
2087
+ next = { minId: conversation.max_entry_id };
2088
+ } else if (conversation.min_entry_id) {
2089
+ next = { maxId: conversation.min_entry_id };
2090
+ }
2091
+ return {
2092
+ conversation,
2093
+ next
2094
+ };
2095
+ }
2096
+ );
2097
+ }
2098
+ function findDmConversationsByUserId(inbox, userId) {
2099
+ const conversations = [];
2100
+ for (const conversationId in inbox.conversations) {
2101
+ const conversation = inbox.conversations[conversationId];
2102
+ const hasUser = conversation.participants.some(
2103
+ (participant) => participant.user_id === userId
2104
+ );
2105
+ if (hasUser) {
2106
+ conversations.push(conversation);
2107
+ }
2108
+ }
2109
+ return conversations;
2110
+ }
2111
+
1953
2112
  const twUrl = "https://x.com";
1954
2113
  class Scraper {
1955
2114
  /**
@@ -2214,6 +2373,45 @@ class Scraper {
2214
2373
  return getTweetAnonymous(id, this.auth);
2215
2374
  }
2216
2375
  }
2376
+ /**
2377
+ * Retrieves the direct message inbox for the authenticated user.
2378
+ *
2379
+ * @return A promise that resolves to an object representing the direct message inbox.
2380
+ */
2381
+ getDmInbox() {
2382
+ return getDmInbox(this.auth);
2383
+ }
2384
+ /**
2385
+ * Retrieves the direct message conversation for the specified conversation ID.
2386
+ *
2387
+ * @param conversationId - The unique identifier of the DM conversation to retrieve.
2388
+ * @param cursor - Use `maxId` to get messages before a message ID (older messages), or `minId` to get messages after a message ID (newer messages).
2389
+ * @return A promise that resolves to the timeline of the DM conversation.
2390
+ */
2391
+ getDmConversation(conversationId, cursor) {
2392
+ return getDmConversation(conversationId, cursor, this.auth);
2393
+ }
2394
+ /**
2395
+ * Retrieves direct messages from a specific conversation.
2396
+ *
2397
+ * @param conversationId - The unique identifier of the conversation to fetch messages from.
2398
+ * @param [maxMessages=20] - The maximum number of messages to retrieve per request.
2399
+ * @param cursor - Use `maxId` to get messages before a message ID (older messages), or `minId` to get messages after a message ID (newer messages).
2400
+ * @returns An {@link AsyncGenerator} of messages from the provided conversation.
2401
+ */
2402
+ getDmMessages(conversationId, maxMessages = 20, cursor) {
2403
+ return getDmMessages(conversationId, maxMessages, cursor, this.auth);
2404
+ }
2405
+ /**
2406
+ * Retrieves a list of direct message conversations for a specific user based on their user ID.
2407
+ *
2408
+ * @param inbox - The DM inbox containing all available conversations.
2409
+ * @param userId - The unique identifier of the user whose DM conversations are to be retrieved.
2410
+ * @return An array of DM conversations associated with the specified user ID.
2411
+ */
2412
+ findDmConversationsByUserId(inbox, userId) {
2413
+ return findDmConversationsByUserId(inbox, userId);
2414
+ }
2217
2415
  /**
2218
2416
  * Returns if the scraper has a guest token. The token may not be valid.
2219
2417
  * @returns `true` if the scraper has a guest token; otherwise `false`.