@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.
@@ -812,13 +812,16 @@ class ApiRequest {
812
812
  toRequestUrl() {
813
813
  const params = new URLSearchParams();
814
814
  if (this.variables) {
815
- params.set("variables", stringify(this.variables));
815
+ const variablesStr = stringify(this.variables);
816
+ if (variablesStr) params.set("variables", variablesStr);
816
817
  }
817
818
  if (this.features) {
818
- params.set("features", stringify(this.features));
819
+ const featuresStr = stringify(this.features);
820
+ if (featuresStr) params.set("features", featuresStr);
819
821
  }
820
822
  if (this.fieldToggles) {
821
- params.set("fieldToggles", stringify(this.fieldToggles));
823
+ const fieldTogglesStr = stringify(this.fieldToggles);
824
+ if (fieldTogglesStr) params.set("fieldToggles", fieldTogglesStr);
822
825
  }
823
826
  return `${this.url}?${params.toString()}`;
824
827
  }
@@ -1544,9 +1547,12 @@ async function getSearchTimeline(query, maxItems, searchMode, auth, cursor) {
1544
1547
  break;
1545
1548
  }
1546
1549
  const params = new URLSearchParams();
1547
- params.set("features", stringify(features));
1548
- params.set("fieldToggles", stringify(fieldToggles));
1549
- params.set("variables", stringify(variables));
1550
+ const featuresStr = stringify(features);
1551
+ const fieldTogglesStr = stringify(fieldToggles);
1552
+ const variablesStr = stringify(variables);
1553
+ if (featuresStr) params.set("features", featuresStr);
1554
+ if (fieldTogglesStr) params.set("fieldToggles", fieldTogglesStr);
1555
+ if (variablesStr) params.set("variables", variablesStr);
1550
1556
  const res = await requestApi(
1551
1557
  `https://api.x.com/graphql/gkjsKepM6gl_HmFWoWKfgg/SearchTimeline?${params.toString()}`,
1552
1558
  auth
@@ -1660,8 +1666,10 @@ async function getFollowingTimeline(userId, maxItems, auth, cursor) {
1660
1666
  variables["cursor"] = cursor;
1661
1667
  }
1662
1668
  const params = new URLSearchParams();
1663
- params.set("features", stringify(features));
1664
- params.set("variables", stringify(variables));
1669
+ const featuresStr = stringify(features);
1670
+ const variablesStr = stringify(variables);
1671
+ if (featuresStr) params.set("features", featuresStr);
1672
+ if (variablesStr) params.set("variables", variablesStr);
1665
1673
  const res = await requestApi(
1666
1674
  `https://x.com/i/api/graphql/iSicc7LrzWGBgDPL0tM_TQ/Following?${params.toString()}`,
1667
1675
  auth
@@ -1695,8 +1703,10 @@ async function getFollowersTimeline(userId, maxItems, auth, cursor) {
1695
1703
  variables["cursor"] = cursor;
1696
1704
  }
1697
1705
  const params = new URLSearchParams();
1698
- params.set("features", stringify(features));
1699
- params.set("variables", stringify(variables));
1706
+ const featuresStr = stringify(features);
1707
+ const variablesStr = stringify(variables);
1708
+ if (featuresStr) params.set("features", featuresStr);
1709
+ if (variablesStr) params.set("variables", variablesStr);
1700
1710
  const res = await requestApi(
1701
1711
  `https://x.com/i/api/graphql/rRXFSG5vR6drKr5M37YOTw/Followers?${params.toString()}`,
1702
1712
  auth
@@ -1972,6 +1982,155 @@ async function getTweetAnonymous(id, auth) {
1972
1982
  return parseTimelineEntryItemContentRaw(res.value.data, id);
1973
1983
  }
1974
1984
 
1985
+ async function* getDmConversationMessagesGenerator(conversationId, maxMessages, initialCursor, fetchFunc) {
1986
+ let nMessages = 0;
1987
+ let cursor = initialCursor;
1988
+ while (nMessages < maxMessages) {
1989
+ const batch = await fetchFunc(
1990
+ conversationId,
1991
+ maxMessages,
1992
+ cursor
1993
+ );
1994
+ const { conversation, next } = batch;
1995
+ if (!conversation?.entries || conversation?.entries?.length === 0) {
1996
+ break;
1997
+ }
1998
+ for (const entry of conversation.entries) {
1999
+ if (nMessages < maxMessages) {
2000
+ yield entry;
2001
+ nMessages++;
2002
+ } else {
2003
+ break;
2004
+ }
2005
+ }
2006
+ cursor = next;
2007
+ if (conversation.status === "AT_END" || !next) {
2008
+ break;
2009
+ }
2010
+ await jitter(1e3);
2011
+ }
2012
+ }
2013
+
2014
+ async function fetchDmInbox(auth) {
2015
+ if (!await auth.isLoggedIn()) {
2016
+ throw new AuthenticationError(
2017
+ "Scraper is not logged-in for fetching direct messages."
2018
+ );
2019
+ }
2020
+ const params = new URLSearchParams();
2021
+ addApiParams(params, false);
2022
+ params.set("nsfw_filtering_enabled", "false");
2023
+ params.set("filter_low_quality", "true");
2024
+ params.set("include_quality", "all");
2025
+ params.set("include_ext_profile_image_shape", "1");
2026
+ params.set("dm_secret_conversations_enabled", "false");
2027
+ params.set("krs_registration_enabled", "false");
2028
+ params.set("include_ext_limited_action_results", "true");
2029
+ params.set("dm_users", "true");
2030
+ params.set("include_groups", "true");
2031
+ params.set("include_inbox_timelines", "true");
2032
+ params.set("supports_reactions", "true");
2033
+ params.set("supports_edit", "true");
2034
+ params.set("include_ext_edit_control", "true");
2035
+ params.set("include_ext_business_affiliations_label", "true");
2036
+ params.set("include_ext_parody_commentary_fan_label", "true");
2037
+ params.set(
2038
+ "ext",
2039
+ "mediaColor,altText,mediaStats,highlightedLabel,parodyCommentaryFanLabel,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl,article"
2040
+ );
2041
+ const res = await requestApi(
2042
+ `https://x.com/i/api/1.1/dm/inbox_initial_state.json?${params.toString()}`,
2043
+ auth
2044
+ );
2045
+ if (!res.success) {
2046
+ throw res.err;
2047
+ }
2048
+ return parseDmInbox(res.value);
2049
+ }
2050
+ async function parseDmInbox(inbox) {
2051
+ return inbox.inbox_initial_state;
2052
+ }
2053
+ async function getDmInbox(auth) {
2054
+ return await fetchDmInbox(auth);
2055
+ }
2056
+ async function fetchDmConversation(conversationId, cursor, auth) {
2057
+ if (!await auth.isLoggedIn()) {
2058
+ throw new AuthenticationError(
2059
+ "Scraper is not logged-in for fetching direct messages."
2060
+ );
2061
+ }
2062
+ const params = new URLSearchParams();
2063
+ addApiParams(params, false);
2064
+ params.set("context", "FETCH_DM_CONVERSATION_HISTORY");
2065
+ params.set("include_ext_profile_image_shape", "1");
2066
+ params.set("dm_secret_conversations_enabled", "false");
2067
+ params.set("krs_registration_enabled", "false");
2068
+ params.set("include_ext_limited_action_results", "true");
2069
+ params.set("dm_users", "true");
2070
+ params.set("include_groups", "true");
2071
+ params.set("include_inbox_timelines", "true");
2072
+ params.set("supports_reactions", "true");
2073
+ params.set("supports_edit", "true");
2074
+ params.set("include_conversation_info", "true");
2075
+ params.set(
2076
+ "ext",
2077
+ "mediaColor,altText,mediaStats,highlightedLabel,parodyCommentaryFanLabel,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl,article"
2078
+ );
2079
+ if (cursor) {
2080
+ if (cursor.maxId) {
2081
+ params.set("max_id", cursor.maxId);
2082
+ }
2083
+ if (cursor.minId) {
2084
+ params.set("min_id", cursor.minId);
2085
+ }
2086
+ }
2087
+ const url = `https://x.com/i/api/1.1/dm/conversation/${conversationId}.json?${params.toString()}`;
2088
+ const res = await requestApi(url, auth);
2089
+ if (!res.success) {
2090
+ throw res.err;
2091
+ }
2092
+ return parseDmConversation(res.value);
2093
+ }
2094
+ async function parseDmConversation(conversation) {
2095
+ return conversation.conversation_timeline;
2096
+ }
2097
+ async function getDmConversation(conversationId, cursor, auth) {
2098
+ return await fetchDmConversation(conversationId, cursor, auth);
2099
+ }
2100
+ function getDmMessages(conversationId, maxMessages, cursor, auth) {
2101
+ return getDmConversationMessagesGenerator(
2102
+ conversationId,
2103
+ maxMessages,
2104
+ cursor,
2105
+ async (id, _max, cursor2) => {
2106
+ const conversation = await fetchDmConversation(id, cursor2, auth);
2107
+ let next = void 0;
2108
+ if (cursor2?.minId && conversation.max_entry_id) {
2109
+ next = { minId: conversation.max_entry_id };
2110
+ } else if (conversation.min_entry_id) {
2111
+ next = { maxId: conversation.min_entry_id };
2112
+ }
2113
+ return {
2114
+ conversation,
2115
+ next
2116
+ };
2117
+ }
2118
+ );
2119
+ }
2120
+ function findDmConversationsByUserId(inbox, userId) {
2121
+ const conversations = [];
2122
+ for (const conversationId in inbox.conversations) {
2123
+ const conversation = inbox.conversations[conversationId];
2124
+ const hasUser = conversation.participants.some(
2125
+ (participant) => participant.user_id === userId
2126
+ );
2127
+ if (hasUser) {
2128
+ conversations.push(conversation);
2129
+ }
2130
+ }
2131
+ return conversations;
2132
+ }
2133
+
1975
2134
  const twUrl = "https://x.com";
1976
2135
  class Scraper {
1977
2136
  /**
@@ -2236,6 +2395,45 @@ class Scraper {
2236
2395
  return getTweetAnonymous(id, this.auth);
2237
2396
  }
2238
2397
  }
2398
+ /**
2399
+ * Retrieves the direct message inbox for the authenticated user.
2400
+ *
2401
+ * @return A promise that resolves to an object representing the direct message inbox.
2402
+ */
2403
+ getDmInbox() {
2404
+ return getDmInbox(this.auth);
2405
+ }
2406
+ /**
2407
+ * Retrieves the direct message conversation for the specified conversation ID.
2408
+ *
2409
+ * @param conversationId - The unique identifier of the DM conversation to retrieve.
2410
+ * @param cursor - Use `maxId` to get messages before a message ID (older messages), or `minId` to get messages after a message ID (newer messages).
2411
+ * @return A promise that resolves to the timeline of the DM conversation.
2412
+ */
2413
+ getDmConversation(conversationId, cursor) {
2414
+ return getDmConversation(conversationId, cursor, this.auth);
2415
+ }
2416
+ /**
2417
+ * Retrieves direct messages from a specific conversation.
2418
+ *
2419
+ * @param conversationId - The unique identifier of the conversation to fetch messages from.
2420
+ * @param [maxMessages=20] - The maximum number of messages to retrieve per request.
2421
+ * @param cursor - Use `maxId` to get messages before a message ID (older messages), or `minId` to get messages after a message ID (newer messages).
2422
+ * @returns An {@link AsyncGenerator} of messages from the provided conversation.
2423
+ */
2424
+ getDmMessages(conversationId, maxMessages = 20, cursor) {
2425
+ return getDmMessages(conversationId, maxMessages, cursor, this.auth);
2426
+ }
2427
+ /**
2428
+ * Retrieves a list of direct message conversations for a specific user based on their user ID.
2429
+ *
2430
+ * @param inbox - The DM inbox containing all available conversations.
2431
+ * @param userId - The unique identifier of the user whose DM conversations are to be retrieved.
2432
+ * @return An array of DM conversations associated with the specified user ID.
2433
+ */
2434
+ findDmConversationsByUserId(inbox, userId) {
2435
+ return findDmConversationsByUserId(inbox, userId);
2436
+ }
2239
2437
  /**
2240
2438
  * Returns if the scraper has a guest token. The token may not be valid.
2241
2439
  * @returns `true` if the scraper has a guest token; otherwise `false`.