@elizaos/plugin-twitter 1.0.13 → 1.0.14

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.
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  Role,
12
12
  Service,
13
13
  createUniqueUuid as createUniqueUuid6,
14
- logger as logger6
14
+ logger as logger7
15
15
  } from "@elizaos/core";
16
16
 
17
17
  // src/base.ts
@@ -21,154 +21,6 @@ import {
21
21
  logger
22
22
  } from "@elizaos/core";
23
23
 
24
- // src/client/api.ts
25
- import { Headers } from "headers-polyfill";
26
-
27
- // src/client/errors.ts
28
- var ApiError = class _ApiError extends Error {
29
- /**
30
- * Constructor for creating a new instance of the class.
31
- *
32
- * @param response The response object.
33
- * @param data The data object.
34
- * @param message The message string.
35
- */
36
- constructor(response, data, message) {
37
- super(message);
38
- this.response = response;
39
- this.data = data;
40
- }
41
- /**
42
- * Creates an instance of ApiError based on a Response object.
43
- *
44
- * @param {Response} response The Response object to parse.
45
- * @returns {Promise<ApiError>} A new instance of ApiError with the parsed data and status.
46
- */
47
- static async fromResponse(response) {
48
- let data = void 0;
49
- try {
50
- data = await response.json();
51
- } catch {
52
- try {
53
- data = await response.text();
54
- } catch {
55
- }
56
- }
57
- return new _ApiError(response, data, `Response status: ${response.status}`);
58
- }
59
- };
60
-
61
- // src/client/api.ts
62
- async function requestApi(url, auth, method = "GET", body) {
63
- const headers = new Headers({
64
- "Content-Type": "application/json"
65
- });
66
- let res;
67
- do {
68
- try {
69
- res = await fetch(url, {
70
- method,
71
- headers,
72
- credentials: "include",
73
- ...body && { body: JSON.stringify(body) }
74
- });
75
- } catch (err) {
76
- if (!(err instanceof Error)) {
77
- throw err;
78
- }
79
- return {
80
- success: false,
81
- err: new Error("Failed to perform request.")
82
- };
83
- }
84
- if (res.status === 429) {
85
- const xRateLimitRemaining = res.headers.get("x-rate-limit-remaining");
86
- const xRateLimitReset = res.headers.get("x-rate-limit-reset");
87
- if (xRateLimitRemaining === "0" && xRateLimitReset) {
88
- const currentTime = (/* @__PURE__ */ new Date()).valueOf() / 1e3;
89
- const timeDeltaMs = 1e3 * (Number.parseInt(xRateLimitReset) - currentTime);
90
- await new Promise((resolve) => setTimeout(resolve, timeDeltaMs));
91
- }
92
- }
93
- } while (res.status === 429);
94
- if (!res.ok) {
95
- return {
96
- success: false,
97
- err: await ApiError.fromResponse(res)
98
- };
99
- }
100
- const transferEncoding = res.headers.get("transfer-encoding");
101
- if (transferEncoding === "chunked") {
102
- const reader = typeof res.body?.getReader === "function" ? res.body.getReader() : null;
103
- if (!reader) {
104
- try {
105
- const text = await res.text();
106
- try {
107
- const value = JSON.parse(text);
108
- return { success: true, value };
109
- } catch (_e) {
110
- return { success: true, value: { text } };
111
- }
112
- } catch (_e) {
113
- return {
114
- success: false,
115
- err: new Error("No readable stream available and cant parse")
116
- };
117
- }
118
- }
119
- let chunks = "";
120
- while (true) {
121
- const { done, value } = await reader.read();
122
- if (done) break;
123
- chunks += new TextDecoder().decode(value);
124
- }
125
- try {
126
- const value = JSON.parse(chunks);
127
- return { success: true, value };
128
- } catch (_e) {
129
- return { success: true, value: { text: chunks } };
130
- }
131
- }
132
- const contentType = res.headers.get("content-type");
133
- if (contentType?.includes("application/json")) {
134
- const value = await res.json();
135
- return { success: true, value };
136
- }
137
- return { success: true, value: {} };
138
- }
139
- function addApiFeatures(o) {
140
- return {
141
- ...o,
142
- rweb_lists_timeline_redesign_enabled: true,
143
- responsive_web_graphql_exclude_directive_enabled: true,
144
- verified_phone_label_enabled: false,
145
- creator_subscriptions_tweet_preview_api_enabled: true,
146
- responsive_web_graphql_timeline_navigation_enabled: true,
147
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
148
- tweetypie_unmention_optimization_enabled: true,
149
- responsive_web_edit_tweet_api_enabled: true,
150
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
151
- view_counts_everywhere_api_enabled: true,
152
- longform_notetweets_consumption_enabled: true,
153
- tweet_awards_web_tipping_enabled: false,
154
- freedom_of_speech_not_reach_fetch_enabled: true,
155
- standardized_nudges_misinfo: true,
156
- longform_notetweets_rich_text_read_enabled: true,
157
- responsive_web_enhance_cards_enabled: false,
158
- subscriptions_verification_info_enabled: true,
159
- subscriptions_verification_info_reason_enabled: true,
160
- subscriptions_verification_info_verified_since_enabled: true,
161
- super_follow_badge_privacy_enabled: false,
162
- super_follow_exclusive_tweet_notifications_enabled: false,
163
- super_follow_tweet_api_enabled: false,
164
- super_follow_user_api_enabled: false,
165
- android_graphql_skip_api_media_color_palette: false,
166
- creator_subscriptions_subscription_count_enabled: false,
167
- blue_business_profile_image_shape_enabled: false,
168
- unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: false
169
- };
170
- }
171
-
172
24
  // src/client/auth.ts
173
25
  import { TwitterApi } from "twitter-api-v2";
