@memberjunction/actions-bizapps-social 2.112.0 → 2.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/base/base-social.action.d.ts.map +1 -1
  4. package/dist/base/base-social.action.js +24 -18
  5. package/dist/base/base-social.action.js.map +1 -1
  6. package/dist/providers/buffer/buffer-base.action.d.ts.map +1 -1
  7. package/dist/providers/buffer/buffer-base.action.js +34 -35
  8. package/dist/providers/buffer/buffer-base.action.js.map +1 -1
  9. package/dist/providers/facebook/actions/boost-post.action.d.ts.map +1 -1
  10. package/dist/providers/facebook/actions/boost-post.action.js +33 -33
  11. package/dist/providers/facebook/actions/boost-post.action.js.map +1 -1
  12. package/dist/providers/facebook/actions/create-album.action.d.ts.map +1 -1
  13. package/dist/providers/facebook/actions/create-album.action.js +36 -34
  14. package/dist/providers/facebook/actions/create-album.action.js.map +1 -1
  15. package/dist/providers/facebook/actions/create-post.action.d.ts.map +1 -1
  16. package/dist/providers/facebook/actions/create-post.action.js +20 -20
  17. package/dist/providers/facebook/actions/create-post.action.js.map +1 -1
  18. package/dist/providers/facebook/actions/get-page-insights.action.d.ts.map +1 -1
  19. package/dist/providers/facebook/actions/get-page-insights.action.js +27 -25
  20. package/dist/providers/facebook/actions/get-page-insights.action.js.map +1 -1
  21. package/dist/providers/facebook/actions/get-page-posts.action.d.ts.map +1 -1
  22. package/dist/providers/facebook/actions/get-page-posts.action.js +23 -19
  23. package/dist/providers/facebook/actions/get-page-posts.action.js.map +1 -1
  24. package/dist/providers/facebook/actions/get-post-insights.action.d.ts.map +1 -1
  25. package/dist/providers/facebook/actions/get-post-insights.action.js +32 -28
  26. package/dist/providers/facebook/actions/get-post-insights.action.js.map +1 -1
  27. package/dist/providers/facebook/actions/respond-to-comments.action.d.ts.map +1 -1
  28. package/dist/providers/facebook/actions/respond-to-comments.action.js +44 -42
  29. package/dist/providers/facebook/actions/respond-to-comments.action.js.map +1 -1
  30. package/dist/providers/facebook/actions/schedule-post.action.d.ts.map +1 -1
  31. package/dist/providers/facebook/actions/schedule-post.action.js +29 -29
  32. package/dist/providers/facebook/actions/schedule-post.action.js.map +1 -1
  33. package/dist/providers/facebook/actions/search-posts.action.d.ts.map +1 -1
  34. package/dist/providers/facebook/actions/search-posts.action.js +39 -37
  35. package/dist/providers/facebook/actions/search-posts.action.js.map +1 -1
  36. package/dist/providers/facebook/facebook-base.action.d.ts.map +1 -1
  37. package/dist/providers/facebook/facebook-base.action.js +59 -44
  38. package/dist/providers/facebook/facebook-base.action.js.map +1 -1
  39. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.d.ts.map +1 -1
  40. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js +31 -33
  41. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js.map +1 -1
  42. package/dist/providers/hootsuite/actions/create-scheduled-post.action.d.ts.map +1 -1
  43. package/dist/providers/hootsuite/actions/create-scheduled-post.action.js +32 -28
  44. package/dist/providers/hootsuite/actions/create-scheduled-post.action.js.map +1 -1
  45. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.d.ts.map +1 -1
  46. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js +19 -19
  47. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js.map +1 -1
  48. package/dist/providers/hootsuite/actions/get-analytics.action.d.ts.map +1 -1
  49. package/dist/providers/hootsuite/actions/get-analytics.action.js +26 -24
  50. package/dist/providers/hootsuite/actions/get-analytics.action.js.map +1 -1
  51. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.d.ts.map +1 -1
  52. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js +22 -22
  53. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js.map +1 -1
  54. package/dist/providers/hootsuite/actions/get-social-profiles.action.d.ts.map +1 -1
  55. package/dist/providers/hootsuite/actions/get-social-profiles.action.js +34 -32
  56. package/dist/providers/hootsuite/actions/get-social-profiles.action.js.map +1 -1
  57. package/dist/providers/hootsuite/actions/search-posts.action.d.ts.map +1 -1
  58. package/dist/providers/hootsuite/actions/search-posts.action.js +52 -43
  59. package/dist/providers/hootsuite/actions/search-posts.action.js.map +1 -1
  60. package/dist/providers/hootsuite/actions/update-scheduled-post.action.d.ts.map +1 -1
  61. package/dist/providers/hootsuite/actions/update-scheduled-post.action.js +28 -30
  62. package/dist/providers/hootsuite/actions/update-scheduled-post.action.js.map +1 -1
  63. package/dist/providers/hootsuite/hootsuite-base.action.d.ts.map +1 -1
  64. package/dist/providers/hootsuite/hootsuite-base.action.js +20 -18
  65. package/dist/providers/hootsuite/hootsuite-base.action.js.map +1 -1
  66. package/dist/providers/instagram/actions/create-post.action.d.ts.map +1 -1
  67. package/dist/providers/instagram/actions/create-post.action.js +26 -27
  68. package/dist/providers/instagram/actions/create-post.action.js.map +1 -1
  69. package/dist/providers/instagram/actions/create-story.action.d.ts.map +1 -1
  70. package/dist/providers/instagram/actions/create-story.action.js +35 -35
  71. package/dist/providers/instagram/actions/create-story.action.js.map +1 -1
  72. package/dist/providers/instagram/actions/get-account-insights.action.d.ts.map +1 -1
  73. package/dist/providers/instagram/actions/get-account-insights.action.js +59 -38
  74. package/dist/providers/instagram/actions/get-account-insights.action.js.map +1 -1
  75. package/dist/providers/instagram/actions/get-business-posts.action.d.ts.map +1 -1
  76. package/dist/providers/instagram/actions/get-business-posts.action.js +29 -29
  77. package/dist/providers/instagram/actions/get-business-posts.action.js.map +1 -1
  78. package/dist/providers/instagram/actions/get-comments.action.d.ts.map +1 -1
  79. package/dist/providers/instagram/actions/get-comments.action.js +36 -36
  80. package/dist/providers/instagram/actions/get-comments.action.js.map +1 -1
  81. package/dist/providers/instagram/actions/get-post-insights.action.d.ts.map +1 -1
  82. package/dist/providers/instagram/actions/get-post-insights.action.js +25 -23
  83. package/dist/providers/instagram/actions/get-post-insights.action.js.map +1 -1
  84. package/dist/providers/instagram/actions/schedule-post.action.d.ts.map +1 -1
  85. package/dist/providers/instagram/actions/schedule-post.action.js +25 -25
  86. package/dist/providers/instagram/actions/schedule-post.action.js.map +1 -1
  87. package/dist/providers/instagram/actions/search-posts.action.d.ts.map +1 -1
  88. package/dist/providers/instagram/actions/search-posts.action.js +60 -56
  89. package/dist/providers/instagram/actions/search-posts.action.js.map +1 -1
  90. package/dist/providers/instagram/instagram-base.action.d.ts.map +1 -1
  91. package/dist/providers/instagram/instagram-base.action.js +27 -25
  92. package/dist/providers/instagram/instagram-base.action.js.map +1 -1
  93. package/dist/providers/linkedin/actions/create-article.action.d.ts.map +1 -1
  94. package/dist/providers/linkedin/actions/create-article.action.js +45 -55
  95. package/dist/providers/linkedin/actions/create-article.action.js.map +1 -1
  96. package/dist/providers/linkedin/actions/create-post.action.d.ts.map +1 -1
  97. package/dist/providers/linkedin/actions/create-post.action.js +29 -31
  98. package/dist/providers/linkedin/actions/create-post.action.js.map +1 -1
  99. package/dist/providers/linkedin/actions/get-followers.action.d.ts.map +1 -1
  100. package/dist/providers/linkedin/actions/get-followers.action.js +28 -28
  101. package/dist/providers/linkedin/actions/get-followers.action.js.map +1 -1
  102. package/dist/providers/linkedin/actions/get-organization-posts.action.d.ts.map +1 -1
  103. package/dist/providers/linkedin/actions/get-organization-posts.action.js +20 -20
  104. package/dist/providers/linkedin/actions/get-organization-posts.action.js.map +1 -1
  105. package/dist/providers/linkedin/actions/get-personal-posts.action.d.ts.map +1 -1
  106. package/dist/providers/linkedin/actions/get-personal-posts.action.js +19 -19
  107. package/dist/providers/linkedin/actions/get-personal-posts.action.js.map +1 -1
  108. package/dist/providers/linkedin/actions/get-post-analytics.action.d.ts.map +1 -1
  109. package/dist/providers/linkedin/actions/get-post-analytics.action.js +23 -25
  110. package/dist/providers/linkedin/actions/get-post-analytics.action.js.map +1 -1
  111. package/dist/providers/linkedin/actions/schedule-post.action.d.ts.map +1 -1
  112. package/dist/providers/linkedin/actions/schedule-post.action.js +30 -32
  113. package/dist/providers/linkedin/actions/schedule-post.action.js.map +1 -1
  114. package/dist/providers/linkedin/actions/search-posts.action.d.ts.map +1 -1
  115. package/dist/providers/linkedin/actions/search-posts.action.js +30 -28
  116. package/dist/providers/linkedin/actions/search-posts.action.js.map +1 -1
  117. package/dist/providers/linkedin/linkedin-base.action.d.ts.map +1 -1
  118. package/dist/providers/linkedin/linkedin-base.action.js +38 -33
  119. package/dist/providers/linkedin/linkedin-base.action.js.map +1 -1
  120. package/dist/providers/tiktok/tiktok-base.action.d.ts.map +1 -1
  121. package/dist/providers/tiktok/tiktok-base.action.js +26 -25
  122. package/dist/providers/tiktok/tiktok-base.action.js.map +1 -1
  123. package/dist/providers/twitter/actions/create-thread.action.d.ts.map +1 -1
  124. package/dist/providers/twitter/actions/create-thread.action.js +29 -25
  125. package/dist/providers/twitter/actions/create-thread.action.js.map +1 -1
  126. package/dist/providers/twitter/actions/create-tweet.action.d.ts.map +1 -1
  127. package/dist/providers/twitter/actions/create-tweet.action.js +23 -23
  128. package/dist/providers/twitter/actions/create-tweet.action.js.map +1 -1
  129. package/dist/providers/twitter/actions/delete-tweet.action.d.ts.map +1 -1
  130. package/dist/providers/twitter/actions/delete-tweet.action.js +19 -19
  131. package/dist/providers/twitter/actions/delete-tweet.action.js.map +1 -1
  132. package/dist/providers/twitter/actions/get-analytics.action.d.ts.map +1 -1
  133. package/dist/providers/twitter/actions/get-analytics.action.js +47 -40
  134. package/dist/providers/twitter/actions/get-analytics.action.js.map +1 -1
  135. package/dist/providers/twitter/actions/get-mentions.action.d.ts.map +1 -1
  136. package/dist/providers/twitter/actions/get-mentions.action.js +31 -30
  137. package/dist/providers/twitter/actions/get-mentions.action.js.map +1 -1
  138. package/dist/providers/twitter/actions/get-timeline.action.d.ts.map +1 -1
  139. package/dist/providers/twitter/actions/get-timeline.action.js +29 -29
  140. package/dist/providers/twitter/actions/get-timeline.action.js.map +1 -1
  141. package/dist/providers/twitter/actions/schedule-tweet.action.d.ts.map +1 -1
  142. package/dist/providers/twitter/actions/schedule-tweet.action.js +26 -26
  143. package/dist/providers/twitter/actions/schedule-tweet.action.js.map +1 -1
  144. package/dist/providers/twitter/actions/search-tweets.action.d.ts.map +1 -1
  145. package/dist/providers/twitter/actions/search-tweets.action.js +58 -56
  146. package/dist/providers/twitter/actions/search-tweets.action.js.map +1 -1
  147. package/dist/providers/twitter/twitter-base.action.d.ts.map +1 -1
  148. package/dist/providers/twitter/twitter-base.action.js +68 -58
  149. package/dist/providers/twitter/twitter-base.action.js.map +1 -1
  150. package/dist/providers/youtube/youtube-base.action.d.ts +1 -1
  151. package/dist/providers/youtube/youtube-base.action.d.ts.map +1 -1
  152. package/dist/providers/youtube/youtube-base.action.js +25 -22
  153. package/dist/providers/youtube/youtube-base.action.js.map +1 -1
  154. package/package.json +6 -5
  155. package/src/base/base-social.action.ts +224 -217
  156. package/src/providers/buffer/buffer-base.action.ts +441 -435
  157. package/src/providers/facebook/actions/boost-post.action.ts +386 -350
  158. package/src/providers/facebook/actions/create-album.action.ts +307 -291
  159. package/src/providers/facebook/actions/create-post.action.ts +227 -224
  160. package/src/providers/facebook/actions/get-page-insights.action.ts +403 -383
  161. package/src/providers/facebook/actions/get-page-posts.action.ts +225 -214
  162. package/src/providers/facebook/actions/get-post-insights.action.ts +316 -300
  163. package/src/providers/facebook/actions/respond-to-comments.action.ts +336 -319
  164. package/src/providers/facebook/actions/schedule-post.action.ts +292 -289
  165. package/src/providers/facebook/actions/search-posts.action.ts +413 -399
  166. package/src/providers/facebook/facebook-base.action.ts +670 -653
  167. package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
  168. package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +189 -184
  169. package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +161 -160
  170. package/src/providers/hootsuite/actions/get-analytics.action.ts +254 -249
  171. package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +207 -206
  172. package/src/providers/hootsuite/actions/get-social-profiles.action.ts +205 -206
  173. package/src/providers/hootsuite/actions/search-posts.action.ts +369 -351
  174. package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +209 -211
  175. package/src/providers/hootsuite/hootsuite-base.action.ts +307 -301
  176. package/src/providers/instagram/actions/create-post.action.ts +296 -276
  177. package/src/providers/instagram/actions/create-story.action.ts +394 -378
  178. package/src/providers/instagram/actions/get-account-insights.action.ts +420 -384
  179. package/src/providers/instagram/actions/get-business-posts.action.ts +242 -233
  180. package/src/providers/instagram/actions/get-comments.action.ts +377 -365
  181. package/src/providers/instagram/actions/get-post-insights.action.ts +273 -265
  182. package/src/providers/instagram/actions/schedule-post.action.ts +235 -233
  183. package/src/providers/instagram/actions/search-posts.action.ts +538 -512
  184. package/src/providers/instagram/instagram-base.action.ts +393 -368
  185. package/src/providers/linkedin/actions/create-article.action.ts +266 -275
  186. package/src/providers/linkedin/actions/create-post.action.ts +177 -179
  187. package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
  188. package/src/providers/linkedin/actions/get-organization-posts.action.ts +147 -146
  189. package/src/providers/linkedin/actions/get-personal-posts.action.ts +139 -138
  190. package/src/providers/linkedin/actions/get-post-analytics.action.ts +189 -190
  191. package/src/providers/linkedin/actions/schedule-post.action.ts +189 -191
  192. package/src/providers/linkedin/actions/search-posts.action.ts +283 -275
  193. package/src/providers/linkedin/linkedin-base.action.ts +421 -407
  194. package/src/providers/tiktok/tiktok-base.action.ts +320 -305
  195. package/src/providers/twitter/actions/create-thread.action.ts +207 -203
  196. package/src/providers/twitter/actions/create-tweet.action.ts +188 -187
  197. package/src/providers/twitter/actions/delete-tweet.action.ts +129 -128
  198. package/src/providers/twitter/actions/get-analytics.action.ts +411 -402
  199. package/src/providers/twitter/actions/get-mentions.action.ts +219 -218
  200. package/src/providers/twitter/actions/get-timeline.action.ts +233 -232
  201. package/src/providers/twitter/actions/schedule-tweet.action.ts +222 -221
  202. package/src/providers/twitter/actions/search-tweets.action.ts +543 -540
  203. package/src/providers/twitter/twitter-base.action.ts +560 -541
  204. package/src/providers/youtube/youtube-base.action.ts +333 -320
