scrapebadger 0.3.0 → 0.4.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.
@@ -10,12 +10,26 @@ function createPaginatedResponse(data, cursor) {
10
10
  hasMore: !!cursor
11
11
  };
12
12
  }
13
+ var RATE_LIMIT_WARN_THRESHOLD = 0.2;
13
14
  async function* paginate(fetchPage, options = {}) {
14
15
  const { maxItems } = options;
15
16
  let cursor;
16
17
  let totalYielded = 0;
17
18
  do {
18
- const response = await fetchPage(cursor);
19
+ const { response, rateLimit } = await fetchPage(cursor);
20
+ if (rateLimit) {
21
+ const { limit, remaining, reset } = rateLimit;
22
+ if (limit > 0 && remaining / limit < RATE_LIMIT_WARN_THRESHOLD) {
23
+ const nowSec = Date.now() / 1e3;
24
+ const windowRemainingSec = Math.max(reset - nowSec, 1);
25
+ const delayMs = remaining > 0 ? windowRemainingSec / remaining * 1e3 : windowRemainingSec * 1e3;
26
+ const resetInSec = Math.round(windowRemainingSec);
27
+ console.warn(
28
+ `\x1B[33m\u26A0 ScrapeBadger: Rate limit: ${remaining}/${limit} remaining (resets in ${resetInSec}s), throttling pagination\x1B[0m`
29
+ );
30
+ await sleep(delayMs);
31
+ }
32
+ }
19
33
  for (const item of response.data) {
20
34
  yield item;
21
35
  totalYielded++;
@@ -26,6 +40,9 @@ async function* paginate(fetchPage, options = {}) {
26
40
  cursor = response.nextCursor;
27
41
  } while (cursor);
28
42
  }
43
+ function sleep(ms) {
44
+ return new Promise((resolve) => setTimeout(resolve, ms));
45
+ }
29
46
 
30
47
  // src/twitter/tweets.ts
31
48
  var TweetsClient = class {
@@ -223,7 +240,8 @@ var TweetsClient = class {
223
240
  */
224
241
  async *getQuotesAll(tweetId, options = {}) {
225
242
  const fetchPage = async (cursor) => {
226
- return this.getQuotes(tweetId, { ...options, cursor });
243
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/tweets/tweet/${tweetId}/quotes`, { params: { cursor } });
244
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
227
245
  };
228
246
  yield* paginate(fetchPage, options);
229
247
  }
@@ -290,7 +308,15 @@ var TweetsClient = class {
290
308
  */
291
309
  async *searchAll(query, options = {}) {
292
310
  const fetchPage = async (cursor) => {
293
- return this.search(query, { ...options, cursor });
311
+ const { data, rateLimit } = await this.client.requestWithHeaders("/v1/twitter/tweets/advanced_search", {
312
+ params: {
313
+ query,
314
+ query_type: options.queryType ?? "Top",
315
+ count: options.count,
316
+ cursor
317
+ }
318
+ });
319
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
294
320
  };
295
321
  yield* paginate(fetchPage, options);
296
322
  }
@@ -334,10 +360,64 @@ var TweetsClient = class {
334
360
  */
335
361
  async *getUserTweetsAll(username, options = {}) {
336
362
  const fetchPage = async (cursor) => {
337
- return this.getUserTweets(username, { ...options, cursor });
363
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/users/${username}/latest_tweets`, { params: { cursor } });
364
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
338
365
  };
339
366
  yield* paginate(fetchPage, options);
340
367
  }
368
+ /**
369
+ * Get the edit history of a tweet.
370
+ *
371
+ * @param tweetId - The tweet ID to get edit history for.
372
+ * @returns Paginated response containing tweet versions.
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * const history = await client.twitter.tweets.getEditHistory("1234567890");
377
+ * console.log(`${history.data.length} version(s) of this tweet`);
378
+ * ```
379
+ */
380
+ async getEditHistory(tweetId) {
381
+ const response = await this.client.request(
382
+ `/v1/twitter/tweets/tweet/${tweetId}/edit_history`
383
+ );
384
+ return createPaginatedResponse(response.data ?? [], void 0);
385
+ }
386
+ /**
387
+ * Get community notes (Birdwatch) attached to a tweet.
388
+ *
389
+ * @param tweetId - The tweet ID to get community notes for.
390
+ * @returns Paginated response containing community notes.
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const notes = await client.twitter.tweets.getCommunityNotes("1234567890");
395
+ * for (const note of notes.data) {
396
+ * console.log(note.text);
397
+ * }
398
+ * ```
399
+ */
400
+ async getCommunityNotes(tweetId) {
401
+ const response = await this.client.request(
402
+ `/v1/twitter/tweets/tweet/${tweetId}/community_notes`
403
+ );
404
+ return createPaginatedResponse(response.data ?? [], void 0);
405
+ }
406
+ /**
407
+ * Get a long-form article by its ID.
408
+ *
409
+ * @param articleId - The article ID to fetch.
410
+ * @returns The article data.
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const article = await client.twitter.tweets.getArticle("abc123");
415
+ * console.log(`${article.title}: ${article.text?.slice(0, 100)}...`);
416
+ * ```
417
+ */
418
+ async getArticle(articleId) {
419
+ return this.client.request(`/v1/twitter/tweets/article/${articleId}`);
420
+ }
341
421
  };
342
422
 
343
423
  // src/twitter/users.ts
@@ -444,7 +524,8 @@ var UsersClient = class {
444
524
  */
445
525
  async *getFollowersAll(username, options = {}) {
446
526
  const fetchPage = async (cursor) => {
447
- return this.getFollowers(username, { ...options, cursor });
527
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/users/${username}/followers`, { params: { cursor } });
528
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
448
529
  };
449
530
  yield* paginate(fetchPage, options);
450
531
  }
@@ -479,7 +560,8 @@ var UsersClient = class {
479
560
  */
480
561
  async *getFollowingAll(username, options = {}) {
481
562
  const fetchPage = async (cursor) => {
482
- return this.getFollowing(username, { ...options, cursor });
563
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/users/${username}/followings`, { params: { cursor } });
564
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
483
565
  };
484
566
  yield* paginate(fetchPage, options);
485
567
  }
@@ -642,10 +724,97 @@ var UsersClient = class {
642
724
  */
643
725
  async *searchAll(query, options = {}) {
644
726
  const fetchPage = async (cursor) => {
645
- return this.search(query, { ...options, cursor });
727
+ const { data, rateLimit } = await this.client.requestWithHeaders("/v1/twitter/users/search_users", { params: { query, cursor } });
728
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
646
729
  };
647
730
  yield* paginate(fetchPage, options);
648
731
  }
732
+ /**
733
+ * Get multiple users by their numeric IDs in a single request.
734
+ *
735
+ * @param userIds - List of user IDs to fetch.
736
+ * @returns Paginated response containing the matching users.
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * const users = await client.twitter.users.getByIds(["44196397", "783214"]);
741
+ * for (const user of users.data) {
742
+ * console.log(`@${user.username}`);
743
+ * }
744
+ * ```
745
+ */
746
+ async getByIds(userIds) {
747
+ const response = await this.client.request(
748
+ "/v1/twitter/users/batch_by_ids",
749
+ { params: { user_ids: userIds.join(",") } }
750
+ );
751
+ return createPaginatedResponse(response.data ?? [], void 0);
752
+ }
753
+ /**
754
+ * Get multiple users by their usernames in a single request.
755
+ *
756
+ * @param usernames - List of usernames (without @) to fetch.
757
+ * @returns Paginated response containing the matching users.
758
+ *
759
+ * @example
760
+ * ```typescript
761
+ * const users = await client.twitter.users.getByUsernames(["elonmusk", "twitter"]);
762
+ * for (const user of users.data) {
763
+ * console.log(`${user.name}: ${user.followers_count?.toLocaleString()} followers`);
764
+ * }
765
+ * ```
766
+ */
767
+ async getByUsernames(usernames) {
768
+ const response = await this.client.request(
769
+ "/v1/twitter/users/batch_by_usernames",
770
+ { params: { usernames: usernames.join(",") } }
771
+ );
772
+ return createPaginatedResponse(response.data ?? [], void 0);
773
+ }
774
+ /**
775
+ * Get tweets that mention a user.
776
+ *
777
+ * @param username - The user's username (without @).
778
+ * @param options - Pagination options with optional count.
779
+ * @returns Paginated response containing tweets mentioning the user.
780
+ *
781
+ * @example
782
+ * ```typescript
783
+ * const mentions = await client.twitter.users.getMentions("elonmusk");
784
+ * for (const tweet of mentions.data) {
785
+ * console.log(`@${tweet.username}: ${tweet.text.slice(0, 100)}...`);
786
+ * }
787
+ * ```
788
+ */
789
+ async getMentions(username, options = {}) {
790
+ const response = await this.client.request(
791
+ `/v1/twitter/users/${username}/mentions`,
792
+ { params: { count: options.count, cursor: options.cursor } }
793
+ );
794
+ return createPaginatedResponse(response.data ?? [], response.next_cursor);
795
+ }
796
+ /**
797
+ * Get long-form articles authored by a user.
798
+ *
799
+ * @param userId - The user's numeric ID.
800
+ * @param options - Pagination options with optional count.
801
+ * @returns Paginated response containing the user's articles as tweets.
802
+ *
803
+ * @example
804
+ * ```typescript
805
+ * const articles = await client.twitter.users.getArticles("44196397");
806
+ * for (const article of articles.data) {
807
+ * console.log(article.text?.slice(0, 100));
808
+ * }
809
+ * ```
810
+ */
811
+ async getArticles(userId, options = {}) {
812
+ const response = await this.client.request(
813
+ `/v1/twitter/users/${userId}/articles`,
814
+ { params: { count: options.count, cursor: options.cursor } }
815
+ );
816
+ return createPaginatedResponse(response.data ?? [], response.next_cursor);
817
+ }
649
818
  };
650
819
 
651
820
  // src/twitter/lists.ts
@@ -702,7 +871,8 @@ var ListsClient = class {
702
871
  */
703
872
  async *getTweetsAll(listId, options = {}) {
704
873
  const fetchPage = async (cursor) => {
705
- return this.getTweets(listId, { ...options, cursor });
874
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/lists/${listId}/tweets`, { params: { cursor } });
875
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
706
876
  };
707
877
  yield* paginate(fetchPage, options);
708
878
  }
@@ -737,7 +907,8 @@ var ListsClient = class {
737
907
  */
738
908
  async *getMembersAll(listId, options = {}) {
739
909
  const fetchPage = async (cursor) => {
740
- return this.getMembers(listId, { ...options, cursor });
910
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/lists/${listId}/members`, { params: { cursor } });
911
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
741
912
  };
742
913
  yield* paginate(fetchPage, options);
743
914
  }
@@ -790,6 +961,29 @@ var ListsClient = class {
790
961
  );
791
962
  return createPaginatedResponse(response.data ?? [], response.next_cursor);
792
963
  }
964
+ /**
965
+ * Search tweets within a specific list.
966
+ *
967
+ * @param listId - The list ID to search within.
968
+ * @param query - Search query string.
969
+ * @param options - Pagination options with optional count.
970
+ * @returns Paginated response containing matching tweets from the list.
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * const results = await client.twitter.lists.searchTweets("123456", "python");
975
+ * for (const tweet of results.data) {
976
+ * console.log(`@${tweet.username}: ${tweet.text.slice(0, 100)}...`);
977
+ * }
978
+ * ```
979
+ */
980
+ async searchTweets(listId, query, options = {}) {
981
+ const response = await this.client.request(
982
+ `/v1/twitter/lists/${listId}/search_tweets`,
983
+ { params: { query, count: options.count, cursor: options.cursor } }
984
+ );
985
+ return createPaginatedResponse(response.data ?? [], response.next_cursor);
986
+ }
793
987
  };