174
26
  var TwitterAuth = class {
@@ -273,172 +125,110 @@ var TwitterAuth = class {
273
125
  };
274
126
 
275
127
  // src/client/profile.ts
276
- import stringify from "json-stable-stringify";
277
128
  function getAvatarOriginalSizeUrl(avatarUrl) {
278
129
  return avatarUrl ? avatarUrl.replace("_normal", "") : void 0;
279
130
  }
280
- function parseProfile(user, isBlueVerified) {
131
+ function parseV2Profile(user) {
281
132
  const profile = {
282
- avatar: getAvatarOriginalSizeUrl(user.profile_image_url_https),
283
- banner: user.profile_banner_url,
133
+ avatar: getAvatarOriginalSizeUrl(user.profile_image_url),
284
134
  biography: user.description,
285
- followersCount: user.followers_count,
286
- followingCount: user.friends_count,
287
- friendsCount: user.friends_count,
288
- mediaCount: user.media_count,
135
+ followersCount: user.public_metrics?.followers_count,
136
+ followingCount: user.public_metrics?.following_count,
137
+ friendsCount: user.public_metrics?.following_count,
138
+ tweetsCount: user.public_metrics?.tweet_count,
289
139
  isPrivate: user.protected ?? false,
290
- isVerified: user.verified,
291
- likesCount: user.favourites_count,
292
- listedCount: user.listed_count,
293
- location: user.location,
140
+ isVerified: user.verified ?? false,
141
+ likesCount: user.public_metrics?.like_count,
142
+ listedCount: user.public_metrics?.listed_count,
143
+ location: user.location || "",
294
144
  name: user.name,
295
- pinnedTweetIds: user.pinned_tweet_ids_str,
296
- tweetsCount: user.statuses_count,
297
- url: `https://twitter.com/${user.screen_name}`,
298
- userId: user.id_str,
299
- username: user.screen_name,
300
- isBlueVerified: isBlueVerified ?? false,
301
- canDm: user.can_dm
145
+ pinnedTweetIds: user.pinned_tweet_id ? [user.pinned_tweet_id] : [],
146
+ url: `https://twitter.com/${user.username}`,
147
+ userId: user.id,
148
+ username: user.username,
149
+ isBlueVerified: user.verified_type === "blue"
302
150
  };
303
- if (user.created_at != null) {
304
- profile.joined = new Date(Date.parse(user.created_at));
151
+ if (user.created_at) {
152
+ profile.joined = new Date(user.created_at);
305
153
  }
306
- const urls = user.entities?.url?.urls;
307
- if (urls?.length != null && urls?.length > 0) {
308
- profile.website = urls[0].expanded_url;
154
+ if (user.entities?.url?.urls?.length > 0) {
155
+ profile.website = user.entities.url.urls[0].expanded_url;
309
156
  }
310
157
  return profile;
311
158
  }
312
159
  async function getProfile(username, auth) {
313
- const params = new URLSearchParams();
314
- params.set(
315
- "variables",
316
- stringify({
317
- screen_name: username,
318
- withSafetyModeUserFields: true
319
- }) ?? ""
320
- );
321
- params.set(
322
- "features",
323
- stringify({
324
- hidden_profile_likes_enabled: false,
325
- hidden_profile_subscriptions_enabled: false,
326
- // Auth-restricted
327
- responsive_web_graphql_exclude_directive_enabled: true,
328
- verified_phone_label_enabled: false,
329
- subscriptions_verification_info_is_identity_verified_enabled: false,
330
- subscriptions_verification_info_verified_since_enabled: true,
331
- highlights_tweets_tab_ui_enabled: true,
332
- creator_subscriptions_tweet_preview_api_enabled: true,
333
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
334
- responsive_web_graphql_timeline_navigation_enabled: true
335
- }) ?? ""
336
- );
337
- params.set(
338
- "fieldToggles",
339
- stringify({ withAuxiliaryUserLabels: false }) ?? ""
340
- );
341
- const res = await requestApi(
342
- `https://twitter.com/i/api/graphql/G3KGOASz96M-Qu0nwmGXNg/UserByScreenName?${params.toString()}`,
343
- auth
344
- );
345
- if (!res.success) {
346
- return res;
347
- }
348
- const { value } = res;
349
- const { errors } = value;
350
- if (errors != null && errors.length > 0) {
351
- return {
352
- success: false,
353
- err: new Error(errors[0].message)
354
- };
355
- }
356
- if (!value.data || !value.data.user || !value.data.user.result) {
160
+ if (!auth) {
357
161
  return {
358
162
  success: false,
359
- err: new Error("User not found.")
163
+ err: new Error("Not authenticated")
360
164
  };
361
165
  }
362
- const { result: user } = value.data.user;
363
- const { legacy } = user;
364
- if (user.rest_id == null || user.rest_id.length === 0) {
166
+ try {
167
+ const client = auth.getV2Client();
168
+ const user = await client.v2.userByUsername(username, {
169
+ "user.fields": [
170
+ "id",
171
+ "name",
172
+ "username",
173
+ "created_at",
174
+ "description",
175
+ "entities",
176
+ "location",
177
+ "pinned_tweet_id",
178
+ "profile_image_url",
179
+ "protected",
180
+ "public_metrics",
181
+ "url",
182
+ "verified",
183
+ "verified_type"
184
+ ]
185
+ });
186
+ if (!user.data) {
187
+ return {
188
+ success: false,
189
+ err: new Error(`User ${username} not found`)
190
+ };
191
+ }
365
192
  return {
366
- success: false,
367
- err: new Error("rest_id not found.")
193
+ success: true,
194
+ value: parseV2Profile(user.data)
368
195
  };
369
- }
370
- legacy.id_str = user.rest_id;
371
- if (legacy.screen_name == null || legacy.screen_name.length === 0) {
196
+ } catch (error) {
372
197
  return {
373
198
  success: false,
374
- err: new Error(`Either ${username} does not exist or is private.`)
199
+ err: new Error(error.message || "Failed to fetch profile")
375
200
  };
376
201
  }
377
- return {
378
- success: true,
379
- value: parseProfile(user.legacy, user.is_blue_verified)
380
- };
381
202
  }
382
203
  var idCache = /* @__PURE__ */ new Map();
383
204
  async function getScreenNameByUserId(userId, auth) {
384
- const params = new URLSearchParams();
385
- params.set(
386
- "variables",
387
- stringify({
388
- userId,
389
- withSafetyModeUserFields: true
390
- }) ?? ""
391
- );
392
- params.set(
393
- "features",
394
- stringify({
395
- hidden_profile_subscriptions_enabled: true,
396
- rweb_tipjar_consumption_enabled: true,
397
- responsive_web_graphql_exclude_directive_enabled: true,
398
- verified_phone_label_enabled: false,
399
- highlights_tweets_tab_ui_enabled: true,
400
- responsive_web_twitter_article_notes_tab_enabled: true,
401
- subscriptions_feature_can_gift_premium: false,
402
- creator_subscriptions_tweet_preview_api_enabled: true,
403
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
404
- responsive_web_graphql_timeline_navigation_enabled: true
405
- }) ?? ""
406
- );
407
- const res = await requestApi(
408
- `https://twitter.com/i/api/graphql/xf3jd90KKBCUxdlI_tNHZw/UserByRestId?${params.toString()}`,
409
- auth
410
- );
411
- if (!res.success) {
412
- return res;
413
- }
414
- const { value } = res;
415
- const { errors } = value;
416
- if (errors != null && errors.length > 0) {
205
+ if (!auth) {
417
206
  return {
418
207
  success: false,
419
- err: new Error(errors[0].message)
208
+ err: new Error("Not authenticated")
420
209
  };
421
210
  }
422
- if (!value.data || !value.data.user || !value.data.user.result) {
211
+ try {
212
+ const client = auth.getV2Client();
213
+ const user = await client.v2.user(userId, {
214
+ "user.fields": ["username"]
215
+ });
216
+ if (!user.data || !user.data.username) {
217
+ return {
218
+ success: false,
219
+ err: new Error(`User with ID ${userId} not found`)
220
+ };
221
+ }
423
222
  return {
424
- success: false,
425
- err: new Error("User not found.")
223
+ success: true,
224
+ value: user.data.username
426
225
  };
427
- }
428
- const { result: user } = value.data.user;
429
- const { legacy } = user;
430
- if (legacy.screen_name == null || legacy.screen_name.length === 0) {
226
+ } catch (error) {
431
227
  return {
432
228
  success: false,
433
- err: new Error(
434
- `Either user with ID ${userId} does not exist or is private.`
435
- )
229
+ err: new Error(error.message || "Failed to fetch user")
436
230
  };
437
231
  }
438
- return {
439
- success: true,
440
- value: legacy.screen_name
441
- };
442
232
  }
443
233
  async function getEntityIdByScreenName(screenName, auth) {
444
234
  const cached = idCache.get(screenName);
@@ -464,202 +254,215 @@ async function getEntityIdByScreenName(screenName, auth) {
464
254
  }
465
255
 
466
256
  // src/client/relationships.ts
467
- import stringify2 from "json-stable-stringify";
468
-
469
- // src/client/timeline-async.ts
470
- async function* getUserTimeline(query, maxProfiles, fetchFunc) {
471
- let nProfiles = 0;
472
- let cursor = void 0;
473
- let consecutiveEmptyBatches = 0;
474
- while (nProfiles < maxProfiles) {
475
- const batch = await fetchFunc(
476
- query,
477
- maxProfiles,
478
- cursor
479
- );
480
- const { profiles, next } = batch;
481
- cursor = next;
482
- if (profiles.length === 0) {
483
- consecutiveEmptyBatches++;
484
- if (consecutiveEmptyBatches > 5) break;
485
- } else consecutiveEmptyBatches = 0;
486
- for (const profile of profiles) {
487
- if (nProfiles < maxProfiles) yield profile;
488
- else break;
489
- nProfiles++;
490
- }
491
- if (!next) break;
492
- }
257
+ import { Headers } from "headers-polyfill";
258
+ function parseV2UserToProfile(user) {
259
+ return {
260
+ avatar: user.profile_image_url?.replace("_normal", ""),
261
+ biography: user.description,
262
+ followersCount: user.public_metrics?.followers_count,
263
+ followingCount: user.public_metrics?.following_count,
264
+ friendsCount: user.public_metrics?.following_count,
265
+ tweetsCount: user.public_metrics?.tweet_count,
266
+ isPrivate: user.protected ?? false,
267
+ isVerified: user.verified ?? false,
268
+ likesCount: user.public_metrics?.like_count,
269
+ listedCount: user.public_metrics?.listed_count,
270
+ location: user.location || "",
271
+ name: user.name,
272
+ pinnedTweetIds: user.pinned_tweet_id ? [user.pinned_tweet_id] : [],
273
+ url: `https://twitter.com/${user.username}`,
274
+ userId: user.id,
275
+ username: user.username,
276
+ isBlueVerified: user.verified_type === "blue",
277
+ joined: user.created_at ? new Date(user.created_at) : void 0,
278
+ website: user.entities?.url?.urls?.[0]?.expanded_url
279
+ };
493
280
  }
494
- async function* getTweetTimeline(query, maxTweets, fetchFunc) {
495
- let nTweets = 0;
496
- let cursor = void 0;
497
- while (nTweets < maxTweets) {
498
- const batch = await fetchFunc(
499
- query,
500
- maxTweets,
501
- cursor
502
- );
503
- const { tweets, next } = batch;
504
- if (tweets.length === 0) {
505
- break;
506
- }
507
- for (const tweet of tweets) {
508
- if (nTweets < maxTweets) {
509
- cursor = next;
510
- yield tweet;
511
- } else {
281
+ async function* getFollowing(userId, maxProfiles, auth) {
282
+ if (!auth) {
283
+ throw new Error("Not authenticated");
284
+ }
285
+ const client = auth.getV2Client();
286
+ let count = 0;
287
+ let paginationToken;
288
+ try {
289
+ while (count < maxProfiles) {
290
+ const response = await client.v2.following(userId, {
291
+ max_results: Math.min(maxProfiles - count, 100),
292
+ pagination_token: paginationToken,
293
+ "user.fields": [
294
+ "id",
295
+ "name",
296
+ "username",
297
+ "created_at",
298
+ "description",
299
+ "entities",
300
+ "location",
301
+ "pinned_tweet_id",
302
+ "profile_image_url",
303
+ "protected",
304
+ "public_metrics",
305
+ "url",
306
+ "verified",
307
+ "verified_type"
308
+ ]
309
+ });
310
+ if (!response.data || response.data.length === 0) {
512
311
  break;
513
312
  }
514
- nTweets++;
313
+ for (const user of response.data) {
314
+ if (count >= maxProfiles) break;
315
+ yield parseV2UserToProfile(user);
316
+ count++;
317
+ }
318
+ paginationToken = response.meta?.next_token;
319
+ if (!paginationToken) break;
515
320
  }
321
+ } catch (error) {
322
+ console.error("Error fetching following:", error);
323
+ throw error;
516
324
  }
517
325
  }
518
-
519
- // src/client/timeline-relationship.ts
520
- function parseRelationshipTimeline(timeline) {
521
- let bottomCursor;
522
- let topCursor;
523
- const profiles = [];
524
- const instructions = timeline.data?.user?.result?.timeline?.timeline?.instructions ?? [];
525
- for (const instruction of instructions) {
526
- if (instruction.type === "TimelineAddEntries" || instruction.type === "TimelineReplaceEntry") {
527
- if (instruction.entry?.content?.cursorType === "Bottom") {
528
- bottomCursor = instruction.entry.content.value;
529
- continue;
530
- }
531
- if (instruction.entry?.content?.cursorType === "Top") {
532
- topCursor = instruction.entry.content.value;
533
- continue;
326
+ async function* getFollowers(userId, maxProfiles, auth) {
327
+ if (!auth) {
328
+ throw new Error("Not authenticated");
329
+ }
330
+ const client = auth.getV2Client();
331
+ let count = 0;
332
+ let paginationToken;
333
+ try {
334
+ while (count < maxProfiles) {
335
+ const response = await client.v2.followers(userId, {
336
+ max_results: Math.min(maxProfiles - count, 100),
337
+ pagination_token: paginationToken,
338
+ "user.fields": [
339
+ "id",
340
+ "name",
341
+ "username",
342
+ "created_at",
343
+ "description",
344
+ "entities",
345
+ "location",
346
+ "pinned_tweet_id",
347
+ "profile_image_url",
348
+ "protected",
349
+ "public_metrics",
350
+ "url",
351
+ "verified",
352
+ "verified_type"
353
+ ]
354
+ });
355
+ if (!response.data || response.data.length === 0) {
356
+ break;
534
357
  }
535
- const entries = instruction.entries ?? [];
536
- for (const entry of entries) {
537
- const itemContent = entry.content?.itemContent;
538
- if (itemContent?.userDisplayType === "User") {
539
- const userResultRaw = itemContent.user_results?.result;
540
- if (userResultRaw?.legacy) {
541
- const profile = parseProfile(
542
- userResultRaw.legacy,
543
- userResultRaw.is_blue_verified
544
- );
545
- if (!profile.userId) {
546
- profile.userId = userResultRaw.rest_id;
547
- }
548
- profiles.push(profile);
549
- }
550
- } else if (entry.content?.cursorType === "Bottom") {
551
- bottomCursor = entry.content.value;
552
- } else if (entry.content?.cursorType === "Top") {
553
- topCursor = entry.content.value;
554
- }
358
+ for (const user of response.data) {
359
+ if (count >= maxProfiles) break;
360
+ yield parseV2UserToProfile(user);
361
+ count++;
555
362
  }
363
+ paginationToken = response.meta?.next_token;
364
+ if (!paginationToken) break;
556
365
  }
366
+ } catch (error) {
367
+ console.error("Error fetching followers:", error);
368
+ throw error;
557
369
  }
558
- return { profiles, next: bottomCursor, previous: topCursor };
559
- }
560
-
561
- // src/client/relationships.ts
562
- function getFollowing(userId, maxProfiles, auth) {
563
- return getUserTimeline(userId, maxProfiles, (q, mt, c) => {
564
- return fetchProfileFollowing(q, mt, auth, c);
565
- });
566
- }
567
- function getFollowers(userId, maxProfiles, auth) {
568
- return getUserTimeline(userId, maxProfiles, (q, mt, c) => {
569
- return fetchProfileFollowers(q, mt, auth, c);
570
- });
571
370
  }
572
371
  async function fetchProfileFollowing(userId, maxProfiles, auth, cursor) {
573
- const timeline = await getFollowingTimeline(
574
- userId,
575
- maxProfiles,
576
- auth,
577
- cursor
578
- );
579
- return parseRelationshipTimeline(timeline);
580
- }
581
- async function fetchProfileFollowers(userId, maxProfiles, auth, cursor) {
582
- const timeline = await getFollowersTimeline(
583
- userId,
584
- maxProfiles,
585
- auth,
586
- cursor
587
- );
588
- return parseRelationshipTimeline(timeline);
589
- }
590
- async function getFollowingTimeline(userId, maxItems, auth, cursor) {
591
- if (!auth.isLoggedIn()) {
592
- throw new Error("Client is not logged-in for profile following.");
593
- }
594
- if (maxItems > 50) {
595
- maxItems = 50;
372
+ if (!auth) {
373
+ throw new Error("Not authenticated");
596
374
  }
597
- const variables = {
598
- userId,
599
- count: maxItems,
600
- includePromotedContent: false
601
- };
602
- const features2 = addApiFeatures({
603
- responsive_web_twitter_article_tweet_consumption_enabled: false,
604
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
605
- longform_notetweets_inline_media_enabled: true,
606
- responsive_web_media_download_video_enabled: false
607
- });
608
- if (cursor != null && cursor !== "") {
609
- variables.cursor = cursor;
610
- }
611
- const params = new URLSearchParams();
612
- params.set("features", stringify2(features2) ?? "");
613
- params.set("variables", stringify2(variables) ?? "");
614
- const res = await requestApi(
615
- `https://twitter.com/i/api/graphql/iSicc7LrzWGBgDPL0tM_TQ/Following?${params.toString()}`,
616
- auth
617
- );
618
- if (!res.success) {
619
- throw res.err;
375
+ const client = auth.getV2Client();
376
+ try {
377
+ const response = await client.v2.following(userId, {
378
+ max_results: Math.min(maxProfiles, 100),
379
+ pagination_token: cursor,
380
+ "user.fields": [
381
+ "id",
382
+ "name",
383
+ "username",
384
+ "created_at",
385
+ "description",
386
+ "entities",
387
+ "location",
388
+ "pinned_tweet_id",
389
+ "profile_image_url",
390
+ "protected",
391
+ "public_metrics",
392
+ "url",
393
+ "verified",
394
+ "verified_type"
395
+ ]
396
+ });
397
+ const profiles = response.data?.map(parseV2UserToProfile) || [];
398
+ return {
399
+ profiles,
400
+ next: response.meta?.next_token
401
+ };
402
+ } catch (error) {
403
+ console.error("Error fetching following profiles:", error);
404
+ throw error;
620
405
  }
621
- return res.value;
622
406
  }
623
- async function getFollowersTimeline(userId, maxItems, auth, cursor) {
624
- if (!auth.isLoggedIn()) {
625
- throw new Error("Client is not logged-in for profile followers.");
626
- }
627
- if (maxItems > 50) {
628
- maxItems = 50;
407
+ async function fetchProfileFollowers(userId, maxProfiles, auth, cursor) {
408
+ if (!auth) {
409
+ throw new Error("Not authenticated");
629
410
  }
630
- const variables = {
631
- userId,
632
- count: maxItems,
633
- includePromotedContent: false
634
- };
635
- const features2 = addApiFeatures({
636
- responsive_web_twitter_article_tweet_consumption_enabled: false,
637
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
638
- longform_notetweets_inline_media_enabled: true,
639
- responsive_web_media_download_video_enabled: false
640
- });
641
- if (cursor != null && cursor !== "") {
642
- variables.cursor = cursor;
643
- }
644
- const params = new URLSearchParams();
645
- params.set("features", stringify2(features2) ?? "");
646
- params.set("variables", stringify2(variables) ?? "");
647
- const res = await requestApi(
648
- `https://twitter.com/i/api/graphql/rRXFSG5vR6drKr5M37YOTw/Followers?${params.toString()}`,
649
- auth
650
- );
651
- if (!res.success) {
652
- throw res.err;
411
+ const client = auth.getV2Client();
412
+ try {
413
+ const response = await client.v2.followers(userId, {
414
+ max_results: Math.min(maxProfiles, 100),
415
+ pagination_token: cursor,
416
+ "user.fields": [
417
+ "id",
418
+ "name",
419
+ "username",
420
+ "created_at",
421
+ "description",
422
+ "entities",
423
+ "location",
424
+ "pinned_tweet_id",
425
+ "profile_image_url",
426
+ "protected",
427
+ "public_metrics",
428
+ "url",
429
+ "verified",
430
+ "verified_type"
431
+ ]
432
+ });
433
+ const profiles = response.data?.map(parseV2UserToProfile) || [];
434
+ return {
435
+ profiles,
436
+ next: response.meta?.next_token
437
+ };
438
+ } catch (error) {
439
+ console.error("Error fetching follower profiles:", error);
440
+ throw error;
653
441
  }
654
- return res.value;
655
442
  }
656
443
  async function followUser(username, auth) {
657
- console.warn(
658
- "Follow user functionality is not supported in Twitter API v2 client"
659
- );
660
- throw new Error(
661
- "Follow user functionality not implemented for Twitter API v2"
662
- );
444
+ if (!auth) {
445
+ throw new Error("Not authenticated");
446
+ }
447
+ const client = auth.getV2Client();
448
+ try {
449
+ const userResponse = await client.v2.userByUsername(username);
450
+ if (!userResponse.data) {
451
+ throw new Error(`User ${username} not found`);
452
+ }
453
+ const meResponse = await client.v2.me();
454
+ if (!meResponse.data) {
455
+ throw new Error("Failed to get authenticated user");
456
+ }
457
+ const result = await client.v2.follow(meResponse.data.id, userResponse.data.id);
458
+ return new Response(JSON.stringify(result), {
459
+ status: result.data?.following ? 200 : 400,
460
+ headers: new Headers({ "Content-Type": "application/json" })
461
+ });
462
+ } catch (error) {
463
+ console.error("Error following user:", error);
464
+ throw error;
465
+ }
663
466
  }
664
467
 
665
468
  // src/client/search.ts
@@ -795,449 +598,6 @@ async function* searchQuotedTweets(quotedTweetId, maxTweets, auth) {
795
598
  const query = `url:"twitter.com/*/status/${quotedTweetId}"`;
796
599
  yield* searchTweets(query, maxTweets, 1 /* Latest */, auth);
797
600
  }
798
- var fetchSearchTweets = async (query, maxTweets, searchMode, auth, cursor) => {
799
- throw new Error(
800
- "fetchSearchTweets is deprecated. Use searchTweets generator instead."
801
- );
802
- };
803
- var fetchSearchProfiles = async (query, maxProfiles, auth, cursor) => {
804
- throw new Error(
805
- "fetchSearchProfiles is deprecated. Use searchProfiles generator instead."
806
- );
807
- };
808
-
809
- // src/client/timeline-following.ts
810
- async function fetchFollowingTimeline(count, seenTweetIds, auth) {
811
- const variables = {
812
- count,
813
- includePromotedContent: true,
814
- latestControlAvailable: true,
815
- requestContext: "launch",
816
- seenTweetIds
817
- };
818
- const features2 = {
819
- profile_label_improvements_pcf_label_in_post_enabled: true,
820
- rweb_tipjar_consumption_enabled: true,
821
- responsive_web_graphql_exclude_directive_enabled: true,
822
- verified_phone_label_enabled: false,
823
- creator_subscriptions_tweet_preview_api_enabled: true,
824
- responsive_web_graphql_timeline_navigation_enabled: true,
825
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
826
- communities_web_enable_tweet_community_results_fetch: true,
827
- c9s_tweet_anatomy_moderator_badge_enabled: true,
828
- articles_preview_enabled: true,
829
- responsive_web_edit_tweet_api_enabled: true,
830
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
831
- view_counts_everywhere_api_enabled: true,
832
- longform_notetweets_consumption_enabled: true,
833
- responsive_web_twitter_article_tweet_consumption_enabled: true,
834
- tweet_awards_web_tipping_enabled: false,
835
- creator_subscriptions_quote_tweet_preview_enabled: false,
836
- freedom_of_speech_not_reach_fetch_enabled: true,
837
- standardized_nudges_misinfo: true,
838
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
839
- rweb_video_timestamps_enabled: true,
840
- longform_notetweets_rich_text_read_enabled: true,
841
- longform_notetweets_inline_media_enabled: true,
842
- responsive_web_enhance_cards_enabled: false
843
- };
844
- const res = await requestApi(
845
- `https://x.com/i/api/graphql/K0X1xbCZUjttdK8RazKAlw/HomeLatestTimeline?variables=${encodeURIComponent(
846
- JSON.stringify(variables)
847
- )}&features=${encodeURIComponent(JSON.stringify(features2))}`,
848
- auth,
849
- "GET"
850
- );
851
- if (!res.success) {
852
- if (res.err instanceof ApiError) {
853
- console.error("Error details:", res.err.data);
854
- }
855
- throw res.err;
856
- }
857
- const home = res.value?.data?.home.home_timeline_urt?.instructions;
858
- if (!home) {
859
- return [];
860
- }
861
- const entries = [];
862
- for (const instruction of home) {
863
- if (instruction.type === "TimelineAddEntries") {
864
- for (const entry of instruction.entries ?? []) {
865
- entries.push(entry);
866
- }
867
- }
868
- }
869
- const tweets = entries.map((entry) => entry.content.itemContent?.tweet_results?.result).filter((tweet) => tweet !== void 0);
870
- return tweets;
871
- }
872
-
873
- // src/client/timeline-home.ts
874
- async function fetchHomeTimeline(count, seenTweetIds, auth) {
875
- const variables = {
876
- count,
877
- includePromotedContent: true,
878
- latestControlAvailable: true,
879
- requestContext: "launch",
880
- withCommunity: true,
881
- seenTweetIds
882
- };
883
- const features2 = {
884
- rweb_tipjar_consumption_enabled: true,
885
- responsive_web_graphql_exclude_directive_enabled: true,
886
- verified_phone_label_enabled: false,
887
- creator_subscriptions_tweet_preview_api_enabled: true,
888
- responsive_web_graphql_timeline_navigation_enabled: true,
889
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
890
- communities_web_enable_tweet_community_results_fetch: true,
891
- c9s_tweet_anatomy_moderator_badge_enabled: true,
892
- articles_preview_enabled: true,
893
- responsive_web_edit_tweet_api_enabled: true,
894
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
895
- view_counts_everywhere_api_enabled: true,
896
- longform_notetweets_consumption_enabled: true,
897
- responsive_web_twitter_article_tweet_consumption_enabled: true,
898
- tweet_awards_web_tipping_enabled: false,
899
- creator_subscriptions_quote_tweet_preview_enabled: false,
900
- freedom_of_speech_not_reach_fetch_enabled: true,
901
- standardized_nudges_misinfo: true,
902
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
903
- rweb_video_timestamps_enabled: true,
904
- longform_notetweets_rich_text_read_enabled: true,
905
- longform_notetweets_inline_media_enabled: true,
906
- responsive_web_enhance_cards_enabled: false
907
- };
908
- const res = await requestApi(
909
- `https://x.com/i/api/graphql/HJFjzBgCs16TqxewQOeLNg/HomeTimeline?variables=${encodeURIComponent(
910
- JSON.stringify(variables)
911
- )}&features=${encodeURIComponent(JSON.stringify(features2))}`,
912
- auth,
913
- "GET"
914
- );
915
- if (!res.success) {
916
- if (res.err instanceof ApiError) {
917
- console.error("Error details:", res.err.data);
918
- }
919
- throw res.err;
920
- }
921
- const home = res.value?.data?.home.home_timeline_urt?.instructions;
922
- if (!home) {
923
- return [];
924
- }
925
- const entries = [];
926
- for (const instruction of home) {
927
- if (instruction.type === "TimelineAddEntries") {
928
- for (const entry of instruction.entries ?? []) {
929
- entries.push(entry);
930
- }
931
- }
932
- }
933
- const tweets = entries.map((entry) => entry.content.itemContent?.tweet_results?.result).filter((tweet) => tweet !== void 0);
934
- return tweets;
935
- }
936
-
937
- // src/client/type-util.ts
938
- function isFieldDefined(key) {
939
- return (value) => isDefined(value[key]);
940
- }
941
- function isDefined(value) {
942
- return value != null;
943
- }
944
-
945
- // src/client/timeline-tweet-util.ts
946
- var reHashtag = /\B(\#\S+\b)/g;
947
- var reCashtag = /\B(\$\S+\b)/g;
948
- var reTwitterUrl = /https:(\/\/t\.co\/([A-Za-z0-9]|[A-Za-z]){10})/g;
949
- var reUsername = /\B(\@\S{1,15}\b)/g;
950
- function parseMediaGroups(media) {
951
- const photos = [];
952
- const videos = [];
953
- let sensitiveContent = void 0;
954
- for (const m of media.filter(isFieldDefined("id_str")).filter(isFieldDefined("media_url_https"))) {
955
- if (m.type === "photo") {
956
- photos.push({
957
- id: m.id_str,
958
- url: m.media_url_https,
959
- alt_text: m.ext_alt_text
960
- });
961
- } else if (m.type === "video") {
962
- videos.push(parseVideo(m));
963
- }
964
- const sensitive = m.ext_sensitive_media_warning;
965
- if (sensitive != null) {
966
- sensitiveContent = sensitive.adult_content || sensitive.graphic_violence || sensitive.other;
967
- }
968
- }
969
- return { sensitiveContent, photos, videos };
970
- }
971
- function parseVideo(m) {
972
- const video = {
973
- id: m.id_str,
974
- preview: m.media_url_https
975
- };
976
- let maxBitrate = 0;
977
- const variants = m.video_info?.variants ?? [];
978
- for (const variant of variants) {
979
- const bitrate = variant.bitrate;
980
- if (bitrate != null && bitrate > maxBitrate && variant.url != null) {
981
- let variantUrl = variant.url;
982
- const stringStart = 0;
983
- const tagSuffixIdx = variantUrl.indexOf("?tag=10");
984
- if (tagSuffixIdx !== -1) {
985
- variantUrl = variantUrl.substring(stringStart, tagSuffixIdx + 1);
986
- }
987
- video.url = variantUrl;
988
- maxBitrate = bitrate;
989
- }
990
- }
991
- return video;
992
- }
993
- function reconstructTweetHtml(tweet, photos, videos) {
994
- const media = [];
995
- let html = tweet.full_text ?? "";
996
- html = html.replace(reHashtag, linkHashtagHtml);
997
- html = html.replace(reCashtag, linkCashtagHtml);
998
- html = html.replace(reUsername, linkUsernameHtml);
999
- html = html.replace(reTwitterUrl, unwrapTcoUrlHtml(tweet, media));
1000
- for (const { url } of photos) {
1001
- if (media.indexOf(url) !== -1) {
1002
- continue;
1003
- }
1004
- html += `<br><img src="${url}"/>`;
1005
- }
1006
- for (const { preview: url } of videos) {
1007
- if (media.indexOf(url) !== -1) {
1008
- continue;
1009
- }
1010
- html += `<br><img src="${url}"/>`;
1011
- }
1012
- html = html.replace(/\n/g, "<br>");
1013
- return html;
1014
- }
1015
- function linkHashtagHtml(hashtag) {
1016
- return `<a href="https://twitter.com/hashtag/${hashtag.replace("#", "")}">${hashtag}</a>`;
1017
- }
1018
- function linkCashtagHtml(cashtag) {
1019
- return `<a href="https://twitter.com/search?q=%24${cashtag.replace("$", "")}">${cashtag}</a>`;
1020
- }
1021
- function linkUsernameHtml(username) {
1022
- return `<a href="https://twitter.com/${username.replace("@", "")}">${username}</a>`;
1023
- }
1024
- function unwrapTcoUrlHtml(tweet, foundedMedia) {
1025
- return (tco) => {
1026
- for (const entity of tweet.entities?.urls ?? []) {
1027
- if (tco === entity.url && entity.expanded_url != null) {
1028
- return `<a href="${entity.expanded_url}">${tco}</a>`;
1029
- }
1030
- }
1031
- for (const entity of tweet.extended_entities?.media ?? []) {
1032
- if (tco === entity.url && entity.media_url_https != null) {
1033
- foundedMedia.push(entity.media_url_https);
1034
- return `<br><a href="${tco}"><img src="${entity.media_url_https}"/></a>`;
1035
- }
1036
- }
1037
- return tco;
1038
- };
1039
- }
1040
-
1041
- // src/client/timeline-v2.ts
1042
- function parseLegacyTweet(user, tweet) {
1043
- if (tweet == null) {
1044
- return {
1045
- success: false,
1046
- err: new Error("Tweet was not found in the timeline object.")
1047
- };
1048
- }
1049
- if (user == null) {
1050
- return {
1051
- success: false,
1052
- err: new Error("User was not found in the timeline object.")
1053
- };
1054
- }
1055
- if (!tweet.id_str) {
1056
- if (!tweet.conversation_id_str) {
1057
- return {
1058
- success: false,
1059
- err: new Error("Tweet ID was not found in object.")
1060
- };
1061
- }
1062
- tweet.id_str = tweet.conversation_id_str;
1063
- }
1064
- const hashtags = tweet.entities?.hashtags ?? [];
1065
- const mentions = tweet.entities?.user_mentions ?? [];
1066
- const media = tweet.extended_entities?.media ?? [];
1067
- const pinnedTweets = new Set(
1068
- user.pinned_tweet_ids_str ?? []
1069
- );
1070
- const urls = tweet.entities?.urls ?? [];
1071
- const { photos, videos, sensitiveContent } = parseMediaGroups(media);
1072
- const tw = {
1073
- bookmarkCount: tweet.bookmark_count,
1074
- conversationId: tweet.conversation_id_str,
1075
- id: tweet.id_str,
1076
- hashtags: hashtags.filter(isFieldDefined("text")).map((hashtag) => hashtag.text),
1077
- likes: tweet.favorite_count,
1078
- mentions: mentions.filter(isFieldDefined("id_str")).map((mention) => ({
1079
- id: mention.id_str,
1080
- username: mention.screen_name,
1081
- name: mention.name
1082
- })),
1083
- name: user.name,
1084
- permanentUrl: `https://twitter.com/${user.screen_name}/status/${tweet.id_str}`,
1085
- photos,
1086
- replies: tweet.reply_count,
1087
- retweets: tweet.retweet_count,
1088
- text: tweet.full_text,
1089
- thread: [],
1090
- urls: urls.filter(isFieldDefined("expanded_url")).map((url) => url.expanded_url),
1091
- userId: tweet.user_id_str,
1092
- username: user.screen_name,
1093
- videos,
1094
- isQuoted: false,
1095
- isReply: false,
1096
- isRetweet: false,
1097
- isPin: false,
1098
- sensitiveContent: false
1099
- };
1100
- if (tweet.created_at) {
1101
- tw.timeParsed = new Date(Date.parse(tweet.created_at));
1102
- tw.timestamp = Math.floor(tw.timeParsed.valueOf() / 1e3);
1103
- }
1104
- if (tweet.place?.id) {
1105
- tw.place = tweet.place;
1106
- }
1107
- const quotedStatusIdStr = tweet.quoted_status_id_str;
1108
- const inReplyToStatusIdStr = tweet.in_reply_to_status_id_str;
1109
- const retweetedStatusIdStr = tweet.retweeted_status_id_str;
1110
- const retweetedStatusResult = tweet.retweeted_status_result?.result;
1111
- if (quotedStatusIdStr) {
1112
- tw.isQuoted = true;
1113
- tw.quotedStatusId = quotedStatusIdStr;
1114
- }
1115
- if (inReplyToStatusIdStr) {
1116
- tw.isReply = true;
1117
- tw.inReplyToStatusId = inReplyToStatusIdStr;
1118
- }
1119
- if (retweetedStatusIdStr || retweetedStatusResult) {
1120
- tw.isRetweet = true;
1121
- tw.retweetedStatusId = retweetedStatusIdStr;
1122
- if (retweetedStatusResult) {
1123
- const parsedResult = parseLegacyTweet(
1124
- retweetedStatusResult?.core?.user_results?.result?.legacy,
1125
- retweetedStatusResult?.legacy
1126
- );
1127
- if (parsedResult.success) {
1128
- tw.retweetedStatus = parsedResult.tweet;
1129
- }
1130
- }
1131
- }
1132
- const views = Number.parseInt(tweet.ext_views?.count ?? "");
1133
- if (!Number.isNaN(views)) {
1134
- tw.views = views;
1135
- }
1136
- if (pinnedTweets.has(tweet.id_str)) {
1137
- tw.isPin = true;
1138
- }
1139
- if (sensitiveContent) {
1140
- tw.sensitiveContent = true;
1141
- }
1142
- tw.html = reconstructTweetHtml(tweet, tw.photos, tw.videos);
1143
- return { success: true, tweet: tw };
1144
- }
1145
- function parseResult(result) {
1146
- const noteTweetResultText = result?.note_tweet?.note_tweet_results?.result?.text;
1147
- if (result?.legacy && noteTweetResultText) {
1148
- result.legacy.full_text = noteTweetResultText;
1149
- }
1150
- const tweetResult = parseLegacyTweet(
1151
- result?.core?.user_results?.result?.legacy,
1152
- result?.legacy
1153
- );
1154
- if (!tweetResult.success) {
1155
- return tweetResult;
1156
- }
1157
- if (!tweetResult.tweet.views && result?.views?.count) {
1158
- const views = Number.parseInt(result.views.count);
1159
- if (!Number.isNaN(views)) {
1160
- tweetResult.tweet.views = views;
1161
- }
1162
- }
1163
- const quotedResult = result?.quoted_status_result?.result;
1164
- if (quotedResult) {
1165
- if (quotedResult.legacy && quotedResult.rest_id) {
1166
- quotedResult.legacy.id_str = quotedResult.rest_id;
1167
- }
1168
- const quotedTweetResult = parseResult(quotedResult);
1169
- if (quotedTweetResult.success) {
1170
- tweetResult.tweet.quotedStatus = quotedTweetResult.tweet;
1171
- }
1172
- }
1173
- return tweetResult;
1174
- }
1175
- var expectedEntryTypes = ["tweet", "profile-conversation"];
1176
- function parseTimelineTweetsV2(timeline) {
1177
- let bottomCursor;
1178
- let topCursor;
1179
- const tweets = [];
1180
- const instructions = timeline.data?.user?.result?.timeline_v2?.timeline?.instructions ?? [];
1181
- for (const instruction of instructions) {
1182
- const entries = instruction.entries ?? [];
1183
- for (const entry of entries) {
1184
- const entryContent = entry.content;
1185
- if (!entryContent) continue;
1186
- if (entryContent.cursorType === "Bottom") {
1187
- bottomCursor = entryContent.value;
1188
- continue;
1189
- }
1190
- if (entryContent.cursorType === "Top") {
1191
- topCursor = entryContent.value;
1192
- continue;
1193
- }
1194
- const idStr = entry.entryId;
1195
- if (!expectedEntryTypes.some((entryType) => idStr.startsWith(entryType))) {
1196
- continue;
1197
- }
1198
- if (entryContent.itemContent) {
1199
- parseAndPush(tweets, entryContent.itemContent, idStr);
1200
- } else if (entryContent.items) {
1201
- for (const item of entryContent.items) {
1202
- if (item.item?.itemContent) {
1203
- parseAndPush(tweets, item.item.itemContent, idStr);
1204
- }
1205
- }
1206
- }
1207
- }
1208
- }
1209
- return { tweets, next: bottomCursor, previous: topCursor };
1210
- }
1211
- function parseTimelineEntryItemContentRaw(content, entryId, isConversation = false) {
1212
- let result = content.tweet_results?.result ?? content.tweetResult?.result;
1213
- if (result?.__typename === "Tweet" || result?.__typename === "TweetWithVisibilityResults" && result?.tweet) {
1214
- if (result?.__typename === "TweetWithVisibilityResults")
1215
- result = result.tweet;
1216
- if (result?.legacy) {
1217
- result.legacy.id_str = result.rest_id ?? entryId.replace("conversation-", "").replace("tweet-", "");
1218
- }
1219
- const tweetResult = parseResult(result);
1220
- if (tweetResult.success) {
1221
- if (isConversation) {
1222
- if (content?.tweetDisplayType === "SelfThread") {
1223
- tweetResult.tweet.isSelfThread = true;
1224
- }
1225
- }
1226
- return tweetResult.tweet;
1227
- }
1228
- }
1229
- return null;
1230
- }
1231
- function parseAndPush(tweets, content, entryId, isConversation = false) {
1232
- const tweet = parseTimelineEntryItemContentRaw(
1233
- content,
1234
- entryId,
1235
- isConversation
1236
- );
1237
- if (tweet) {
1238
- tweets.push(tweet);
1239
- }
1240
- }
1241
601
 
1242
602
  // src/client/tweets.ts
1243
603
  var defaultOptions = {
@@ -1318,13 +678,6 @@ var defaultOptions = {
1318
678
  "place_type"
1319
679
  ]
1320
680
  };
1321
- var features = addApiFeatures({
1322
- interactive_text_enabled: true,
1323
- longform_notetweets_inline_media_enabled: false,
1324
- responsive_web_text_conversations_enabled: false,
1325
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false,
1326
- vibe_api_enabled: false
1327
- });
1328
681
  async function fetchTweets(userId, maxTweets, cursor, auth) {
1329
682
  const client = auth.getV2Client();
1330
683
  try {
@@ -1360,7 +713,7 @@ async function fetchTweets(userId, maxTweets, cursor, auth) {
1360
713
  next: response.meta.next_token
1361
714
  };
1362
715
  } catch (error) {
1363
- throw new Error(`Failed to fetch tweets: ${error.message}`);
716
+ throw new Error(`Failed to fetch tweets: ${error?.message || error}`);
1364
717
  }
1365
718
  }
1366
719
  async function fetchTweetsAndReplies(userId, maxTweets, cursor, auth) {
@@ -1397,7 +750,7 @@ async function fetchTweetsAndReplies(userId, maxTweets, cursor, auth) {
1397
750
  next: response.meta.next_token
1398
751
  };
1399
752
  } catch (error) {
1400
- throw new Error(`Failed to fetch tweets and replies: ${error.message}`);
753
+ throw new Error(`Failed to fetch tweets and replies: ${error?.message || error}`);
1401
754
  }
1402
755
  }
1403
756
  async function createCreateTweetRequestV2(text, auth, tweetId, options) {
@@ -1442,45 +795,50 @@ async function createCreateTweetRequestV2(text, auth, tweetId, options) {
1442
795
  }
1443
796
  return await getTweetV2(tweetResponse.data.id, auth, optionsConfig);
1444
797
  }
1445
- function parseTweetV2ToV1(tweetV2, includes, defaultTweetData) {
1446
- let parsedTweet;
1447
- if (defaultTweetData != null) {
1448
- parsedTweet = defaultTweetData;
1449
- }
1450
- parsedTweet = {
798
+ function parseTweetV2ToV1(tweetV2, includes) {
799
+ const parsedTweet = {
1451
800
  id: tweetV2.id,
1452
- text: tweetV2.text ?? defaultTweetData?.text ?? "",
1453
- hashtags: tweetV2.entities?.hashtags?.map((tag) => tag.tag) ?? defaultTweetData?.hashtags ?? [],
801
+ text: tweetV2.text ?? "",
802
+ hashtags: tweetV2.entities?.hashtags?.map((tag) => tag.tag) ?? [],
1454
803
  mentions: tweetV2.entities?.mentions?.map((mention) => ({
1455
804
  id: mention.id,
1456
805
  username: mention.username
1457
- })) ?? defaultTweetData?.mentions ?? [],
1458
- urls: tweetV2.entities?.urls?.map((url) => url.url) ?? defaultTweetData?.urls ?? [],
1459
- likes: tweetV2.public_metrics?.like_count ?? defaultTweetData?.likes ?? 0,
1460
- retweets: tweetV2.public_metrics?.retweet_count ?? defaultTweetData?.retweets ?? 0,
1461
- replies: tweetV2.public_metrics?.reply_count ?? defaultTweetData?.replies ?? 0,
1462
- views: tweetV2.public_metrics?.impression_count ?? defaultTweetData?.views ?? 0,
1463
- userId: tweetV2.author_id ?? defaultTweetData?.userId,
1464
- conversationId: tweetV2.conversation_id ?? defaultTweetData?.conversationId,
1465
- photos: defaultTweetData?.photos ?? [],
1466
- videos: defaultTweetData?.videos ?? [],
1467
- poll: defaultTweetData?.poll ?? null,
1468
- username: defaultTweetData?.username ?? "",
1469
- name: defaultTweetData?.name ?? "",
1470
- place: defaultTweetData?.place,
1471
- thread: defaultTweetData?.thread ?? []
806
+ })) ?? [],
807
+ urls: tweetV2.entities?.urls?.map((url) => url.url) ?? [],
808
+ likes: tweetV2.public_metrics?.like_count ?? 0,
809
+ retweets: tweetV2.public_metrics?.retweet_count ?? 0,
810
+ replies: tweetV2.public_metrics?.reply_count ?? 0,
811
+ quotes: tweetV2.public_metrics?.quote_count ?? 0,
812
+ views: tweetV2.public_metrics?.impression_count ?? 0,
813
+ userId: tweetV2.author_id,
814
+ conversationId: tweetV2.conversation_id,
815
+ photos: [],
816
+ videos: [],
817
+ poll: null,
818
+ username: "",
819
+ name: "",
820
+ thread: [],
821
+ timestamp: tweetV2.created_at ? new Date(tweetV2.created_at).getTime() / 1e3 : Date.now() / 1e3,
822
+ permanentUrl: `https://twitter.com/i/status/${tweetV2.id}`,
823
+ // Check for referenced tweets
824
+ isReply: tweetV2.referenced_tweets?.some((ref) => ref.type === "replied_to") ?? false,
825
+ isRetweet: tweetV2.referenced_tweets?.some((ref) => ref.type === "retweeted") ?? false,
826
+ isQuoted: tweetV2.referenced_tweets?.some((ref) => ref.type === "quoted") ?? false,
827
+ inReplyToStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "replied_to")?.id,
828
+ quotedStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "quoted")?.id,
829
+ retweetedStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "retweeted")?.id
1472
830
  };
1473
831
  if (includes?.polls?.length) {
1474
832
  const poll = includes.polls[0];
1475
833
  parsedTweet.poll = {
1476
834
  id: poll.id,
1477
- end_datetime: poll.end_datetime ? poll.end_datetime : defaultTweetData?.poll?.end_datetime ? defaultTweetData?.poll?.end_datetime : void 0,
835
+ end_datetime: poll.end_datetime,
1478
836
  options: poll.options.map((option) => ({
1479
837
  position: option.position,
1480
838
  label: option.label,
1481
839
  votes: option.votes
1482
840
  })),
1483
- voting_status: poll.voting_status ?? defaultTweetData?.poll?.voting_status
841
+ voting_status: poll.voting_status
1484
842
  };
1485
843
  }
1486
844
  if (includes?.media?.length) {
@@ -1507,8 +865,8 @@ function parseTweetV2ToV1(tweetV2, includes, defaultTweetData) {
1507
865
  (user2) => user2.id === tweetV2.author_id
1508
866
  );
1509
867
  if (user) {
1510
- parsedTweet.username = user.username ?? defaultTweetData?.username ?? "";
1511
- parsedTweet.name = user.name ?? defaultTweetData?.name ?? "";
868
+ parsedTweet.username = user.username ?? "";
869
+ parsedTweet.name = user.name ?? "";
1512
870
  }
1513
871
  }
1514
872
  if (tweetV2?.geo?.place_id && includes?.places?.length) {
@@ -1518,11 +876,11 @@ function parseTweetV2ToV1(tweetV2, includes, defaultTweetData) {
1518
876
  if (place) {
1519
877
  parsedTweet.place = {
1520
878
  id: place.id,
1521
- full_name: place.full_name ?? defaultTweetData?.place?.full_name ?? "",
1522
- country: place.country ?? defaultTweetData?.place?.country ?? "",
1523
- country_code: place.country_code ?? defaultTweetData?.place?.country_code ?? "",
1524
- name: place.name ?? defaultTweetData?.place?.name ?? "",
1525
- place_type: place.place_type ?? defaultTweetData?.place?.place_type
879
+ full_name: place.full_name ?? "",
880
+ country: place.country ?? "",
881
+ country_code: place.country_code ?? "",
882
+ name: place.name ?? "",
883
+ place_type: place.place_type
1526
884
  };
1527
885
  }
1528
886
  }
@@ -1552,7 +910,7 @@ async function createCreateTweetRequest(text, auth, tweetId, mediaData, hideLink
1552
910
  data: result
1553
911
  };
1554
912
  } catch (error) {
1555
- throw new Error(`Failed to create tweet: ${error.message}`);
913
+ throw new Error(`Failed to create tweet: ${error?.message || error}`);
1556
914
  }
1557
915
  }
1558
916
  async function createCreateNoteTweetRequest(text, auth, tweetId, mediaData) {
@@ -1611,35 +969,71 @@ async function deleteTweet(tweetId, auth) {
1611
969
  throw new Error(`Failed to delete tweet: ${error.message}`);
1612
970
  }
1613
971
  }
1614
- function getTweets(user, maxTweets, auth) {
1615
- return getTweetTimeline(user, maxTweets, async (q, mt, c) => {
1616
- const userIdRes = await getEntityIdByScreenName(q, auth);
1617
- if (!userIdRes.success) {
1618
- throw userIdRes.err;
1619
- }
1620
- const { value: userId } = userIdRes;
1621
- return fetchTweets(userId, mt, c, auth);
1622
- });
972
+ async function* getTweets(user, maxTweets, auth) {
973
+ const userIdRes = await getEntityIdByScreenName(user, auth);
974
+ if (!userIdRes.success) {
975
+ throw userIdRes.err;
976
+ }
977
+ const { value: userId } = userIdRes;
978
+ let cursor;
979
+ let totalFetched = 0;
980
+ while (totalFetched < maxTweets) {
981
+ const response = await fetchTweets(userId, maxTweets - totalFetched, cursor, auth);
982
+ for (const tweet of response.tweets) {
983
+ yield tweet;
984
+ totalFetched++;
985
+ if (totalFetched >= maxTweets) break;
986
+ }
987
+ cursor = response.next;
988
+ if (!cursor) break;
989
+ }
1623
990
  }
1624
- function getTweetsByUserId(userId, maxTweets, auth) {
1625
- return getTweetTimeline(userId, maxTweets, (q, mt, c) => {
1626
- return fetchTweets(q, mt, c, auth);
1627
- });
991
+ async function* getTweetsByUserId(userId, maxTweets, auth) {
992
+ let cursor;
993
+ let totalFetched = 0;
994
+ while (totalFetched < maxTweets) {
995
+ const response = await fetchTweets(userId, maxTweets - totalFetched, cursor, auth);
996
+ for (const tweet of response.tweets) {
997
+ yield tweet;
998
+ totalFetched++;
999
+ if (totalFetched >= maxTweets) break;
1000
+ }
1001
+ cursor = response.next;
1002
+ if (!cursor) break;
1003
+ }
1628
1004
  }
1629
- function getTweetsAndReplies(user, maxTweets, auth) {
1630
- return getTweetTimeline(user, maxTweets, async (q, mt, c) => {
1631
- const userIdRes = await getEntityIdByScreenName(q, auth);
1632
- if (!userIdRes.success) {
1633
- throw userIdRes.err;
1634
- }
1635
- const { value: userId } = userIdRes;
1636
- return fetchTweetsAndReplies(userId, mt, c, auth);
1637
- });
1005
+ async function* getTweetsAndReplies(user, maxTweets, auth) {
1006
+ const userIdRes = await getEntityIdByScreenName(user, auth);
1007
+ if (!userIdRes.success) {
1008
+ throw userIdRes.err;
1009
+ }
1010
+ const { value: userId } = userIdRes;
1011
+ let cursor;
1012
+ let totalFetched = 0;
1013
+ while (totalFetched < maxTweets) {
1014
+ const response = await fetchTweetsAndReplies(userId, maxTweets - totalFetched, cursor, auth);
1015
+ for (const tweet of response.tweets) {
1016
+ yield tweet;
1017
+ totalFetched++;
1018
+ if (totalFetched >= maxTweets) break;
1019
+ }
1020
+ cursor = response.next;
1021
+ if (!cursor) break;
1022
+ }
1638
1023
  }
1639
- function getTweetsAndRepliesByUserId(userId, maxTweets, auth) {
1640
- return getTweetTimeline(userId, maxTweets, (q, mt, c) => {
1641
- return fetchTweetsAndReplies(q, mt, c, auth);
1642
- });
1024
+ async function* getTweetsAndRepliesByUserId(userId, maxTweets, auth) {
1025
+ let cursor;
1026
+ let totalFetched = 0;
1027
+ while (totalFetched < maxTweets) {
1028
+ const response = await fetchTweetsAndReplies(userId, maxTweets - totalFetched, cursor, auth);
1029
+ for (const tweet of response.tweets) {
1030
+ yield tweet;
1031
+ totalFetched++;
1032
+ if (totalFetched >= maxTweets) break;
1033
+ }
1034
+ cursor = response.next;
1035
+ if (!cursor) break;
1036
+ }
1643
1037
  }
1644
1038
  async function getTweetWhere(tweets, query) {
1645
1039
  const isCallback = typeof query === "function";
@@ -1723,11 +1117,9 @@ async function getTweetV2(id, auth, options = defaultOptions) {
1723
1117
  console.warn(`Tweet data not found for ID: ${id}`);
1724
1118
  return null;
1725
1119
  }
1726
- const defaultTweetData = await getTweet(tweetData.data.id, auth);
1727
1120
  const parsedTweet = parseTweetV2ToV1(
1728
1121
  tweetData.data,
1729
- tweetData?.includes,
1730
- defaultTweetData
1122
+ tweetData?.includes
1731
1123
  );
1732
1124
  return parsedTweet;
1733
1125
  } catch (error) {
@@ -1817,19 +1209,6 @@ async function retweet(tweetId, auth) {
1817
1209
  async function createCreateLongTweetRequest(text, auth, tweetId, mediaData) {
1818
1210
  return createCreateTweetRequest(text, auth, tweetId, mediaData);
1819
1211
  }
1820
- async function getArticle(id, auth) {
1821
- const tweet = await getTweet(id, auth);
1822
- if (!tweet) {
1823
- return null;
1824
- }
1825
- return {
1826
- id: tweet.id || id,
1827
- articleId: id,
1828
- title: "",
1829
- previewText: tweet.text?.substring(0, 100) || "",
1830
- text: tweet.text || ""
1831
- };
1832
- }
1833
1212
  async function fetchRetweetersPage(tweetId, auth, cursor, count = 40) {
1834
1213
  console.warn("Fetching retweeters not implemented for Twitter API v2");
1835
1214
  return {
@@ -1859,7 +1238,6 @@ async function getAllRetweeters(tweetId, auth) {
1859
1238
  }
1860
1239
 
1861
1240
  // src/client/client.ts
1862
- var UserTweetsUrl = "https://twitter.com/i/api/graphql/E3opETHurmVJflFsUBVuUQ/UserTweets";
1863
1241
  var Client = class {
1864
1242
  /**
1865
1243
  * Creates a new Client object.
@@ -1924,8 +1302,17 @@ var Client = class {
1924
1302
  * @param cursor The search cursor, which can be passed into further requests for more results.
1925
1303
  * @returns A page of results, containing a cursor that can be used in further requests.
1926
1304
  */
1927
- fetchSearchTweets(query, maxTweets, searchMode, cursor) {
1928
- return fetchSearchTweets(query, maxTweets, searchMode, this.auth, cursor);
1305
+ async fetchSearchTweets(query, maxTweets, searchMode, cursor) {
1306
+ const tweets = [];
1307
+ const generator = searchTweets(query, maxTweets, searchMode, this.auth);
1308
+ for await (const tweet of generator) {
1309
+ tweets.push(tweet);
1310
+ }
1311
+ return {
1312
+ tweets,
1313
+ // v2 API doesn't provide cursor-based pagination for search
1314
+ next: void 0
1315
+ };
1929
1316
  }
1930
1317
  /**
1931
1318
  * Fetches profiles from Twitter.
@@ -1934,8 +1321,17 @@ var Client = class {
1934
1321
  * @param cursor The search cursor, which can be passed into further requests for more results.
1935
1322
  * @returns A page of results, containing a cursor that can be used in further requests.
1936
1323
  */
1937
- fetchSearchProfiles(query, maxProfiles, cursor) {
1938
- return fetchSearchProfiles(query, maxProfiles, this.auth, cursor);
1324
+ async fetchSearchProfiles(query, maxProfiles, cursor) {
1325
+ const profiles = [];
1326
+ const generator = searchProfiles(query, maxProfiles, this.auth);
1327
+ for await (const profile of generator) {
1328
+ profiles.push(profile);
1329
+ }
1330
+ return {
1331
+ profiles,
1332
+ // v2 API doesn't provide cursor-based pagination for search
1333
+ next: void 0
1334
+ };
1939
1335
  }
1940
1336
  /**
1941
1337
  * Fetches list tweets from Twitter.
@@ -1986,82 +1382,101 @@ var Client = class {
1986
1382
  return fetchProfileFollowers(userId, maxProfiles, this.auth, cursor);
1987
1383
  }
1988
1384
  /**
1989
- * Fetches the home timeline for the current user. (for you feed)
1385
+ * Fetches the home timeline for the current user using Twitter API v2.
1386
+ * Note: Twitter API v2 doesn't distinguish between "For You" and "Following" feeds.
1990
1387
  * @param count The number of tweets to fetch.
1991
- * @param seenTweetIds An array of tweet IDs that have already been seen.
1992
- * @returns A promise that resolves to the home timeline response.
1388
+ * @param seenTweetIds An array of tweet IDs that have already been seen (not used in v2).
1389
+ * @returns A promise that resolves to an array of tweets.
1993
1390
  */
1994
1391
  async fetchHomeTimeline(count, seenTweetIds) {
1995
- return await fetchHomeTimeline(count, seenTweetIds, this.auth);
1392
+ if (!this.auth) {
1393
+ throw new Error("Not authenticated");
1394
+ }
1395
+ const client = this.auth.getV2Client();
1396
+ try {
1397
+ const timeline = await client.v2.homeTimeline({
1398
+ max_results: Math.min(count, 100),
1399
+ "tweet.fields": [
1400
+ "id",
1401
+ "text",
1402
+ "created_at",
1403
+ "author_id",
1404
+ "referenced_tweets",
1405
+ "entities",
1406
+ "public_metrics",
1407
+ "attachments",
1408
+ "conversation_id"
1409
+ ],
1410
+ "user.fields": ["id", "name", "username", "profile_image_url"],
1411
+ "media.fields": ["url", "preview_image_url", "type"],
1412
+ expansions: [
1413
+ "author_id",
1414
+ "attachments.media_keys",
1415
+ "referenced_tweets.id"
1416
+ ]
1417
+ });
1418
+ const tweets = [];
1419
+ for await (const tweet of timeline) {
1420
+ tweets.push(parseTweetV2ToV1(tweet, timeline.includes));
1421
+ if (tweets.length >= count) break;
1422
+ }
1423
+ return tweets;
1424
+ } catch (error) {
1425
+ console.error("Failed to fetch home timeline:", error);
1426
+ throw error;
1427
+ }
1996
1428
  }
1997
1429
  /**
1998
- * Fetches the home timeline for the current user. (following feed)
1430
+ * Fetches the home timeline for the current user (same as fetchHomeTimeline in v2).
1431
+ * Twitter API v2 doesn't provide separate "Following" timeline endpoint.
1999
1432
  * @param count The number of tweets to fetch.
2000
- * @param seenTweetIds An array of tweet IDs that have already been seen.
2001
- * @returns A promise that resolves to the home timeline response.
1433
+ * @param seenTweetIds An array of tweet IDs that have already been seen (not used in v2).
1434
+ * @returns A promise that resolves to an array of tweets.
2002
1435
  */
2003
1436
  async fetchFollowingTimeline(count, seenTweetIds) {
2004
- return await fetchFollowingTimeline(count, seenTweetIds, this.auth);
1437
+ return this.fetchHomeTimeline(count, seenTweetIds);
2005
1438
  }
2006
1439
  async getUserTweets(userId, maxTweets = 200, cursor) {
2007
- if (maxTweets > 200) {
2008
- maxTweets = 200;
2009
- }
2010
- const variables = {
2011
- userId,
2012
- count: maxTweets,
2013
- includePromotedContent: true,
2014
- withQuickPromoteEligibilityTweetFields: true,
2015
- withVoice: true,
2016
- withV2Timeline: true
2017
- };
2018
- if (cursor) {
2019
- variables.cursor = cursor;
2020
- }
2021
- const features2 = {
2022
- rweb_tipjar_consumption_enabled: true,
2023
- responsive_web_graphql_exclude_directive_enabled: true,
2024
- verified_phone_label_enabled: false,
2025
- creator_subscriptions_tweet_preview_api_enabled: true,
2026
- responsive_web_graphql_timeline_navigation_enabled: true,
2027
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
2028
- communities_web_enable_tweet_community_results_fetch: true,
2029
- c9s_tweet_anatomy_moderator_badge_enabled: true,
2030
- articles_preview_enabled: true,
2031
- responsive_web_edit_tweet_api_enabled: true,
2032
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
2033
- view_counts_everywhere_api_enabled: true,
2034
- longform_notetweets_consumption_enabled: true,
2035
- responsive_web_twitter_article_tweet_consumption_enabled: true,
2036
- tweet_awards_web_tipping_enabled: false,
2037
- creator_subscriptions_quote_tweet_preview_enabled: false,
2038
- freedom_of_speech_not_reach_fetch_enabled: true,
2039
- standardized_nudges_misinfo: true,
2040
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
2041
- rweb_video_timestamps_enabled: true,
2042
- longform_notetweets_rich_text_read_enabled: true,
2043
- longform_notetweets_inline_media_enabled: true,
2044
- responsive_web_enhance_cards_enabled: false
2045
- };
2046
- const fieldToggles = {
2047
- withArticlePlainText: false
2048
- };
2049
- const res = await requestApi(
2050
- `${UserTweetsUrl}?variables=${encodeURIComponent(
2051
- JSON.stringify(variables)
2052
- )}&features=${encodeURIComponent(JSON.stringify(features2))}&fieldToggles=${encodeURIComponent(
2053
- JSON.stringify(fieldToggles)
2054
- )}`,
2055
- this.auth
2056
- );
2057
- if (!res.success) {
2058
- throw res.err;
1440
+ if (!this.auth) {
1441
+ throw new Error("Not authenticated");
1442
+ }
1443
+ const client = this.auth.getV2Client();
1444
+ try {
1445
+ const response = await client.v2.userTimeline(userId, {
1446
+ max_results: Math.min(maxTweets, 100),
1447
+ "tweet.fields": [
1448
+ "id",
1449
+ "text",
1450
+ "created_at",
1451
+ "author_id",
1452
+ "referenced_tweets",
1453
+ "entities",
1454
+ "public_metrics",
1455
+ "attachments",
1456
+ "conversation_id"
1457
+ ],
1458
+ "user.fields": ["id", "name", "username", "profile_image_url"],
1459
+ "media.fields": ["url", "preview_image_url", "type"],
1460
+ expansions: [
1461
+ "author_id",
1462
+ "attachments.media_keys",
1463
+ "referenced_tweets.id"
1464
+ ],
1465
+ pagination_token: cursor
1466
+ });
1467
+ const tweets = [];
1468
+ for await (const tweet of response) {
1469
+ tweets.push(parseTweetV2ToV1(tweet, response.includes));
1470
+ if (tweets.length >= maxTweets) break;
1471
+ }
1472
+ return {
1473
+ tweets,
1474
+ next: response.meta?.next_token
1475
+ };
1476
+ } catch (error) {
1477
+ console.error("Failed to fetch user tweets:", error);
1478
+ throw error;
2059
1479
  }
2060
- const timelineV2 = parseTimelineTweetsV2(res.value);
2061
- return {
2062
- tweets: timelineV2.tweets,
2063
- next: timelineV2.next
2064
- };
2065
1480
  }
2066
1481
  async *getUserTweetsIterator(userId, maxTweets = 200) {
2067
1482
  let cursor;
@@ -2344,32 +1759,6 @@ var Client = class {
2344
1759
  "Logout is not applicable when using Twitter API v2 credentials"
2345
1760
  );
2346
1761
  }
2347
- /**
2348
- * Sets the optional cookie to be used in requests.
2349
- * @param _cookie The cookie to be used in requests.
2350
- * @deprecated This function no longer represents any part of Twitter's auth flow.
2351
- * @returns This client instance.
2352
- */
2353
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2354
- withCookie(_cookie) {
2355
- console.warn(
2356
- "Warning: Client#withCookie is deprecated and will be removed in a later version. Use Client#login or Client#setCookies instead."
2357
- );
2358
- return this;
2359
- }
2360
- /**
2361
- * Sets the optional CSRF token to be used in requests.
2362
- * @param _token The CSRF token to be used in requests.
2363
- * @deprecated This function no longer represents any part of Twitter's auth flow.
2364
- * @returns This client instance.
2365
- */
2366
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2367
- withXCsrfToken(_token) {
2368
- console.warn(
2369
- "Warning: Client#withXCsrfToken is deprecated and will be removed in a later version."
2370
- );
2371
- return this;
2372
- }
2373
1762
  /**
2374
1763
  * Sends a quote tweet.
2375
1764
  * @param text The text of the tweet.
@@ -2453,14 +1842,6 @@ var Client = class {
2453
1842
  }
2454
1843
  return res.value;
2455
1844
  }
2456
- /**
2457
- * Fetches a article (long form tweet) by its ID.
2458
- * @param id The ID of the article to fetch. In the format of (http://x.com/i/article/id)
2459
- * @returns The {@link TimelineArticle} object, or `null` if it couldn't be fetched.
2460
- */
2461
- getArticle(id) {
2462
- return getArticle(id, this.auth);
2463
- }
2464
1845
  /**
2465
1846
  * Retrieves all users who retweeted the given tweet.
2466
1847
  * @param tweetId The ID of the tweet.
@@ -2670,72 +2051,6 @@ var _ClientBase = class _ClientBase {
2670
2051
  onReady() {
2671
2052
  throw new Error("Not implemented in base class, please call from subclass");
2672
2053
  }
2673
- /**
2674
- * Parse the raw tweet data into a standardized Tweet object.
2675
- */
2676
- /**
2677
- * Parses a raw tweet object into a structured Tweet object.
2678
- *
2679
- * @param {any} raw - The raw tweet object to parse.
2680
- * @param {number} [depth=0] - The current depth of parsing nested quotes/retweets.
2681
- * @param {number} [maxDepth=3] - The maximum depth allowed for parsing nested quotes/retweets.
2682
- * @returns {Tweet} The parsed Tweet object.
2683
- */
2684
- parseTweet(raw, depth = 0, maxDepth = 3) {
2685
- const canRecurse = depth < maxDepth;
2686
- const quotedStatus = raw.quoted_status_result?.result && canRecurse ? this.parseTweet(raw.quoted_status_result.result, depth + 1, maxDepth) : void 0;
2687
- const retweetedStatus = raw.retweeted_status_result?.result && canRecurse ? this.parseTweet(
2688
- raw.retweeted_status_result.result,
2689
- depth + 1,
2690
- maxDepth
2691
- ) : void 0;
2692
- const t = {
2693
- bookmarkCount: raw.bookmarkCount ?? raw.legacy?.bookmark_count ?? void 0,
2694
- conversationId: raw.conversationId ?? raw.legacy?.conversation_id_str,
2695
- hashtags: raw.hashtags ?? raw.legacy?.entities?.hashtags ?? [],
2696
- html: raw.html,
2697
- id: raw.id ?? raw.rest_id ?? raw.legacy.id_str ?? raw.id_str ?? void 0,
2698
- inReplyToStatus: raw.inReplyToStatus,
2699
- inReplyToStatusId: raw.inReplyToStatusId ?? raw.legacy?.in_reply_to_status_id_str ?? void 0,
2700
- isQuoted: raw.legacy?.is_quote_status === true,
2701
- isPin: raw.isPin,
2702
- isReply: raw.isReply,
2703
- isRetweet: raw.legacy?.retweeted === true,
2704
- isSelfThread: raw.isSelfThread,
2705
- language: raw.legacy?.lang,
2706
- likes: raw.legacy?.favorite_count ?? 0,
2707
- name: raw.name ?? raw?.user_results?.result?.legacy?.name ?? raw.core?.user_results?.result?.legacy?.name,
2708
- mentions: raw.mentions ?? raw.legacy?.entities?.user_mentions ?? [],
2709
- permanentUrl: raw.permanentUrl ?? (raw.core?.user_results?.result?.legacy?.screen_name && raw.rest_id ? `https://x.com/${raw.core?.user_results?.result?.legacy?.screen_name}/status/${raw.rest_id}` : void 0),
2710
- photos: raw.photos ?? (raw.legacy?.entities?.media?.filter((media) => media.type === "photo").map((media) => ({
2711
- id: media.id_str || media.rest_id || media.legacy.id_str,
2712
- url: media.media_url_https,
2713
- alt_text: media.alt_text
2714
- })) || []),
2715
- place: raw.place,
2716
- poll: raw.poll ?? null,
2717
- quotedStatus,
2718
- quotedStatusId: raw.quotedStatusId ?? raw.legacy?.quoted_status_id_str ?? void 0,
2719
- quotes: raw.legacy?.quote_count ?? 0,
2720
- replies: raw.legacy?.reply_count ?? 0,
2721
- retweets: raw.legacy?.retweet_count ?? 0,
2722
- retweetedStatus,
2723
- retweetedStatusId: raw.legacy?.retweeted_status_id_str ?? void 0,
2724
- text: raw.text ?? raw.legacy?.full_text ?? void 0,
2725
- thread: raw.thread || [],
2726
- timeParsed: raw.timeParsed ? new Date(raw.timeParsed) : raw.legacy?.created_at ? new Date(raw.legacy?.created_at) : void 0,
2727
- timestamp: raw.timestamp ?? (raw.legacy?.created_at ? new Date(raw.legacy.created_at).getTime() / 1e3 : void 0),
2728
- urls: raw.urls ?? raw.legacy?.entities?.urls ?? [],
2729
- userId: raw.userId ?? raw.legacy?.user_id_str ?? void 0,
2730
- username: raw.username ?? raw.core?.user_results?.result?.legacy?.screen_name ?? void 0,
2731
- videos: raw.videos ?? raw.legacy?.entities?.media?.filter(
2732
- (media) => media.type === "video"
2733
- ) ?? [],
2734
- views: raw.views?.count ? Number(raw.views.count) : 0,
2735
- sensitiveContent: raw.sensitiveContent
2736
- };
2737
- return t;
2738
- }
2739
2054
  async init() {
2740
2055
  const apiKey = this.state?.TWITTER_API_KEY || this.runtime.getSetting("TWITTER_API_KEY");
2741
2056
  const apiSecretKey = this.state?.TWITTER_API_SECRET_KEY || this.runtime.getSetting("TWITTER_API_SECRET_KEY");
@@ -2845,7 +2160,7 @@ var _ClientBase = class _ClientBase {
2845
2160
  this.profile.id,
2846
2161
  count
2847
2162
  );
2848
- return homeTimeline.tweets.map((t) => this.parseTweet(t));
2163
+ return homeTimeline.tweets;
2849
2164
  }
2850
2165
  /**
2851
2166
  * Fetch timeline for twitter account, optionally only from followed accounts
@@ -2853,8 +2168,7 @@ var _ClientBase = class _ClientBase {
2853
2168
  async fetchHomeTimeline(count, following) {
2854
2169
  logger.debug("fetching home timeline");
2855
2170
  const homeTimeline = following ? await this.twitterClient.fetchFollowingTimeline(count, []) : await this.twitterClient.fetchHomeTimeline(count, []);
2856
- const processedTimeline = homeTimeline.filter((t) => t.__typename !== "TweetWithVisibilityResults").map((tweet) => this.parseTweet(tweet));
2857
- return processedTimeline;
2171
+ return homeTimeline;
2858
2172
  }
2859
2173
  async fetchSearchTweets(query, maxTweets, searchMode, cursor) {
2860
2174
  try {
@@ -3162,7 +2476,7 @@ import {
3162
2476
  ContentType,
3163
2477
  EventType,
3164
2478
  createUniqueUuid as createUniqueUuid3,
3165
- logger as logger3
2479
+ logger as logger4
3166
2480
  } from "@elizaos/core";
3167
2481
 
3168
2482
  // src/utils.ts
@@ -3170,9 +2484,14 @@ import fs from "fs";
3170
2484
  import path from "path";
3171
2485
  import {
3172
2486
  createUniqueUuid as createUniqueUuid2,
3173
- logger as logger2,
2487
+ logger as logger3,
3174
2488
  truncateToCompleteSentence
3175
2489
  } from "@elizaos/core";
2490
+
2491
+ // src/utils/error-handler.ts
2492
+ import { logger as logger2 } from "@elizaos/core";
2493
+
2494
+ // src/utils.ts
3176
2495
  async function sendTweet(client, text, mediaData = [], tweetToReplyTo) {
3177
2496
  const isNoteTweet = text.length > TWEET_MAX_LENGTH;
3178
2497
  const postText = isNoteTweet ? truncateToCompleteSentence(text, TWEET_MAX_LENGTH) : text;
@@ -3183,9 +2502,9 @@ async function sendTweet(client, text, mediaData = [], tweetToReplyTo) {
3183
2502
  tweetToReplyTo,
3184
2503
  mediaData
3185
2504
  );
3186
- logger2.log("Successfully posted Tweet");
2505
+ logger3.log("Successfully posted Tweet");
3187
2506
  } catch (error) {
3188
- logger2.error("Error posting Tweet:", error);
2507
+ logger3.error("Error posting Tweet:", error);
3189
2508
  throw error;
3190
2509
  }
3191
2510
  try {
@@ -3197,14 +2516,14 @@ async function sendTweet(client, text, mediaData = [], tweetToReplyTo) {
3197
2516
  }
3198
2517
  await client.cacheLatestCheckedTweetId();
3199
2518
  await client.cacheTweet(tweetResult);
3200
- logger2.log("Successfully posted a tweet", tweetResult.id);
2519
+ logger3.log("Successfully posted a tweet", tweetResult.id);
3201
2520
  return tweetResult;
3202
2521
  }
3203
2522
  } catch (error) {
3204
- logger2.error("Error parsing tweet response:", error);
2523
+ logger3.error("Error parsing tweet response:", error);
3205
2524
  throw error;
3206
2525
  }
3207
- logger2.error("No valid response from Twitter API");
2526
+ logger3.error("No valid response from Twitter API");
3208
2527
  throw new Error("Failed to send tweet - no valid response");
3209
2528
  }
3210
2529
  var parseActionResponseFromText = (text) => {
@@ -7370,7 +6689,7 @@ var TwitterInteractionClient = class {
7370
6689
  * Asynchronously handles Twitter interactions by checking for mentions, processing tweets, and updating the last checked tweet ID.
7371
6690
  */
7372
6691
  async handleTwitterInteractions() {
7373
- logger3.log("Checking Twitter interactions");
6692
+ logger4.log("Checking Twitter interactions");
7374
6693
  const twitterUsername = this.client.profile?.username;
7375
6694
  try {
7376
6695
  const cursorKey = `twitter/${twitterUsername}/mention_cursor`;
@@ -7389,9 +6708,9 @@ var TwitterInteractionClient = class {
7389
6708
  }
7390
6709
  await this.processMentionTweets(mentionCandidates);
7391
6710
  await this.client.cacheLatestCheckedTweetId();
7392
- logger3.log("Finished checking Twitter interactions");
6711
+ logger4.log("Finished checking Twitter interactions");
7393
6712
  } catch (error) {
7394
- logger3.error("Error handling Twitter interactions:", error);
6713
+ logger4.error("Error handling Twitter interactions:", error);
7395
6714
  }
7396
6715
  }
7397
6716
  /**
@@ -7405,7 +6724,7 @@ var TwitterInteractionClient = class {
7405
6724
  * Note: MENTION_RECEIVED is currently disabled (see TODO below)
7406
6725
  */
7407
6726
  async processMentionTweets(mentionCandidates) {
7408
- logger3.log(
6727
+ logger4.log(
7409
6728
  "Completed checking mentioned tweets:",
7410
6729
  mentionCandidates.length
7411
6730
  );
@@ -7419,7 +6738,7 @@ var TwitterInteractionClient = class {
7419
6738
  targetUsersConfig
7420
6739
  );
7421
6740
  if (!shouldTarget) {
7422
- logger3.log(
6741
+ logger4.log(
7423
6742
  `Skipping tweet from @${tweet.username} - not in target users list`
7424
6743
  );
7425
6744
  }
@@ -7431,7 +6750,7 @@ var TwitterInteractionClient = class {
7431
6750
  const tweetId = createUniqueUuid3(this.runtime, tweet.id);
7432
6751
  const existingResponse = await this.runtime.getMemoryById(tweetId);
7433
6752
  if (existingResponse) {
7434
- logger3.log(`Already responded to tweet ${tweet.id}, skipping`);
6753
+ logger4.log(`Already responded to tweet ${tweet.id}, skipping`);
7435
6754
  continue;
7436
6755
  }
7437
6756
  const conversationRoomId = createUniqueUuid3(
@@ -7448,12 +6767,12 @@ var TwitterInteractionClient = class {
7448
6767
  (memory2) => memory2.content?.inReplyTo === tweetId || memory2.content?.source === "twitter" && memory2.agentId === this.runtime.agentId && memory2.content?.inReplyTo === tweetId
7449
6768
  );
7450
6769
  if (hasExistingReply) {
7451
- logger3.log(
6770
+ logger4.log(
7452
6771
  `Already replied to tweet ${tweet.id} (found existing reply), skipping`
7453
6772
  );
7454
6773
  continue;
7455
6774
  }
7456
- logger3.log("New Tweet found", tweet.permanentUrl);
6775
+ logger4.log("New Tweet found", tweet.permanentUrl);
7457
6776
  const entityId = createUniqueUuid3(
7458
6777
  this.runtime,
7459
6778
  tweet.userId === this.client.profile.id ? this.runtime.agentId : tweet.userId
@@ -7668,23 +6987,23 @@ var TwitterInteractionClient = class {
7668
6987
  thread
7669
6988
  }) {
7670
6989
  if (!message.content.text) {
7671
- logger3.log("Skipping Tweet with no text", tweet.id);
6990
+ logger4.log("Skipping Tweet with no text", tweet.id);
7672
6991
  return { text: "", actions: ["IGNORE"] };
7673
6992
  }
7674
6993
  const callback = async (response, tweetId) => {
7675
6994
  try {
7676
6995
  if (!response.text) {
7677
- logger3.warn("No text content in response, skipping tweet reply");
6996
+ logger4.warn("No text content in response, skipping tweet reply");
7678
6997
  return [];
7679
6998
  }
7680
6999
  const tweetToReplyTo = tweetId || tweet.id;
7681
7000
  if (this.isDryRun) {
7682
- logger3.info(
7001
+ logger4.info(
7683
7002
  `[DRY RUN] Would have replied to ${tweet.username} with: ${response.text}`
7684
7003
  );
7685
7004
  return [];
7686
7005
  }
7687
- logger3.info(`Replying to tweet ${tweetToReplyTo}`);
7006
+ logger4.info(`Replying to tweet ${tweetToReplyTo}`);
7688
7007
  const tweetResult = await sendTweet(
7689
7008
  this.client,
7690
7009
  response.text,
@@ -7694,7 +7013,7 @@ var TwitterInteractionClient = class {
7694
7013
  if (!tweetResult) {
7695
7014
  throw new Error("Failed to get tweet result from response");
7696
7015
  }
7697
- const responseId = createUniqueUuid3(this.runtime, tweetResult.rest_id);
7016
+ const responseId = createUniqueUuid3(this.runtime, tweetResult.id);
7698
7017
  const responseMemory = {
7699
7018
  id: responseId,
7700
7019
  entityId: this.runtime.agentId,
@@ -7710,7 +7029,7 @@ var TwitterInteractionClient = class {
7710
7029
  await this.runtime.createMemory(responseMemory, "messages");
7711
7030
  return [responseMemory];
7712
7031
  } catch (error) {
7713
- logger3.error("Error replying to tweet:", error);
7032
+ logger4.error("Error replying to tweet:", error);
7714
7033
  return [];
7715
7034
  }
7716
7035
  };
@@ -7733,17 +7052,17 @@ var TwitterInteractionClient = class {
7733
7052
  const thread = [];
7734
7053
  const visited = /* @__PURE__ */ new Set();
7735
7054
  async function processThread(currentTweet, depth = 0) {
7736
- logger3.log("Processing tweet:", {
7055
+ logger4.log("Processing tweet:", {
7737
7056
  id: currentTweet.id,
7738
7057
  inReplyToStatusId: currentTweet.inReplyToStatusId,
7739
7058
  depth
7740
7059
  });
7741
7060
  if (!currentTweet) {
7742
- logger3.log("No current tweet found for thread building");
7061
+ logger4.log("No current tweet found for thread building");
7743
7062
  return;
7744
7063
  }
7745
7064
  if (depth >= maxReplies) {
7746
- logger3.log("Reached maximum reply depth", depth);
7065
+ logger4.log("Reached maximum reply depth", depth);
7747
7066
  return;
7748
7067
  }
7749
7068
  const memory = await this.runtime.getMemoryById(
@@ -7781,37 +7100,37 @@ var TwitterInteractionClient = class {
7781
7100
  );
7782
7101
  }
7783
7102
  if (visited.has(currentTweet.id)) {
7784
- logger3.log("Already visited tweet:", currentTweet.id);
7103
+ logger4.log("Already visited tweet:", currentTweet.id);
7785
7104
  return;
7786
7105
  }
7787
7106
  visited.add(currentTweet.id);
7788
7107
  thread.unshift(currentTweet);
7789
7108
  if (currentTweet.inReplyToStatusId) {
7790
- logger3.log("Fetching parent tweet:", currentTweet.inReplyToStatusId);
7109
+ logger4.log("Fetching parent tweet:", currentTweet.inReplyToStatusId);
7791
7110
  try {
7792
7111
  const parentTweet = await this.twitterClient.getTweet(
7793
7112
  currentTweet.inReplyToStatusId
7794
7113
  );
7795
7114
  if (parentTweet) {
7796
- logger3.log("Found parent tweet:", {
7115
+ logger4.log("Found parent tweet:", {
7797
7116
  id: parentTweet.id,
7798
7117
  text: parentTweet.text?.slice(0, 50)
7799
7118
  });
7800
7119
  await processThread(parentTweet, depth + 1);
7801
7120
  } else {
7802
- logger3.log(
7121
+ logger4.log(
7803
7122
  "No parent tweet found for:",
7804
7123
  currentTweet.inReplyToStatusId
7805
7124
  );
7806
7125
  }
7807
7126
  } catch (error) {
7808
- logger3.log("Error fetching parent tweet:", {
7127
+ logger4.log("Error fetching parent tweet:", {
7809
7128
  tweetId: currentTweet.inReplyToStatusId,
7810
7129
  error
7811
7130
  });
7812
7131
  }
7813
7132
  } else {
7814
- logger3.log("Reached end of reply chain at:", currentTweet.id);
7133
+ logger4.log("Reached end of reply chain at:", currentTweet.id);
7815
7134
  }
7816
7135
  }
7817
7136
  await processThread.bind(this)(tweet, 0);
@@ -7837,7 +7156,7 @@ import {
7837
7156
  ChannelType as ChannelType3,
7838
7157
  EventType as EventType2,
7839
7158
  createUniqueUuid as createUniqueUuid4,
7840
- logger as logger4
7159
+ logger as logger5
7841
7160
  } from "@elizaos/core";
7842
7161
  var TwitterPostClient = class {
7843
7162
  /**
@@ -7850,17 +7169,20 @@ var TwitterPostClient = class {
7850
7169
  this.client = client;
7851
7170
  this.state = state;
7852
7171
  this.runtime = runtime;
7853
- this.isDryRun = this.state?.TWITTER_DRY_RUN || this.runtime.getSetting("TWITTER_DRY_RUN");
7854
- logger4.log("Twitter Client Configuration:");
7855
- logger4.log(`- Dry Run Mode: ${this.isDryRun ? "Enabled" : "Disabled"}`);
7856
- logger4.log(
7172
+ const dryRunSetting = this.state?.TWITTER_DRY_RUN ?? this.runtime.getSetting("TWITTER_DRY_RUN");
7173
+ this.isDryRun = dryRunSetting === true || dryRunSetting === "true" || typeof dryRunSetting === "string" && dryRunSetting.toLowerCase() === "true";
7174
+ logger5.log("Twitter Client Configuration:");
7175
+ logger5.log(`- Dry Run Mode: ${this.isDryRun ? "Enabled" : "Disabled"}`);
7176
+ logger5.log(
7857
7177
  `- Post Interval: ${this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN") || 90}-${this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX") || 180} minutes`
7858
7178
  );
7859
- logger4.log(
7860
- `- Post Immediately: ${this.state?.TWITTER_POST_IMMEDIATELY || this.runtime.getSetting("TWITTER_POST_IMMEDIATELY") ? "enabled" : "disabled"}`
7179
+ const postImmediatelySetting = this.state?.TWITTER_POST_IMMEDIATELY ?? this.runtime.getSetting("TWITTER_POST_IMMEDIATELY");
7180
+ const isPostImmediately = postImmediatelySetting === true || postImmediatelySetting === "true" || typeof postImmediatelySetting === "string" && postImmediatelySetting.toLowerCase() === "true";
7181
+ logger5.log(
7182
+ `- Post Immediately: ${isPostImmediately ? "enabled" : "disabled"}`
7861
7183
  );
7862
7184
  if (this.isDryRun) {
7863
- logger4.log(
7185
+ logger5.log(
7864
7186
  "Twitter client initialized in dry run mode - no actual tweets should be posted"
7865
7187
  );
7866
7188
  }
@@ -7869,7 +7191,7 @@ var TwitterPostClient = class {
7869
7191
  * Starts the Twitter post client, setting up a loop to periodically generate new tweets.
7870
7192
  */
7871
7193
  async start() {
7872
- logger4.log("Starting Twitter post client...");
7194
+ logger5.log("Starting Twitter post client...");
7873
7195
  const generateNewTweetLoop = async () => {
7874
7196
  const minPostMinutes = this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN") || 90;
7875
7197
  const maxPostMinutes = this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX") || 180;
@@ -7879,7 +7201,9 @@ var TwitterPostClient = class {
7879
7201
  setTimeout(generateNewTweetLoop, interval);
7880
7202
  };
7881
7203
  setTimeout(generateNewTweetLoop, 60 * 1e3);
7882
- if (this.state?.TWITTER_POST_IMMEDIATELY || this.runtime.getSetting("TWITTER_POST_IMMEDIATELY")) {
7204
+ const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY ?? this.runtime.getSetting("TWITTER_POST_IMMEDIATELY");
7205
+ const shouldPostImmediately = postImmediately === true || postImmediately === "true" || typeof postImmediately === "string" && postImmediately.toLowerCase() === "true";
7206
+ if (shouldPostImmediately) {
7883
7207
  await new Promise((resolve) => setTimeout(resolve, 1e3));
7884
7208
  await this.generateNewTweet();
7885
7209
  }
@@ -7889,33 +7213,38 @@ var TwitterPostClient = class {
7889
7213
  * This approach aligns with our platform-independent architecture.
7890
7214
  */
7891
7215
  async generateNewTweet() {
7216
+ logger5.info("Attempting to generate new tweet...");
7892
7217
  try {
7893
7218
  const userId = this.client.profile?.id;
7894
7219
  if (!userId) {
7895
- logger4.error("Cannot generate tweet: Twitter profile not available");
7220
+ logger5.error("Cannot generate tweet: Twitter profile not available");
7896
7221
  return;
7897
7222
  }
7223
+ logger5.info(`Generating tweet for user: ${this.client.profile?.username} (${userId})`);
7898
7224
  const worldId = createUniqueUuid4(this.runtime, userId);
7899
7225
  const roomId = createUniqueUuid4(this.runtime, `${userId}-home`);
7900
7226
  const callback = async (content) => {
7227
+ logger5.info("Tweet generation callback triggered");
7901
7228
  try {
7902
7229
  if (this.isDryRun) {
7903
- logger4.info(`[DRY RUN] Would post tweet: ${content.text}`);
7230
+ logger5.info(`[DRY RUN] Would post tweet: ${content.text}`);
7904
7231
  return [];
7905
7232
  }
7906
7233
  if (content.text.includes("Error: Missing")) {
7907
- logger4.error("Error: Missing some context", content);
7234
+ logger5.error("Error: Missing some context", content);
7908
7235
  return [];
7909
7236
  }
7237
+ logger5.info(`Posting tweet: ${content.text}`);
7910
7238
  const result = await this.postToTwitter(
7911
7239
  content.text,
7912
7240
  content.mediaData
7913
7241
  );
7914
7242
  if (result === null) {
7915
- logger4.info("Skipped posting duplicate tweet");
7243
+ logger5.info("Skipped posting duplicate tweet");
7916
7244
  return [];
7917
7245
  }
7918
- const tweetId = result.rest_id || result.id_str || result.legacy?.id_str;
7246
+ const tweetId = result.id;
7247
+ logger5.info(`Tweet posted successfully! ID: ${tweetId}`);
7919
7248
  if (result) {
7920
7249
  const postedTweetId = createUniqueUuid4(this.runtime, tweetId);
7921
7250
  const postedMemory = {
@@ -7940,10 +7269,11 @@ var TwitterPostClient = class {
7940
7269
  }
7941
7270
  return [];
7942
7271
  } catch (error) {
7943
- logger4.error("Error posting tweet:", error, content);
7272
+ logger5.error("Error posting tweet:", error, content);
7944
7273
  return [];
7945
7274
  }
7946
7275
  };
7276
+ logger5.info("Emitting POST_GENERATED event to trigger content generation...");
7947
7277
  this.runtime.emitEvent(
7948
7278
  [EventType2.POST_GENERATED, "TWITTER_POST_GENERATED" /* POST_GENERATED */],
7949
7279
  {
@@ -7955,8 +7285,9 @@ var TwitterPostClient = class {
7955
7285
  source: "twitter"
7956
7286
  }
7957
7287
  );
7288
+ logger5.info("POST_GENERATED event emitted successfully");
7958
7289
  } catch (error) {
7959
- logger4.error("Error generating tweet:", error);
7290
+ logger5.error("Error generating tweet:", error);
7960
7291
  }
7961
7292
  }
7962
7293
  /**
@@ -7973,7 +7304,7 @@ var TwitterPostClient = class {
7973
7304
  if (lastPost) {
7974
7305
  const lastTweet = await this.client.getTweet(lastPost.id);
7975
7306
  if (lastTweet && lastTweet.text === text) {
7976
- logger4.warn(
7307
+ logger5.warn(
7977
7308
  "Tweet is a duplicate of the last post. Skipping to avoid duplicate."
7978
7309
  );
7979
7310
  return null;
@@ -7983,22 +7314,22 @@ var TwitterPostClient = class {
7983
7314
  if (mediaData && mediaData.length > 0) {
7984
7315
  for (const media of mediaData) {
7985
7316
  try {
7986
- logger4.warn(
7317
+ logger5.warn(
7987
7318
  "Media upload not currently supported with the modern Twitter API"
7988
7319
  );
7989
7320
  } catch (error) {
7990
- logger4.error("Error uploading media:", error);
7321
+ logger5.error("Error uploading media:", error);
7991
7322
  }
7992
7323
  }
7993
7324
  }
7994
7325
  const result = await sendTweet(this.client, text, mediaData);
7995
7326
  if (!result) {
7996
- logger4.error("Error sending tweet; Bad response:");
7327
+ logger5.error("Error sending tweet; Bad response:");
7997
7328
  return null;
7998
7329
  }
7999
7330
  return result;
8000
7331
  } catch (error) {
8001
- logger4.error("Error posting to Twitter:", error);
7332
+ logger5.error("Error posting to Twitter:", error);
8002
7333
  throw error;
8003
7334
  }
8004
7335
  }
@@ -8014,7 +7345,7 @@ import {
8014
7345
  ModelType,
8015
7346
  parseKeyValueXml
8016
7347
  } from "@elizaos/core";
8017
- import { logger as logger5 } from "@elizaos/core";
7348
+ import { logger as logger6 } from "@elizaos/core";
8018
7349
 
8019
7350
  // src/templates.ts
8020
7351
  var twitterActionTemplate = `
@@ -8099,30 +7430,7 @@ var TwitterTimelineClient = class {
8099
7430
  async getTimeline(count) {
8100
7431
  const twitterUsername = this.client.profile?.username;
8101
7432
  const homeTimeline = this.timelineType === "following" /* Following */ ? await this.twitterClient.fetchFollowingTimeline(count, []) : await this.twitterClient.fetchHomeTimeline(count, []);
8102
- return homeTimeline.map((tweet) => ({
8103
- id: tweet.rest_id,
8104
- name: tweet.core?.user_results?.result?.legacy?.name,
8105
- username: tweet.core?.user_results?.result?.legacy?.screen_name,
8106
- text: tweet.legacy?.full_text,
8107
- inReplyToStatusId: tweet.legacy?.in_reply_to_status_id_str,
8108
- timestamp: new Date(tweet.legacy?.created_at).getTime() / 1e3,
8109
- userId: tweet.legacy?.user_id_str,
8110
- conversationId: tweet.legacy?.conversation_id_str,
8111
- permanentUrl: `https://twitter.com/${tweet.core?.user_results?.result?.legacy?.screen_name}/status/${tweet.rest_id}`,
8112
- hashtags: tweet.legacy?.entities?.hashtags || [],
8113
- mentions: tweet.legacy?.entities?.user_mentions || [],
8114
- photos: tweet.legacy?.entities?.media?.filter((media) => media.type === "photo").map((media) => ({
8115
- id: media.id_str,
8116
- url: media.media_url_https,
8117
- // Store media_url_https as url
8118
- alt_text: media.alt_text
8119
- })) || [],
8120
- thread: tweet.thread || [],
8121
- urls: tweet.legacy?.entities?.urls || [],
8122
- videos: tweet.legacy?.entities?.media?.filter(
8123
- (media) => media.type === "video"
8124
- ) || []
8125
- })).filter((tweet) => tweet.username !== twitterUsername);
7433
+ return homeTimeline.filter((tweet) => tweet.username !== twitterUsername);
8126
7434
  }
8127
7435
  createTweetId(runtime, tweet) {
8128
7436
  return createUniqueUuid5(runtime, tweet.id);
@@ -8178,7 +7486,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
8178
7486
  }
8179
7487
  );
8180
7488
  if (!actionResponse) {
8181
- logger5.log(`No valid actions generated for tweet ${tweet.id}`);
7489
+ logger6.log(`No valid actions generated for tweet ${tweet.id}`);
8182
7490
  continue;
8183
7491
  }
8184
7492
  const { actions } = parseActionResponseFromText(actionResponse.trim());
@@ -8189,7 +7497,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
8189
7497
  roomId
8190
7498
  });
8191
7499
  } catch (error) {
8192
- logger5.error(`Error processing tweet ${tweet.id}:`, error);
7500
+ logger6.error(`Error processing tweet ${tweet.id}:`, error);
8193
7501
  continue;
8194
7502
  }
8195
7503
  }
@@ -8236,7 +7544,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
8236
7544
  this.handleReplyAction(tweet);
8237
7545
  }
8238
7546
  } catch (error) {
8239
- logger5.error(`Error processing tweet ${tweet.id}:`, error);
7547
+ logger6.error(`Error processing tweet ${tweet.id}:`, error);
8240
7548
  continue;
8241
7549
  }
8242
7550
  }
@@ -8267,17 +7575,17 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
8267
7575
  async handleLikeAction(tweet) {
8268
7576
  try {
8269
7577
  await this.twitterClient.likeTweet(tweet.id);
8270
- logger5.log(`Liked tweet ${tweet.id}`);
7578
+ logger6.log(`Liked tweet ${tweet.id}`);
8271
7579
  } catch (error) {
8272
- logger5.error(`Error liking tweet ${tweet.id}:`, error);
7580
+ logger6.error(`Error liking tweet ${tweet.id}:`, error);
8273
7581
  }
8274
7582
  }
8275
7583
  async handleRetweetAction(tweet) {
8276
7584
  try {
8277
7585
  await this.twitterClient.retweet(tweet.id);
8278
- logger5.log(`Retweeted tweet ${tweet.id}`);
7586
+ logger6.log(`Retweeted tweet ${tweet.id}`);
8279
7587
  } catch (error) {
8280
- logger5.error(`Error retweeting tweet ${tweet.id}:`, error);
7588
+ logger6.error(`Error retweeting tweet ${tweet.id}:`, error);
8281
7589
  }
8282
7590
  }
8283
7591
  async handleQuoteAction(tweet) {
@@ -8304,11 +7612,11 @@ ${tweet.text}`;
8304
7612
  const body = await result.json();
8305
7613
  const tweetResult = body?.data?.create_tweet?.tweet_results?.result || body?.data || body;
8306
7614
  if (tweetResult) {
8307
- logger5.log("Successfully posted quote tweet");
7615
+ logger6.log("Successfully posted quote tweet");
8308
7616
  } else {
8309
- logger5.error("Quote tweet creation failed:", body);
7617
+ logger6.error("Quote tweet creation failed:", body);
8310
7618
  }
8311
- const tweetId = tweetResult?.rest_id || tweetResult?.id || Date.now().toString();
7619
+ const tweetId = tweetResult?.id || Date.now().toString();
8312
7620
  const responseId = createUniqueUuid5(this.runtime, tweetId);
8313
7621
  const responseMemory = {
8314
7622
  id: responseId,
@@ -8324,7 +7632,7 @@ ${tweet.text}`;
8324
7632
  await this.runtime.createMemory(responseMemory, "messages");
8325
7633
  }
8326
7634
  } catch (error) {
8327
- logger5.error("Error in quote tweet generation:", error);
7635
+ logger6.error("Error in quote tweet generation:", error);
8328
7636
  }
8329
7637
  }
8330
7638
  async handleReplyAction(tweet) {
@@ -8351,7 +7659,7 @@ ${tweet.text}`;
8351
7659
  if (!tweetResult) {
8352
7660
  throw new Error("Failed to get tweet result from response");
8353
7661
  }
8354
- const responseId = createUniqueUuid5(this.runtime, tweetResult.rest_id);
7662
+ const responseId = createUniqueUuid5(this.runtime, tweetResult.id);
8355
7663
  const responseMemory = {
8356
7664
  id: responseId,
8357
7665
  entityId: this.runtime.agentId,
@@ -8366,7 +7674,7 @@ ${tweet.text}`;
8366
7674
  await this.runtime.createMemory(responseMemory, "messages");
8367
7675
  }
8368
7676
  } catch (error) {
8369
- logger5.error("Error in quote tweet generation:", error);
7677
+ logger6.error("Error in quote tweet generation:", error);
8370
7678
  }
8371
7679
  }
8372
7680
  };
@@ -8479,18 +7787,35 @@ console.log(`Twitter plugin loaded with service name: ${TWITTER_SERVICE_NAME}`);
8479
7787
  var TwitterClientInstance = class {
8480
7788
  constructor(runtime, state) {
8481
7789
  this.client = new ClientBase(runtime, state);
8482
- if (runtime.getSetting("TWITTER_POST_ENABLE") === "true") {
7790
+ const postEnableSetting = runtime.getSetting("TWITTER_POST_ENABLE");
7791
+ logger7.info(`TWITTER_POST_ENABLE raw value: "${postEnableSetting}"`);
7792
+ logger7.info(`TWITTER_POST_ENABLE type: ${typeof postEnableSetting}`);
7793
+ const postEnabled = postEnableSetting === true || postEnableSetting === "true" || typeof postEnableSetting === "string" && postEnableSetting.toLowerCase() === "true";
7794
+ if (postEnabled) {
7795
+ logger7.info("Twitter posting is ENABLED - creating post client");
8483
7796
  this.post = new TwitterPostClient(this.client, runtime, state);
7797
+ } else {
7798
+ logger7.info("Twitter posting is DISABLED - set TWITTER_POST_ENABLE=true to enable automatic posting");
8484
7799
  }
8485
- if (runtime.getSetting("TWITTER_SEARCH_ENABLE") !== "false") {
7800
+ const searchEnabledSetting = runtime.getSetting("TWITTER_SEARCH_ENABLE");
7801
+ logger7.info(`TWITTER_SEARCH_ENABLE raw value: "${searchEnabledSetting}"`);
7802
+ const searchEnabled = searchEnabledSetting !== false && searchEnabledSetting !== "false";
7803
+ if (searchEnabled) {
7804
+ logger7.info("Twitter search/interactions are ENABLED");
8486
7805
  this.interaction = new TwitterInteractionClient(
8487
7806
  this.client,
8488
7807
  runtime,
8489
7808
  state
8490
7809
  );
7810
+ } else {
7811
+ logger7.info("Twitter search/interactions are DISABLED");
8491
7812
  }
8492
- if (runtime.getSetting("TWITTER_ENABLE_ACTION_PROCESSING") === "true") {
7813
+ const actionProcessingEnabled = runtime.getSetting("TWITTER_ENABLE_ACTION_PROCESSING") === "true";
7814
+ if (actionProcessingEnabled) {
7815
+ logger7.info("Twitter action processing is ENABLED");
8493
7816
  this.timeline = new TwitterTimelineClient(this.client, runtime, state);
7817
+ } else {
7818
+ logger7.info("Twitter action processing is DISABLED");
8494
7819
  }
8495
7820
  this.service = TwitterService.getInstance();
8496
7821
  }
@@ -8511,7 +7836,7 @@ var _TwitterService = class _TwitterService extends Service {
8511
7836
  try {
8512
7837
  const existingClient = this.getClient(clientId, runtime.agentId);
8513
7838
  if (existingClient) {
8514
- logger6.info(`Twitter client already exists for ${clientId}`);
7839
+ logger7.info(`Twitter client already exists for ${clientId}`);
8515
7840
  return existingClient;
8516
7841
  }
8517
7842
  const client = new TwitterClientInstance(runtime, state);
@@ -8527,10 +7852,10 @@ var _TwitterService = class _TwitterService extends Service {
8527
7852
  }
8528
7853
  this.clients.set(this.getClientKey(clientId, runtime.agentId), client);
8529
7854
  await this.emitServerJoinedEvent(runtime, client);
8530
- logger6.info(`Created Twitter client for ${clientId}`);
7855
+ logger7.info(`Created Twitter client for ${clientId}`);
8531
7856
  return client;
8532
7857
  } catch (error) {
8533
- logger6.error(`Failed to create Twitter client for ${clientId}:`, error);
7858
+ logger7.error(`Failed to create Twitter client for ${clientId}:`, error);
8534
7859
  throw error;
8535
7860
  }
8536
7861
  }
@@ -8542,7 +7867,7 @@ var _TwitterService = class _TwitterService extends Service {
8542
7867
  async emitServerJoinedEvent(runtime, client) {
8543
7868
  try {
8544
7869
  if (!client.client.profile) {
8545
- logger6.warn(
7870
+ logger7.warn(
8546
7871
  "Twitter profile not available yet, can't emit WORLD_JOINED event"
8547
7872
  );
8548
7873
  return;
@@ -8617,9 +7942,9 @@ var _TwitterService = class _TwitterService extends Service {
8617
7942
  source: "twitter"
8618
7943
  }
8619
7944
  );
8620
- logger6.info(`Emitted WORLD_JOINED event for Twitter account ${username}`);
7945
+ logger7.info(`Emitted WORLD_JOINED event for Twitter account ${username}`);
8621
7946
  } catch (error) {
8622
- logger6.error("Failed to emit WORLD_JOINED event for Twitter:", error);
7947
+ logger7.error("Failed to emit WORLD_JOINED event for Twitter:", error);
8623
7948
  }
8624
7949
  }
8625
7950
  getClient(clientId, agentId) {
@@ -8632,9 +7957,9 @@ var _TwitterService = class _TwitterService extends Service {
8632
7957
  try {
8633
7958
  await client.service.stop();
8634
7959
  this.clients.delete(key);
8635
- logger6.info(`Stopped Twitter client for ${clientId}`);
7960
+ logger7.info(`Stopped Twitter client for ${clientId}`);
8636
7961
  } catch (error) {
8637
- logger6.error(`Error stopping Twitter client for ${clientId}:`, error);
7962
+ logger7.error(`Error stopping Twitter client for ${clientId}:`, error);
8638
7963
  }
8639
7964
  }
8640
7965
  }
@@ -8651,7 +7976,7 @@ var _TwitterService = class _TwitterService extends Service {
8651
7976
  );
8652
7977
  try {
8653
7978
  if (config.TWITTER_API_KEY && config.TWITTER_API_SECRET_KEY && config.TWITTER_ACCESS_TOKEN && config.TWITTER_ACCESS_TOKEN_SECRET) {
8654
- logger6.info("Creating default Twitter client from character settings");
7979
+ logger7.info("Creating default Twitter client from character settings");
8655
7980
  await twitterClientManager.createClient(
8656
7981
  runtime,
8657
7982
  runtime.agentId,
@@ -8659,7 +7984,7 @@ var _TwitterService = class _TwitterService extends Service {
8659
7984
  );
8660
7985
  }
8661
7986
  } catch (error) {
8662
- logger6.error("Failed to create default Twitter client:", error);
7987
+ logger7.error("Failed to create default Twitter client:", error);
8663
7988
  throw error;
8664
7989
  }
8665
7990
  return twitterClientManager;
@@ -8673,7 +7998,7 @@ var _TwitterService = class _TwitterService extends Service {
8673
7998
  await client.service.stop();
8674
7999
  this.clients.delete(key);
8675
8000
  } catch (error) {
8676
- logger6.error(`Error stopping Twitter client ${key}:`, error);
8001
+ logger7.error(`Error stopping Twitter client ${key}:`, error);
8677
8002
  }
8678
8003
  }
8679
8004
  }