@@ -1,7 +1,7 @@
1
1
  import { RegisterClass } from '@memberjunction/global';
2
2
  import { TwitterBaseAction, Tweet, TwitterSearchParams } from '../twitter-base.action';
3
3
  import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
4
- import { LogStatus, LogError } from '@memberjunction/global';
4
+ import { LogStatus, LogError } from '@memberjunction/core';
5
5
  import { SocialPost, SearchParams } from '../../../base/base-social.action';
6
6
  import { BaseAction } from '@memberjunction/actions';
7
7
 
@@ -10,577 +10,580 @@ import { BaseAction } from '@memberjunction/actions';
10
10
  */
11
11
  @RegisterClass(BaseAction, 'TwitterSearchTweetsAction')
12
12
  export class TwitterSearchTweetsAction extends TwitterBaseAction {
13
- /**
14
- * Search for tweets on Twitter
15
- */
16
- protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
17
- const { Params, ContextUser } = params;
18
-
19
- try {
20
- // Initialize OAuth
21
- const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
22
- if (!(await this.initializeOAuth(companyIntegrationId))) {
23
- throw new Error('Failed to initialize OAuth connection');
24
- }
25
-
26
- // Extract parameters
27
- const query = this.getParamValue(Params, 'Query');
28
- const hashtags = this.getParamValue(Params, 'Hashtags');
29
- const fromUser = this.getParamValue(Params, 'FromUser');
30
- const toUser = this.getParamValue(Params, 'ToUser');
31
- const mentionUser = this.getParamValue(Params, 'MentionUser');
32
- const startDate = this.getParamValue(Params, 'StartDate');
33
- const endDate = this.getParamValue(Params, 'EndDate');
34
- const language = this.getParamValue(Params, 'Language');
35
- const hasMedia = this.getParamValue(Params, 'HasMedia');
36
- const hasLinks = this.getParamValue(Params, 'HasLinks');
37
- const isRetweet = this.getParamValue(Params, 'IsRetweet');
38
- const isReply = this.getParamValue(Params, 'IsReply');
39
- const isQuote = this.getParamValue(Params, 'IsQuote');
40
- const isVerified = this.getParamValue(Params, 'IsVerified');
41
- const minLikes = this.getParamValue(Params, 'MinLikes');
42
- const minRetweets = this.getParamValue(Params, 'MinRetweets');
43
- const minReplies = this.getParamValue(Params, 'MinReplies');
44
- const place = this.getParamValue(Params, 'Place');
45
- const maxResults = this.getParamValue(Params, 'MaxResults') || 100;
46
- const sortOrder = this.getParamValue(Params, 'SortOrder') || 'recency';
47
-
48
- // Build search query with advanced operators
49
- const searchQuery = this.buildAdvancedSearchQuery({
50
- query,
51
- hashtags,
52
- fromUser,
53
- toUser,
54
- mentionUser,
55
- language,
56
- hasMedia,
57
- hasLinks,
58
- isRetweet,
59
- isReply,
60
- isQuote,
61
- isVerified,
62
- minLikes,
63
- minRetweets,
64
- minReplies,
65
- place,
66
- });
67
-
68
- // Validate query
69
- if (!searchQuery.trim()) {
70
- throw new Error('At least one search parameter must be provided');
71
- }
72
-
73
- // Twitter search query length limit
74
- if (searchQuery.length > 512) {
75
- throw new Error(`Search query exceeds Twitter's 512 character limit (current: ${searchQuery.length} characters)`);
76
- }
77
-
78
- // Build search parameters
79
- const searchParams: TwitterSearchParams = {
80
- query: searchQuery,
81
- max_results: Math.min(maxResults, 100), // API limit per request
82
- sort_order: sortOrder as 'recency' | 'relevancy',
83
- };
84
-
85
- // Add date filters
86
- if (startDate) {
87
- searchParams.start_time = this.formatTwitterDate(startDate);
88
- }
89
- if (endDate) {
90
- searchParams.end_time = this.formatTwitterDate(endDate);
91
- }
92
-
93
- // Perform search
94
- LogStatus(`Searching tweets with query: ${searchQuery.substring(0, 100)}${searchQuery.length > 100 ? '...' : ''}`);
95
- const tweets = await this.searchTweetsInternal(searchParams, maxResults);
96
-
97
- // Convert to normalized format
98
- const normalizedPosts: SocialPost[] = tweets.map((tweet) => this.normalizePost(tweet));
99
-
100
- // Analyze search results
101
- const analysis = this.analyzeSearchResults(tweets, normalizedPosts);
102
-
103
- // Update output parameters
104
- const outputParams = [...Params];
105
- const postsParam = outputParams.find((p) => p.Name === 'Posts');
106
- if (postsParam) postsParam.Value = normalizedPosts;
107
- const tweetsParam = outputParams.find((p) => p.Name === 'Tweets');
108
- if (tweetsParam) tweetsParam.Value = tweets;
109
- const analysisParam = outputParams.find((p) => p.Name === 'Analysis');
110
- if (analysisParam) analysisParam.Value = analysis;
111
- const actualQueryParam = outputParams.find((p) => p.Name === 'ActualQuery');
112
- if (actualQueryParam) actualQueryParam.Value = searchQuery;
113
-
114
- return {
115
- Success: true,
116
- ResultCode: 'SUCCESS',
117
- Message: `Successfully found ${normalizedPosts.length} tweets matching search criteria`,
118
- Params: outputParams,
119
- };
120
- } catch (error) {
121
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
122
-
123
- return {
124
- Success: false,
125
- ResultCode: this.getErrorCode(error),
126
- Message: `Failed to search tweets: ${errorMessage}`,
127
- Params,
128
- };
129
- }
130
- }
13
+ /**
14
+ * Search for tweets on Twitter
15
+ */
16
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
17
+ const { Params, ContextUser } = params;
18
+
19
+ try {
20
+ // Initialize OAuth
21
+ const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
22
+ if (!await this.initializeOAuth(companyIntegrationId)) {
23
+ throw new Error('Failed to initialize OAuth connection');
24
+ }
131
25
 
132
- /**
133
- * Build advanced search query with Twitter operators
134
- */
135
- private buildAdvancedSearchQuery(params: any): string {
136
- const parts: string[] = [];
26
+ // Extract parameters
27
+ const query = this.getParamValue(Params, 'Query');
28
+ const hashtags = this.getParamValue(Params, 'Hashtags');
29
+ const fromUser = this.getParamValue(Params, 'FromUser');
30
+ const toUser = this.getParamValue(Params, 'ToUser');
31
+ const mentionUser = this.getParamValue(Params, 'MentionUser');
32
+ const startDate = this.getParamValue(Params, 'StartDate');
33
+ const endDate = this.getParamValue(Params, 'EndDate');
34
+ const language = this.getParamValue(Params, 'Language');
35
+ const hasMedia = this.getParamValue(Params, 'HasMedia');
36
+ const hasLinks = this.getParamValue(Params, 'HasLinks');
37
+ const isRetweet = this.getParamValue(Params, 'IsRetweet');
38
+ const isReply = this.getParamValue(Params, 'IsReply');
39
+ const isQuote = this.getParamValue(Params, 'IsQuote');
40
+ const isVerified = this.getParamValue(Params, 'IsVerified');
41
+ const minLikes = this.getParamValue(Params, 'MinLikes');
42
+ const minRetweets = this.getParamValue(Params, 'MinRetweets');
43
+ const minReplies = this.getParamValue(Params, 'MinReplies');
44
+ const place = this.getParamValue(Params, 'Place');
45
+ const maxResults = this.getParamValue(Params, 'MaxResults') || 100;
46
+ const sortOrder = this.getParamValue(Params, 'SortOrder') || 'recency';
47
+
48
+ // Build search query with advanced operators
49
+ const searchQuery = this.buildAdvancedSearchQuery({
50
+ query,
51
+ hashtags,
52
+ fromUser,
53
+ toUser,
54
+ mentionUser,
55
+ language,
56
+ hasMedia,
57
+ hasLinks,
58
+ isRetweet,
59
+ isReply,
60
+ isQuote,
61
+ isVerified,
62
+ minLikes,
63
+ minRetweets,
64
+ minReplies,
65
+ place
66
+ });
67
+
68
+ // Validate query
69
+ if (!searchQuery.trim()) {
70
+ throw new Error('At least one search parameter must be provided');
71
+ }
137
72
 
138
- // Basic query
139
- if (params.query) {
140
- parts.push(params.query);
141
- }
73
+ // Twitter search query length limit
74
+ if (searchQuery.length > 512) {
75
+ throw new Error(`Search query exceeds Twitter's 512 character limit (current: ${searchQuery.length} characters)`);
76
+ }
142
77
 
143
- // Hashtags
144
- if (params.hashtags && Array.isArray(params.hashtags) && params.hashtags.length > 0) {
145
- const hashtagQuery = params.hashtags.map((tag: string) => (tag.startsWith('#') ? tag : `#${tag}`)).join(' OR ');
146
- parts.push(`(${hashtagQuery})`);
147
- }
78
+ // Build search parameters
79
+ const searchParams: TwitterSearchParams = {
80
+ query: searchQuery,
81
+ max_results: Math.min(maxResults, 100), // API limit per request
82
+ sort_order: sortOrder as 'recency' | 'relevancy'
83
+ };
148
84
 
149
- // User filters
150
- if (params.fromUser) {
151
- parts.push(`from:${params.fromUser}`);
152
- }
153
- if (params.toUser) {
154
- parts.push(`to:${params.toUser}`);
155
- }
156
- if (params.mentionUser) {
157
- parts.push(`@${params.mentionUser}`);
158
- }
85
+ // Add date filters
86
+ if (startDate) {
87
+ searchParams.start_time = this.formatTwitterDate(startDate);
88
+ }
89
+ if (endDate) {
90
+ searchParams.end_time = this.formatTwitterDate(endDate);
91
+ }
159
92
 
160
- // Language filter
161
- if (params.language) {
162
- parts.push(`lang:${params.language}`);
93
+ // Perform search
94
+ LogStatus(`Searching tweets with query: ${searchQuery.substring(0, 100)}${searchQuery.length > 100 ? '...' : ''}`);
95
+ const tweets = await this.searchTweetsInternal(searchParams, maxResults);
96
+
97
+ // Convert to normalized format
98
+ const normalizedPosts: SocialPost[] = tweets.map(tweet => this.normalizePost(tweet));
99
+
100
+ // Analyze search results
101
+ const analysis = this.analyzeSearchResults(tweets, normalizedPosts);
102
+
103
+ // Update output parameters
104
+ const outputParams = [...Params];
105
+ const postsParam = outputParams.find(p => p.Name === 'Posts');
106
+ if (postsParam) postsParam.Value = normalizedPosts;
107
+ const tweetsParam = outputParams.find(p => p.Name === 'Tweets');
108
+ if (tweetsParam) tweetsParam.Value = tweets;
109
+ const analysisParam = outputParams.find(p => p.Name === 'Analysis');
110
+ if (analysisParam) analysisParam.Value = analysis;
111
+ const actualQueryParam = outputParams.find(p => p.Name === 'ActualQuery');
112
+ if (actualQueryParam) actualQueryParam.Value = searchQuery;
113
+
114
+ return {
115
+ Success: true,
116
+ ResultCode: 'SUCCESS',
117
+ Message: `Successfully found ${normalizedPosts.length} tweets matching search criteria`,
118
+ Params: outputParams
119
+ };
120
+
121
+ } catch (error) {
122
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
123
+
124
+ return {
125
+ Success: false,
126
+ ResultCode: this.getErrorCode(error),
127
+ Message: `Failed to search tweets: ${errorMessage}`,
128
+ Params
129
+ };
130
+ }
163
131
  }
164
132
 
165
- // Media and link filters
166
- if (params.hasMedia === true) {
167
- parts.push('has:media');
168
- } else if (params.hasMedia === false) {
169
- parts.push('-has:media');
170
- }
133
+ /**
134
+ * Build advanced search query with Twitter operators
135
+ */
136
+ private buildAdvancedSearchQuery(params: any): string {
137
+ const parts: string[] = [];
171
138
 
172
- if (params.hasLinks === true) {
173
- parts.push('has:links');
174
- } else if (params.hasLinks === false) {
175
- parts.push('-has:links');
176
- }
139
+ // Basic query
140
+ if (params.query) {
141
+ parts.push(params.query);
142
+ }
177
143
 
178
- // Tweet type filters
179
- if (params.isRetweet === true) {
180
- parts.push('is:retweet');
181
- } else if (params.isRetweet === false) {
182
- parts.push('-is:retweet');
183
- }
144
+ // Hashtags
145
+ if (params.hashtags && Array.isArray(params.hashtags) && params.hashtags.length > 0) {
146
+ const hashtagQuery = params.hashtags
147
+ .map((tag: string) => tag.startsWith('#') ? tag : `#${tag}`)
148
+ .join(' OR ');
149
+ parts.push(`(${hashtagQuery})`);
150
+ }
184
151
 
185
- if (params.isReply === true) {
186
- parts.push('is:reply');
187
- } else if (params.isReply === false) {
188
- parts.push('-is:reply');
189
- }
152
+ // User filters
153
+ if (params.fromUser) {
154
+ parts.push(`from:${params.fromUser}`);
155
+ }
156
+ if (params.toUser) {
157
+ parts.push(`to:${params.toUser}`);
158
+ }
159
+ if (params.mentionUser) {
160
+ parts.push(`@${params.mentionUser}`);
161
+ }
190
162
 
191
- if (params.isQuote === true) {
192
- parts.push('is:quote');
193
- } else if (params.isQuote === false) {
194
- parts.push('-is:quote');
195
- }
163
+ // Language filter
164
+ if (params.language) {
165
+ parts.push(`lang:${params.language}`);
166
+ }
196
167
 
197
- // Verified filter
198
- if (params.isVerified === true) {
199
- parts.push('is:verified');
200
- } else if (params.isVerified === false) {
201
- parts.push('-is:verified');
202
- }
168
+ // Media and link filters
169
+ if (params.hasMedia === true) {
170
+ parts.push('has:media');
171
+ } else if (params.hasMedia === false) {
172
+ parts.push('-has:media');
173
+ }
203
174
 
204
- // Engagement filters (Note: These require Academic Research access)
205
- if (params.minLikes && params.minLikes > 0) {
206
- parts.push(`min_faves:${params.minLikes}`);
207
- }
208
- if (params.minRetweets && params.minRetweets > 0) {
209
- parts.push(`min_retweets:${params.minRetweets}`);
210
- }
211
- if (params.minReplies && params.minReplies > 0) {
212
- parts.push(`min_replies:${params.minReplies}`);
213
- }
175
+ if (params.hasLinks === true) {
176
+ parts.push('has:links');
177
+ } else if (params.hasLinks === false) {
178
+ parts.push('-has:links');
179
+ }
214
180
 
215
- // Place filter
216
- if (params.place) {
217
- parts.push(`place:"${params.place}"`);
218
- }
181
+ // Tweet type filters
182
+ if (params.isRetweet === true) {
183
+ parts.push('is:retweet');
184
+ } else if (params.isRetweet === false) {
185
+ parts.push('-is:retweet');
186
+ }
219
187
 
220
- return parts.join(' ');
221
- }
222
-
223
- /**
224
- * Internal method to search tweets with pagination
225
- */
226
- private async searchTweetsInternal(searchParams: TwitterSearchParams, maxResults: number): Promise<Tweet[]> {
227
- const tweets: Tweet[] = [];
228
- let nextToken: string | undefined;
229
-
230
- const queryParams: Record<string, any> = {
231
- query: searchParams.query,
232
- 'tweet.fields':
233
- 'id,text,created_at,author_id,conversation_id,public_metrics,attachments,entities,referenced_tweets,lang,possibly_sensitive',
234
- 'user.fields': 'id,name,username,profile_image_url,description,created_at,verified',
235
- 'media.fields': 'url,preview_image_url,type,width,height',
236
- expansions: 'author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id',
237
- max_results: searchParams.max_results,
238
- sort_order: searchParams.sort_order,
239
- };
240
-
241
- // Add time filters
242
- if (searchParams.start_time) {
243
- queryParams['start_time'] = searchParams.start_time;
244
- }
245
- if (searchParams.end_time) {
246
- queryParams['end_time'] = searchParams.end_time;
247
- }
188
+ if (params.isReply === true) {
189
+ parts.push('is:reply');
190
+ } else if (params.isReply === false) {
191
+ parts.push('-is:reply');
192
+ }
248
193
 
249
- while (tweets.length < maxResults) {
250
- if (nextToken) {
251
- queryParams['next_token'] = nextToken;
252
- }
194
+ if (params.isQuote === true) {
195
+ parts.push('is:quote');
196
+ } else if (params.isQuote === false) {
197
+ parts.push('-is:quote');
198
+ }
253
199
 
254
- try {
255
- const response = await this.axiosInstance.get('/tweets/search/recent', {
256
- params: queryParams,
257
- });
200
+ // Verified filter
201
+ if (params.isVerified === true) {
202
+ parts.push('is:verified');
203
+ } else if (params.isVerified === false) {
204
+ parts.push('-is:verified');
205
+ }
258
206
 
259
- if (response.data.data && Array.isArray(response.data.data)) {
260
- tweets.push(...response.data.data);
207
+ // Engagement filters (Note: These require Academic Research access)
208
+ if (params.minLikes && params.minLikes > 0) {
209
+ parts.push(`min_faves:${params.minLikes}`);
210
+ }
211
+ if (params.minRetweets && params.minRetweets > 0) {
212
+ parts.push(`min_retweets:${params.minRetweets}`);
213
+ }
214
+ if (params.minReplies && params.minReplies > 0) {
215
+ parts.push(`min_replies:${params.minReplies}`);
261
216
  }
262
217
 
263
- // Check if we've reached the desired number of results
264
- if (tweets.length >= maxResults) {
265
- return tweets.slice(0, maxResults);
218
+ // Place filter
219
+ if (params.place) {
220
+ parts.push(`place:"${params.place}"`);
266
221
  }
267
222
 
268
- // Check for more pages
269
- nextToken = response.data.meta?.next_token;
270
- if (!nextToken) {
271
- break;
223
+ return parts.join(' ');
224
+ }
225
+
226
+ /**
227
+ * Internal method to search tweets with pagination
228
+ */
229
+ private async searchTweetsInternal(searchParams: TwitterSearchParams, maxResults: number): Promise<Tweet[]> {
230
+ const tweets: Tweet[] = [];
231
+ let nextToken: string | undefined;
232
+
233
+ const queryParams: Record<string, any> = {
234
+ 'query': searchParams.query,
235
+ 'tweet.fields': 'id,text,created_at,author_id,conversation_id,public_metrics,attachments,entities,referenced_tweets,lang,possibly_sensitive',
236
+ 'user.fields': 'id,name,username,profile_image_url,description,created_at,verified',
237
+ 'media.fields': 'url,preview_image_url,type,width,height',
238
+ 'expansions': 'author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id',
239
+ 'max_results': searchParams.max_results,
240
+ 'sort_order': searchParams.sort_order
241
+ };
242
+
243
+ // Add time filters
244
+ if (searchParams.start_time) {
245
+ queryParams['start_time'] = searchParams.start_time;
272
246
  }
273
- } catch (error) {
274
- // If we get a 400 error, it might be due to unsupported operators
275
- if ((error as any).response?.status === 400) {
276
- const errorDetail = (error as any).response?.data?.detail || '';
277
- if (errorDetail.includes('min_faves') || errorDetail.includes('min_retweets') || errorDetail.includes('min_replies')) {
278
- LogStatus('Note: Engagement filters (min_likes, min_retweets, min_replies) require Academic Research access');
279
- // Retry without engagement filters
280
- const cleanedQuery = searchParams.query
281
- .replace(/min_faves:\d+\s*/g, '')
282
- .replace(/min_retweets:\d+\s*/g, '')
283
- .replace(/min_replies:\d+\s*/g, '')
284
- .trim();
285
-
286
- if (cleanedQuery !== searchParams.query) {
287
- queryParams['query'] = cleanedQuery;
288
- continue;
289
- }
290
- }
247
+ if (searchParams.end_time) {
248
+ queryParams['end_time'] = searchParams.end_time;
291
249
  }
292
- throw error;
293
- }
294
- }
295
250
 
296
- return tweets;
297
- }
251
+ while (tweets.length < maxResults) {
252
+ if (nextToken) {
253
+ queryParams['next_token'] = nextToken;
254
+ }
298
255
 
299
- /**
300
- * Implement searchPosts for base class
301
- */
302
- protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
303
- const searchParams: TwitterSearchParams = {
304
- query: this.buildSearchQuery(params),
305
- max_results: params.limit || 100,
306
- };
256
+ try {
257
+ const response = await this.axiosInstance.get('/tweets/search/recent', {
258
+ params: queryParams
259
+ });
260
+
261
+ if (response.data.data && Array.isArray(response.data.data)) {
262
+ tweets.push(...response.data.data);
263
+ }
264
+
265
+ // Check if we've reached the desired number of results
266
+ if (tweets.length >= maxResults) {
267
+ return tweets.slice(0, maxResults);
268
+ }
269
+
270
+ // Check for more pages
271
+ nextToken = response.data.meta?.next_token;
272
+ if (!nextToken) {
273
+ break;
274
+ }
275
+
276
+ } catch (error) {
277
+ // If we get a 400 error, it might be due to unsupported operators
278
+ if ((error as any).response?.status === 400) {
279
+ const errorDetail = (error as any).response?.data?.detail || '';
280
+ if (errorDetail.includes('min_faves') || errorDetail.includes('min_retweets') || errorDetail.includes('min_replies')) {
281
+ LogStatus('Note: Engagement filters (min_likes, min_retweets, min_replies) require Academic Research access');
282
+ // Retry without engagement filters
283
+ const cleanedQuery = searchParams.query
284
+ .replace(/min_faves:\d+\s*/g, '')
285
+ .replace(/min_retweets:\d+\s*/g, '')
286
+ .replace(/min_replies:\d+\s*/g, '')
287
+ .trim();
288
+
289
+ if (cleanedQuery !== searchParams.query) {
290
+ queryParams['query'] = cleanedQuery;
291
+ continue;
292
+ }
293
+ }
294
+ }
295
+ throw error;
296
+ }
297
+ }
307
298
 
308
- if (params.startDate) {
309
- searchParams.start_time = this.formatTwitterDate(params.startDate);
299
+ return tweets;
310
300
  }
311
- if (params.endDate) {
312
- searchParams.end_time = this.formatTwitterDate(params.endDate);
301
+
302
+ /**
303
+ * Implement searchPosts for base class
304
+ */
305
+ protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
306
+ const searchParams: TwitterSearchParams = {
307
+ query: this.buildSearchQuery(params),
308
+ max_results: params.limit || 100
309
+ };
310
+
311
+ if (params.startDate) {
312
+ searchParams.start_time = this.formatTwitterDate(params.startDate);
313
+ }
314
+ if (params.endDate) {
315
+ searchParams.end_time = this.formatTwitterDate(params.endDate);
316
+ }
317
+
318
+ const tweets = await this.searchTweetsInternal(searchParams, params.limit || 100);
319
+ return tweets.map(tweet => this.normalizePost(tweet));
313
320
  }
314
321
 
315
- const tweets = await this.searchTweetsInternal(searchParams, params.limit || 100);
316
- return tweets.map((tweet) => this.normalizePost(tweet));
317
- }
318
-
319
- /**
320
- * Analyze search results
321
- */
322
- private analyzeSearchResults(tweets: Tweet[], normalizedPosts: SocialPost[]): any {
323
- const analysis = {
324
- totalResults: tweets.length,
325
- dateRange: {
326
- earliest: null as string | null,
327
- latest: null as string | null,
328
- },
329
- languages: {} as Record<string, number>,
330
- tweetTypes: {
331
- original: 0,
332
- replies: 0,
333
- retweets: 0,
334
- quotes: 0,
335
- },
336
- topHashtags: [] as Array<{ tag: string; count: number }>,
337
- topMentions: [] as Array<{ username: string; count: number }>,
338
- engagementStats: {
339
- totalLikes: 0,
340
- totalRetweets: 0,
341
- totalReplies: 0,
342
- totalQuotes: 0,
343
- averageEngagement: 0,
344
- },
345
- topEngagedTweets: [] as any[],
346
- };
347
-
348
- // Track hashtags and mentions
349
- const hashtagCounts = new Map<string, number>();
350
- const mentionCounts = new Map<string, number>();
351
-
352
- tweets.forEach((tweet, index) => {
353
- // Date range
354
- const createdAt = tweet.created_at;
355
- if (!analysis.dateRange.earliest || createdAt < analysis.dateRange.earliest) {
356
- analysis.dateRange.earliest = createdAt;
357
- }
358
- if (!analysis.dateRange.latest || createdAt > analysis.dateRange.latest) {
359
- analysis.dateRange.latest = createdAt;
360
- }
361
-
362
- // Language
363
- const lang = (tweet as any).lang || 'unknown';
364
- analysis.languages[lang] = (analysis.languages[lang] || 0) + 1;
365
-
366
- // Tweet types
367
- if (tweet.referenced_tweets) {
368
- const types = tweet.referenced_tweets.map((ref) => ref.type);
369
- if (types.includes('replied_to')) analysis.tweetTypes.replies++;
370
- else if (types.includes('retweeted')) analysis.tweetTypes.retweets++;
371
- else if (types.includes('quoted')) analysis.tweetTypes.quotes++;
372
- } else {
373
- analysis.tweetTypes.original++;
374
- }
375
-
376
- // Hashtags
377
- if (tweet.entities?.hashtags) {
378
- tweet.entities.hashtags.forEach((hashtag) => {
379
- const tag = hashtag.tag.toLowerCase();
380
- hashtagCounts.set(tag, (hashtagCounts.get(tag) || 0) + 1);
381
- });
382
- }
322
+ /**
323
+ * Analyze search results
324
+ */
325
+ private analyzeSearchResults(tweets: Tweet[], normalizedPosts: SocialPost[]): any {
326
+ const analysis = {
327
+ totalResults: tweets.length,
328
+ dateRange: {
329
+ earliest: null as string | null,
330
+ latest: null as string | null
331
+ },
332
+ languages: {} as Record<string, number>,
333
+ tweetTypes: {
334
+ original: 0,
335
+ replies: 0,
336
+ retweets: 0,
337
+ quotes: 0
338
+ },
339
+ topHashtags: [] as Array<{ tag: string; count: number }>,
340
+ topMentions: [] as Array<{ username: string; count: number }>,
341
+ engagementStats: {
342
+ totalLikes: 0,
343
+ totalRetweets: 0,
344
+ totalReplies: 0,
345
+ totalQuotes: 0,
346
+ averageEngagement: 0
347
+ },
348
+ topEngagedTweets: [] as any[]
349
+ };
350
+
351
+ // Track hashtags and mentions
352
+ const hashtagCounts = new Map<string, number>();
353
+ const mentionCounts = new Map<string, number>();
354
+
355
+ tweets.forEach((tweet, index) => {
356
+ // Date range
357
+ const createdAt = tweet.created_at;
358
+ if (!analysis.dateRange.earliest || createdAt < analysis.dateRange.earliest) {
359
+ analysis.dateRange.earliest = createdAt;
360
+ }
361
+ if (!analysis.dateRange.latest || createdAt > analysis.dateRange.latest) {
362
+ analysis.dateRange.latest = createdAt;
363
+ }
364
+
365
+ // Language
366
+ const lang = (tweet as any).lang || 'unknown';
367
+ analysis.languages[lang] = (analysis.languages[lang] || 0) + 1;
368
+
369
+ // Tweet types
370
+ if (tweet.referenced_tweets) {
371
+ const types = tweet.referenced_tweets.map(ref => ref.type);
372
+ if (types.includes('replied_to')) analysis.tweetTypes.replies++;
373
+ else if (types.includes('retweeted')) analysis.tweetTypes.retweets++;
374
+ else if (types.includes('quoted')) analysis.tweetTypes.quotes++;
375
+ } else {
376
+ analysis.tweetTypes.original++;
377
+ }
378
+
379
+ // Hashtags
380
+ if (tweet.entities?.hashtags) {
381
+ tweet.entities.hashtags.forEach(hashtag => {
382
+ const tag = hashtag.tag.toLowerCase();
383
+ hashtagCounts.set(tag, (hashtagCounts.get(tag) || 0) + 1);
384
+ });
385
+ }
383
386
 
384
- // Mentions
385
- if (tweet.entities?.mentions) {
386
- tweet.entities.mentions.forEach((mention) => {
387
- const username = mention.username.toLowerCase();
388
- mentionCounts.set(username, (mentionCounts.get(username) || 0) + 1);
387
+ // Mentions
388
+ if (tweet.entities?.mentions) {
389
+ tweet.entities.mentions.forEach(mention => {
390
+ const username = mention.username.toLowerCase();
391
+ mentionCounts.set(username, (mentionCounts.get(username) || 0) + 1);
392
+ });
393
+ }
394
+
395
+ // Engagement stats
396
+ if (tweet.public_metrics) {
397
+ analysis.engagementStats.totalLikes += tweet.public_metrics.like_count || 0;
398
+ analysis.engagementStats.totalRetweets += tweet.public_metrics.retweet_count || 0;
399
+ analysis.engagementStats.totalReplies += tweet.public_metrics.reply_count || 0;
400
+ analysis.engagementStats.totalQuotes += tweet.public_metrics.quote_count || 0;
401
+ }
389
402
  });
390
- }
391
-
392
- // Engagement stats
393
- if (tweet.public_metrics) {
394
- analysis.engagementStats.totalLikes += tweet.public_metrics.like_count || 0;
395
- analysis.engagementStats.totalRetweets += tweet.public_metrics.retweet_count || 0;
396
- analysis.engagementStats.totalReplies += tweet.public_metrics.reply_count || 0;
397
- analysis.engagementStats.totalQuotes += tweet.public_metrics.quote_count || 0;
398
- }
399
- });
400
-
401
- // Calculate average engagement
402
- if (tweets.length > 0) {
403
- const totalEngagement =
404
- analysis.engagementStats.totalLikes +
405
- analysis.engagementStats.totalRetweets +
406
- analysis.engagementStats.totalReplies +
407
- analysis.engagementStats.totalQuotes;
408
- analysis.engagementStats.averageEngagement = Math.round(totalEngagement / tweets.length);
403
+
404
+ // Calculate average engagement
405
+ if (tweets.length > 0) {
406
+ const totalEngagement =
407
+ analysis.engagementStats.totalLikes +
408
+ analysis.engagementStats.totalRetweets +
409
+ analysis.engagementStats.totalReplies +
410
+ analysis.engagementStats.totalQuotes;
411
+ analysis.engagementStats.averageEngagement = Math.round(totalEngagement / tweets.length);
412
+ }
413
+
414
+ // Top hashtags
415
+ analysis.topHashtags = Array.from(hashtagCounts.entries())
416
+ .sort((a, b) => b[1] - a[1])
417
+ .slice(0, 10)
418
+ .map(([tag, count]) => ({ tag, count }));
419
+
420
+ // Top mentions
421
+ analysis.topMentions = Array.from(mentionCounts.entries())
422
+ .sort((a, b) => b[1] - a[1])
423
+ .slice(0, 10)
424
+ .map(([username, count]) => ({ username, count }));
425
+
426
+ // Top engaged tweets
427
+ analysis.topEngagedTweets = normalizedPosts
428
+ .filter(post => post.analytics)
429
+ .sort((a, b) => (b.analytics?.engagements || 0) - (a.analytics?.engagements || 0))
430
+ .slice(0, 5)
431
+ .map(post => ({
432
+ id: post.id,
433
+ content: post.content.substring(0, 100) + (post.content.length > 100 ? '...' : ''),
434
+ engagement: post.analytics?.engagements,
435
+ metrics: post.analytics
436
+ }));
437
+
438
+ return analysis;
439
+ }
440
+
441
+ /**
442
+ * Get error code based on error type
443
+ */
444
+ private getErrorCode(error: any): string {
445
+ if (error instanceof Error) {
446
+ if (error.message.includes('Rate Limit')) return 'RATE_LIMIT';
447
+ if (error.message.includes('Unauthorized')) return 'INVALID_TOKEN';
448
+ if (error.message.includes('character limit')) return 'QUERY_TOO_LONG';
449
+ if (error.message.includes('Academic Research')) return 'INSUFFICIENT_ACCESS';
450
+ }
451
+ return 'ERROR';
452
+ }
453
+
454
+ /**
455
+ * Define the parameters this action expects
456
+ */
457
+ public get Params(): ActionParam[] {
458
+ return [
459
+ ...this.commonSocialParams,
460
+ {
461
+ Name: 'Query',
462
+ Type: 'Input',
463
+ Value: null
464
+ },
465
+ {
466
+ Name: 'Hashtags',
467
+ Type: 'Input',
468
+ Value: null
469
+ },
470
+ {
471
+ Name: 'FromUser',
472
+ Type: 'Input',
473
+ Value: null
474
+ },
475
+ {
476
+ Name: 'ToUser',
477
+ Type: 'Input',
478
+ Value: null
479
+ },
480
+ {
481
+ Name: 'MentionUser',
482
+ Type: 'Input',
483
+ Value: null
484
+ },
485
+ {
486
+ Name: 'StartDate',
487
+ Type: 'Input',
488
+ Value: null
489
+ },
490
+ {
491
+ Name: 'EndDate',
492
+ Type: 'Input',
493
+ Value: null
494
+ },
495
+ {
496
+ Name: 'Language',
497
+ Type: 'Input',
498
+ Value: null
499
+ },
500
+ {
501
+ Name: 'HasMedia',
502
+ Type: 'Input',
503
+ Value: null
504
+ },
505
+ {
506
+ Name: 'HasLinks',
507
+ Type: 'Input',
508
+ Value: null
509
+ },
510
+ {
511
+ Name: 'IsRetweet',
512
+ Type: 'Input',
513
+ Value: null
514
+ },
515
+ {
516
+ Name: 'IsReply',
517
+ Type: 'Input',
518
+ Value: null
519
+ },
520
+ {
521
+ Name: 'IsQuote',
522
+ Type: 'Input',
523
+ Value: null
524
+ },
525
+ {
526
+ Name: 'IsVerified',
527
+ Type: 'Input',
528
+ Value: null
529
+ },
530
+ {
531
+ Name: 'MinLikes',
532
+ Type: 'Input',
533
+ Value: null
534
+ },
535
+ {
536
+ Name: 'MinRetweets',
537
+ Type: 'Input',
538
+ Value: null
539
+ },
540
+ {
541
+ Name: 'MinReplies',
542
+ Type: 'Input',
543
+ Value: null
544
+ },
545
+ {
546
+ Name: 'Place',
547
+ Type: 'Input',
548
+ Value: null
549
+ },
550
+ {
551
+ Name: 'MaxResults',
552
+ Type: 'Input',
553
+ Value: 100
554
+ },
555
+ {
556
+ Name: 'SortOrder',
557
+ Type: 'Input',
558
+ Value: 'recency' // 'recency' or 'relevancy'
559
+ },
560
+ {
561
+ Name: 'Posts',
562
+ Type: 'Output',
563
+ Value: null
564
+ },
565
+ {
566
+ Name: 'Tweets',
567
+ Type: 'Output',
568
+ Value: null
569
+ },
570
+ {
571
+ Name: 'Analysis',
572
+ Type: 'Output',
573
+ Value: null
574
+ },
575
+ {
576
+ Name: 'ActualQuery',
577
+ Type: 'Output',
578
+ Value: null
579
+ }
580
+ ];
409
581
  }
410
582
 
411
- // Top hashtags
412
- analysis.topHashtags = Array.from(hashtagCounts.entries())
413
- .sort((a, b) => b[1] - a[1])
414
- .slice(0, 10)
415
- .map(([tag, count]) => ({ tag, count }));
416
-
417
- // Top mentions
418
- analysis.topMentions = Array.from(mentionCounts.entries())
419
- .sort((a, b) => b[1] - a[1])
420
- .slice(0, 10)
421
- .map(([username, count]) => ({ username, count }));
422
-
423
- // Top engaged tweets
424
- analysis.topEngagedTweets = normalizedPosts
425
- .filter((post) => post.analytics)
426
- .sort((a, b) => (b.analytics?.engagements || 0) - (a.analytics?.engagements || 0))
427
- .slice(0, 5)
428
- .map((post) => ({
429
- id: post.id,
430
- content: post.content.substring(0, 100) + (post.content.length > 100 ? '...' : ''),
431
- engagement: post.analytics?.engagements,
432
- metrics: post.analytics,
433
- }));
434
-
435
- return analysis;
436
- }
437
-
438
- /**
439
- * Get error code based on error type
440
- */
441
- private getErrorCode(error: any): string {
442
- if (error instanceof Error) {
443
- if (error.message.includes('Rate Limit')) return 'RATE_LIMIT';
444
- if (error.message.includes('Unauthorized')) return 'INVALID_TOKEN';
445
- if (error.message.includes('character limit')) return 'QUERY_TOO_LONG';
446
- if (error.message.includes('Academic Research')) return 'INSUFFICIENT_ACCESS';
583
+ /**
584
+ * Get action description
585
+ */
586
+ public get Description(): string {
587
+ return 'Searches for tweets on Twitter/X using advanced operators and filters, with comprehensive analysis of results including historical data';
447
588
  }
448
- return 'ERROR';
449
- }
450
-
451
- /**
452
- * Define the parameters this action expects
453
- */
454
- public get Params(): ActionParam[] {
455
- return [
456
- ...this.commonSocialParams,
457
- {
458
- Name: 'Query',
459
- Type: 'Input',
460
- Value: null,
461
- },
462
- {
463
- Name: 'Hashtags',
464
- Type: 'Input',
465
- Value: null,
466
- },
467
- {
468
- Name: 'FromUser',
469
- Type: 'Input',
470
- Value: null,
471
- },
472
- {
473
- Name: 'ToUser',
474
- Type: 'Input',
475
- Value: null,
476
- },
477
- {
478
- Name: 'MentionUser',
479
- Type: 'Input',
480
- Value: null,
481
- },
482
- {
483
- Name: 'StartDate',
484
- Type: 'Input',
485
- Value: null,
486
- },
487
- {
488
- Name: 'EndDate',
489
- Type: 'Input',
490
- Value: null,
491
- },
492
- {
493
- Name: 'Language',
494
- Type: 'Input',
495
- Value: null,
496
- },
497
- {
498
- Name: 'HasMedia',
499
- Type: 'Input',
500
- Value: null,
501
- },
502
- {
503
- Name: 'HasLinks',
504
- Type: 'Input',
505
- Value: null,
506
- },
507
- {
508
- Name: 'IsRetweet',
509
- Type: 'Input',
510
- Value: null,
511
- },
512
- {
513
- Name: 'IsReply',
514
- Type: 'Input',
515
- Value: null,
516
- },
517
- {
518
- Name: 'IsQuote',
519
- Type: 'Input',
520
- Value: null,
521
- },
522
- {
523
- Name: 'IsVerified',
524
- Type: 'Input',
525
- Value: null,
526
- },
527
- {
528
- Name: 'MinLikes',
529
- Type: 'Input',
530
- Value: null,
531
- },
532
- {
533
- Name: 'MinRetweets',
534
- Type: 'Input',
535
- Value: null,
536
- },
537
- {
538
- Name: 'MinReplies',
539
- Type: 'Input',
540
- Value: null,
541
- },
542
- {
543
- Name: 'Place',
544
- Type: 'Input',
545
- Value: null,
546
- },
547
- {
548
- Name: 'MaxResults',
549
- Type: 'Input',
550
- Value: 100,
551
- },
552
- {
553
- Name: 'SortOrder',
554
- Type: 'Input',
555
- Value: 'recency', // 'recency' or 'relevancy'
556
- },
557
- {
558
- Name: 'Posts',
559
- Type: 'Output',
560
- Value: null,
561
- },
562
- {
563
- Name: 'Tweets',
564
- Type: 'Output',
565
- Value: null,
566
- },
567
- {
568
- Name: 'Analysis',
569
- Type: 'Output',
570
- Value: null,
571
- },
572
- {
573
- Name: 'ActualQuery',
574
- Type: 'Output',
575
- Value: null,
576
- },
577
- ];
578
- }
579
-
580
- /**
581
- * Get action description
582
- */
583
- public get Description(): string {
584
- return 'Searches for tweets on Twitter/X using advanced operators and filters, with comprehensive analysis of results including historical data';
585
- }
586
- }
589
+ }