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