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