794
988
 
795
989
  // src/twitter/communities.ts
@@ -885,7 +1079,14 @@ var CommunitiesClient = class {
885
1079
  */
886
1080
  async *getTweetsAll(communityId, options = {}) {
887
1081
  const fetchPage = async (cursor) => {
888
- return this.getTweets(communityId, { ...options, cursor });
1082
+ const { data, rateLimit } = await this.client.requestWithHeaders(`/v1/twitter/communities/${communityId}/tweets`, {
1083
+ params: {
1084
+ tweet_type: options.tweetType ?? "Top",
1085
+ count: options.count ?? 40,
1086
+ cursor
1087
+ }
1088
+ });
1089
+ return { response: createPaginatedResponse(data.data ?? [], data.next_cursor), rateLimit };
889
1090
  };
890
1091
  yield* paginate(fetchPage, options);
891
1092
  }
@@ -1825,6 +2026,46 @@ function verifyWebhookSignature(secret, body, signatureHeader) {
1825
2026
  }
1826
2027
  }
1827
2028
 
2029
+ // src/twitter/spaces.ts
2030
+ var SpacesClient = class {
2031
+ client;
2032
+ constructor(client) {
2033
+ this.client = client;
2034
+ }
2035
+ /**
2036
+ * Get details for a specific Twitter Space.
2037
+ *
2038
+ * @param spaceId - The Space ID to fetch.
2039
+ * @returns The Space data.
2040
+ * @throws NotFoundError - If the Space doesn't exist.
2041
+ *
2042
+ * @example
2043
+ * ```typescript
2044
+ * const space = await client.twitter.spaces.getDetail("1eaKbrPPbPwKX");
2045
+ * console.log(`${space.title} — ${space.participant_count} participants`);
2046
+ * ```
2047
+ */
2048
+ async getDetail(spaceId) {
2049
+ return this.client.request(`/v1/twitter/spaces/${spaceId}`);
2050
+ }
2051
+ /**
2052
+ * Get details for a live video broadcast.
2053
+ *
2054
+ * @param broadcastId - The broadcast ID to fetch.
2055
+ * @returns The broadcast data.
2056
+ * @throws NotFoundError - If the broadcast doesn't exist.
2057
+ *
2058
+ * @example
2059
+ * ```typescript
2060
+ * const broadcast = await client.twitter.spaces.getBroadcast("broadcast123");
2061
+ * console.log(`${broadcast.title}: ${broadcast.total_viewers} viewers`);
2062
+ * ```
2063
+ */
2064
+ async getBroadcast(broadcastId) {
2065
+ return this.client.request(`/v1/twitter/spaces/broadcast/${broadcastId}`);
2066
+ }
2067
+ };
2068
+
1828
2069
  // src/twitter/client.ts
1829
2070
  var TwitterClient = class {
1830
2071
  /** Client for tweet operations */
@@ -1841,6 +2082,8 @@ var TwitterClient = class {
1841
2082
  geo;
1842
2083
  /** Client for real-time stream monitor management and WebSocket streaming */
1843
2084
  stream;
2085
+ /** Client for Twitter Spaces and live broadcast operations */
2086
+ spaces;
1844
2087
  /**
1845
2088
  * Create a new Twitter client.
1846
2089
  *
@@ -1854,9 +2097,10 @@ var TwitterClient = class {
1854
2097
  this.trends = new TrendsClient(client);
1855
2098
  this.geo = new GeoClient(client);
1856
2099
  this.stream = new StreamClient(client);
2100
+ this.spaces = new SpacesClient(client);
1857
2101
  }
1858
2102
  };
1859
2103
 
1860
- export { CommunitiesClient, GeoClient, ListsClient, StreamClient, TrendsClient, TweetsClient, TwitterClient, UsersClient, verifyWebhookSignature };
2104
+ export { CommunitiesClient, GeoClient, ListsClient, SpacesClient, StreamClient, TrendsClient, TweetsClient, TwitterClient, UsersClient, verifyWebhookSignature };
1861
2105
  //# sourceMappingURL=index.mjs.map
1862
2106
  //# sourceMappingURL=index.mjs.map