@memberjunction/actions-bizapps-social 2.112.0 → 2.113.1

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, TwitterMetrics } 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 { SocialAnalytics } from '../../../base/base-social.action';
6
6
  import { BaseAction } from '@memberjunction/actions';
7
7
 
@@ -10,423 +10,432 @@ import { BaseAction } from '@memberjunction/actions';
10
10
  */
11
11
  @RegisterClass(BaseAction, 'TwitterGetAnalyticsAction')
12
12
  export class TwitterGetAnalyticsAction extends TwitterBaseAction {
13
- /**
14
- * Get analytics from 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 analyticsType = this.getParamValue(Params, 'AnalyticsType') || 'account'; // 'account' or 'tweets'
28
- const tweetIds = this.getParamValue(Params, 'TweetIDs');
29
- const startDate = this.getParamValue(Params, 'StartDate');
30
- const endDate = this.getParamValue(Params, 'EndDate');
31
- const granularity = this.getParamValue(Params, 'Granularity') || 'day'; // 'hour', 'day', 'total'
32
-
33
- if (analyticsType === 'tweets') {
34
- // Get analytics for specific tweets
35
- if (!tweetIds || !Array.isArray(tweetIds) || tweetIds.length === 0) {
36
- throw new Error('TweetIDs array is required for tweet analytics');
13
+ /**
14
+ * Get analytics from 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 analyticsType = this.getParamValue(Params, 'AnalyticsType') || 'account'; // 'account' or 'tweets'
28
+ const tweetIds = this.getParamValue(Params, 'TweetIDs');
29
+ const startDate = this.getParamValue(Params, 'StartDate');
30
+ const endDate = this.getParamValue(Params, 'EndDate');
31
+ const granularity = this.getParamValue(Params, 'Granularity') || 'day'; // 'hour', 'day', 'total'
32
+
33
+ if (analyticsType === 'tweets') {
34
+ // Get analytics for specific tweets
35
+ if (!tweetIds || !Array.isArray(tweetIds) || tweetIds.length === 0) {
36
+ throw new Error('TweetIDs array is required for tweet analytics');
37
+ }
38
+
39
+ return await this.getTweetAnalytics(Params, tweetIds);
40
+ } else {
41
+ // Get account-level analytics
42
+ return await this.getAccountAnalytics(Params, startDate, endDate, granularity);
43
+ }
44
+
45
+ } catch (error) {
46
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
47
+
48
+ return {
49
+ Success: false,
50
+ ResultCode: this.getErrorCode(error),
51
+ Message: `Failed to get analytics: ${errorMessage}`,
52
+ Params
53
+ };
37
54
  }
55
+ }
38
56
 
39
- return await this.getTweetAnalytics(Params, tweetIds);
40
- } else {
41
- // Get account-level analytics
42
- return await this.getAccountAnalytics(Params, startDate, endDate, granularity);
43
- }
44
- } catch (error) {
45
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
46
-
47
- return {
48
- Success: false,
49
- ResultCode: this.getErrorCode(error),
50
- Message: `Failed to get analytics: ${errorMessage}`,
51
- Params,
52
- };
57
+ /**
58
+ * Get analytics for specific tweets
59
+ */
60
+ private async getTweetAnalytics(params: ActionParam[], tweetIds: string[]): Promise<ActionResultSimple> {
61
+ try {
62
+ LogStatus(`Getting analytics for ${tweetIds.length} tweets...`);
63
+
64
+ // Twitter API v2 requires organic metrics scope for detailed analytics
65
+ // We'll get public metrics for each tweet
66
+ const tweetAnalytics: any[] = [];
67
+
68
+ // Process in batches of 100 (API limit)
69
+ const batchSize = 100;
70
+ for (let i = 0; i < tweetIds.length; i += batchSize) {
71
+ const batch = tweetIds.slice(i, i + batchSize);
72
+ const ids = batch.join(',');
73
+
74
+ const response = await this.axiosInstance.get('/tweets', {
75
+ params: {
76
+ 'ids': ids,
77
+ 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics,promoted_metrics',
78
+ 'expansions': 'author_id',
79
+ 'user.fields': 'id,username'
80
+ }
81
+ });
82
+
83
+ if (response.data.data) {
84
+ for (const tweet of response.data.data) {
85
+ const metrics: TwitterMetrics = {
86
+ impression_count: 0,
87
+ engagement_count: 0,
88
+ retweet_count: 0,
89
+ reply_count: 0,
90
+ like_count: 0,
91
+ quote_count: 0,
92
+ bookmark_count: 0,
93
+ url_link_clicks: 0,
94
+ user_profile_clicks: 0
95
+ };
96
+
97
+ // Combine public and organic metrics if available
98
+ if (tweet.public_metrics) {
99
+ Object.assign(metrics, tweet.public_metrics);
100
+ }
101
+ if (tweet.organic_metrics) {
102
+ Object.assign(metrics, tweet.organic_metrics);
103
+ }
104
+
105
+ // Calculate engagement count
106
+ metrics.engagement_count =
107
+ metrics.retweet_count +
108
+ metrics.reply_count +
109
+ metrics.like_count +
110
+ metrics.quote_count +
111
+ metrics.url_link_clicks +
112
+ metrics.user_profile_clicks;
113
+
114
+ const normalizedAnalytics = this.normalizeAnalytics(metrics);
115
+
116
+ tweetAnalytics.push({
117
+ tweetId: tweet.id,
118
+ text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''),
119
+ createdAt: tweet.created_at,
120
+ metrics: normalizedAnalytics,
121
+ engagementRate: metrics.impression_count > 0
122
+ ? ((metrics.engagement_count / metrics.impression_count) * 100).toFixed(2) + '%'
123
+ : '0%'
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ // Calculate aggregate metrics
130
+ const aggregateMetrics = this.calculateAggregateMetrics(tweetAnalytics);
131
+
132
+ // Update output parameters
133
+ const outputParams = [...params];
134
+ const analyticsParam = outputParams.find(p => p.Name === 'Analytics');
135
+ if (analyticsParam) analyticsParam.Value = tweetAnalytics;
136
+ const aggregateParam = outputParams.find(p => p.Name === 'AggregateMetrics');
137
+ if (aggregateParam) aggregateParam.Value = aggregateMetrics;
138
+
139
+ return {
140
+ Success: true,
141
+ ResultCode: 'SUCCESS',
142
+ Message: `Successfully retrieved analytics for ${tweetAnalytics.length} tweets`,
143
+ Params: outputParams
144
+ };
145
+
146
+ } catch (error) {
147
+ throw error;
148
+ }
53
149
  }
54
- }
55
-
56
- /**
57
- * Get analytics for specific tweets
58
- */
59
- private async getTweetAnalytics(params: ActionParam[], tweetIds: string[]): Promise<ActionResultSimple> {
60
- try {
61
- LogStatus(`Getting analytics for ${tweetIds.length} tweets...`);
62
-
63
- // Twitter API v2 requires organic metrics scope for detailed analytics
64
- // We'll get public metrics for each tweet
65
- const tweetAnalytics: any[] = [];
66
-
67
- // Process in batches of 100 (API limit)
68
- const batchSize = 100;
69
- for (let i = 0; i < tweetIds.length; i += batchSize) {
70
- const batch = tweetIds.slice(i, i + batchSize);
71
- const ids = batch.join(',');
72
-
73
- const response = await this.axiosInstance.get('/tweets', {
74
- params: {
75
- ids: ids,
76
- 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics,promoted_metrics',
77
- expansions: 'author_id',
78
- 'user.fields': 'id,username',
79
- },
80
- });
81
150
 
82
- if (response.data.data) {
83
- for (const tweet of response.data.data) {
84
- const metrics: TwitterMetrics = {
85
- impression_count: 0,
86
- engagement_count: 0,
87
- retweet_count: 0,
88
- reply_count: 0,
89
- like_count: 0,
90
- quote_count: 0,
91
- bookmark_count: 0,
92
- url_link_clicks: 0,
93
- user_profile_clicks: 0,
151
+ /**
152
+ * Get account-level analytics
153
+ */
154
+ private async getAccountAnalytics(
155
+ params: ActionParam[],
156
+ startDate?: string,
157
+ endDate?: string,
158
+ granularity?: string
159
+ ): Promise<ActionResultSimple> {
160
+ try {
161
+ // Get current user
162
+ const currentUser = await this.getCurrentUser();
163
+
164
+ LogStatus(`Getting account analytics for @${currentUser.username}...`);
165
+
166
+ // For account analytics, we'll analyze recent tweets performance
167
+ const queryParams: Record<string, any> = {
168
+ 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics',
169
+ 'max_results': 100
94
170
  };
95
171
 
96
- // Combine public and organic metrics if available
97
- if (tweet.public_metrics) {
98
- Object.assign(metrics, tweet.public_metrics);
172
+ if (startDate) {
173
+ queryParams['start_time'] = this.formatTwitterDate(startDate);
99
174
  }
100
- if (tweet.organic_metrics) {
101
- Object.assign(metrics, tweet.organic_metrics);
175
+ if (endDate) {
176
+ queryParams['end_time'] = this.formatTwitterDate(endDate);
102
177
  }
103
178
 
104
- // Calculate engagement count
105
- metrics.engagement_count =
106
- metrics.retweet_count +
107
- metrics.reply_count +
108
- metrics.like_count +
109
- metrics.quote_count +
110
- metrics.url_link_clicks +
111
- metrics.user_profile_clicks;
112
-
113
- const normalizedAnalytics = this.normalizeAnalytics(metrics);
114
-
115
- tweetAnalytics.push({
116
- tweetId: tweet.id,
117
- text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''),
118
- createdAt: tweet.created_at,
119
- metrics: normalizedAnalytics,
120
- engagementRate:
121
- metrics.impression_count > 0 ? ((metrics.engagement_count / metrics.impression_count) * 100).toFixed(2) + '%' : '0%',
179
+ // Get user's tweets
180
+ const tweets = await this.getPaginatedTweets(
181
+ `/users/${currentUser.id}/tweets`,
182
+ queryParams,
183
+ 200 // Get up to 200 tweets for analysis
184
+ );
185
+
186
+ // Calculate time-based analytics
187
+ const timeBasedAnalytics = this.calculateTimeBasedAnalytics(tweets, granularity || 'day');
188
+
189
+ // Calculate overall metrics
190
+ const overallMetrics = {
191
+ totalTweets: tweets.length,
192
+ totalImpressions: 0,
193
+ totalEngagements: 0,
194
+ totalLikes: 0,
195
+ totalRetweets: 0,
196
+ totalReplies: 0,
197
+ totalQuotes: 0,
198
+ averageEngagementRate: 0,
199
+ topPerformingTweets: [] as any[]
200
+ };
201
+
202
+ tweets.forEach(tweet => {
203
+ if (tweet.public_metrics) {
204
+ overallMetrics.totalImpressions += tweet.public_metrics.impression_count || 0;
205
+ overallMetrics.totalLikes += tweet.public_metrics.like_count || 0;
206
+ overallMetrics.totalRetweets += tweet.public_metrics.retweet_count || 0;
207
+ overallMetrics.totalReplies += tweet.public_metrics.reply_count || 0;
208
+ overallMetrics.totalQuotes += tweet.public_metrics.quote_count || 0;
209
+
210
+ const engagement =
211
+ (tweet.public_metrics.like_count || 0) +
212
+ (tweet.public_metrics.retweet_count || 0) +
213
+ (tweet.public_metrics.reply_count || 0) +
214
+ (tweet.public_metrics.quote_count || 0);
215
+
216
+ overallMetrics.totalEngagements += engagement;
217
+ }
122
218
  });
123
- }
219
+
220
+ // Calculate average engagement rate
221
+ if (overallMetrics.totalImpressions > 0) {
222
+ overallMetrics.averageEngagementRate =
223
+ parseFloat(((overallMetrics.totalEngagements / overallMetrics.totalImpressions) * 100).toFixed(2));
224
+ }
225
+
226
+ // Get top performing tweets
227
+ overallMetrics.topPerformingTweets = tweets
228
+ .filter(t => t.public_metrics)
229
+ .sort((a, b) => {
230
+ const aEngagement = this.calculateTweetEngagement(a.public_metrics!);
231
+ const bEngagement = this.calculateTweetEngagement(b.public_metrics!);
232
+ return bEngagement - aEngagement;
233
+ })
234
+ .slice(0, 5)
235
+ .map(tweet => ({
236
+ id: tweet.id,
237
+ text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''),
238
+ createdAt: tweet.created_at,
239
+ metrics: tweet.public_metrics,
240
+ engagement: this.calculateTweetEngagement(tweet.public_metrics!)
241
+ }));
242
+
243
+ // Update output parameters
244
+ const outputParams = [...params];
245
+ const overallParam = outputParams.find(p => p.Name === 'OverallMetrics');
246
+ if (overallParam) overallParam.Value = overallMetrics;
247
+ const timeBasedParam = outputParams.find(p => p.Name === 'TimeBasedAnalytics');
248
+ if (timeBasedParam) timeBasedParam.Value = timeBasedAnalytics;
249
+
250
+ return {
251
+ Success: true,
252
+ ResultCode: 'SUCCESS',
253
+ Message: `Successfully retrieved account analytics for ${tweets.length} tweets`,
254
+ Params: outputParams
255
+ };
256
+
257
+ } catch (error) {
258
+ throw error;
124
259
  }
125
- }
126
-
127
- // Calculate aggregate metrics
128
- const aggregateMetrics = this.calculateAggregateMetrics(tweetAnalytics);
129
-
130
- // Update output parameters
131
- const outputParams = [...params];
132
- const analyticsParam = outputParams.find((p) => p.Name === 'Analytics');
133
- if (analyticsParam) analyticsParam.Value = tweetAnalytics;
134
- const aggregateParam = outputParams.find((p) => p.Name === 'AggregateMetrics');
135
- if (aggregateParam) aggregateParam.Value = aggregateMetrics;
136
-
137
- return {
138
- Success: true,
139
- ResultCode: 'SUCCESS',
140
- Message: `Successfully retrieved analytics for ${tweetAnalytics.length} tweets`,
141
- Params: outputParams,
142
- };
143
- } catch (error) {
144
- throw error;
145
260
  }
146
- }
147
-
148
- /**
149
- * Get account-level analytics
150
- */
151
- private async getAccountAnalytics(
152
- params: ActionParam[],
153
- startDate?: string,
154
- endDate?: string,
155
- granularity?: string
156
- ): Promise<ActionResultSimple> {
157
- try {
158
- // Get current user
159
- const currentUser = await this.getCurrentUser();
160
-
161
- LogStatus(`Getting account analytics for @${currentUser.username}...`);
162
-
163
- // For account analytics, we'll analyze recent tweets performance
164
- const queryParams: Record<string, any> = {
165
- 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics',
166
- max_results: 100,
167
- };
168
-
169
- if (startDate) {
170
- queryParams['start_time'] = this.formatTwitterDate(startDate);
171
- }
172
- if (endDate) {
173
- queryParams['end_time'] = this.formatTwitterDate(endDate);
174
- }
175
-
176
- // Get user's tweets
177
- const tweets = await this.getPaginatedTweets(
178
- `/users/${currentUser.id}/tweets`,
179
- queryParams,
180
- 200 // Get up to 200 tweets for analysis
181
- );
182
-
183
- // Calculate time-based analytics
184
- const timeBasedAnalytics = this.calculateTimeBasedAnalytics(tweets, granularity || 'day');
185
-
186
- // Calculate overall metrics
187
- const overallMetrics = {
188
- totalTweets: tweets.length,
189
- totalImpressions: 0,
190
- totalEngagements: 0,
191
- totalLikes: 0,
192
- totalRetweets: 0,
193
- totalReplies: 0,
194
- totalQuotes: 0,
195
- averageEngagementRate: 0,
196
- topPerformingTweets: [] as any[],
197
- };
198
-
199
- tweets.forEach((tweet) => {
200
- if (tweet.public_metrics) {
201
- overallMetrics.totalImpressions += tweet.public_metrics.impression_count || 0;
202
- overallMetrics.totalLikes += tweet.public_metrics.like_count || 0;
203
- overallMetrics.totalRetweets += tweet.public_metrics.retweet_count || 0;
204
- overallMetrics.totalReplies += tweet.public_metrics.reply_count || 0;
205
- overallMetrics.totalQuotes += tweet.public_metrics.quote_count || 0;
206
-
207
- const engagement =
208
- (tweet.public_metrics.like_count || 0) +
209
- (tweet.public_metrics.retweet_count || 0) +
210
- (tweet.public_metrics.reply_count || 0) +
211
- (tweet.public_metrics.quote_count || 0);
212
-
213
- overallMetrics.totalEngagements += engagement;
261
+
262
+ /**
263
+ * Calculate aggregate metrics from tweet analytics
264
+ */
265
+ private calculateAggregateMetrics(tweetAnalytics: any[]): any {
266
+ const aggregate = {
267
+ totalImpressions: 0,
268
+ totalEngagements: 0,
269
+ totalLikes: 0,
270
+ totalRetweets: 0,
271
+ totalReplies: 0,
272
+ averageEngagementRate: 0,
273
+ bestPerformingTweet: null as any,
274
+ worstPerformingTweet: null as any
275
+ };
276
+
277
+ let bestEngagement = -1;
278
+ let worstEngagement = Infinity;
279
+
280
+ tweetAnalytics.forEach(analytics => {
281
+ const metrics = analytics.metrics;
282
+ aggregate.totalImpressions += metrics.impressions;
283
+ aggregate.totalEngagements += metrics.engagements;
284
+ aggregate.totalLikes += metrics.likes;
285
+ aggregate.totalRetweets += metrics.shares;
286
+ aggregate.totalReplies += metrics.comments;
287
+
288
+ if (metrics.engagements > bestEngagement) {
289
+ bestEngagement = metrics.engagements;
290
+ aggregate.bestPerformingTweet = analytics;
291
+ }
292
+ if (metrics.engagements < worstEngagement) {
293
+ worstEngagement = metrics.engagements;
294
+ aggregate.worstPerformingTweet = analytics;
295
+ }
296
+ });
297
+
298
+ if (aggregate.totalImpressions > 0) {
299
+ aggregate.averageEngagementRate =
300
+ parseFloat(((aggregate.totalEngagements / aggregate.totalImpressions) * 100).toFixed(2));
214
301
  }
215
- });
216
302
 
217
- // Calculate average engagement rate
218
- if (overallMetrics.totalImpressions > 0) {
219
- overallMetrics.averageEngagementRate = parseFloat(
220
- ((overallMetrics.totalEngagements / overallMetrics.totalImpressions) * 100).toFixed(2)
303
+ return aggregate;
304
+ }
305
+
306
+ /**
307
+ * Calculate time-based analytics
308
+ */
309
+ private calculateTimeBasedAnalytics(tweets: Tweet[], granularity: string): any[] {
310
+ const buckets: Map<string, any> = new Map();
311
+
312
+ tweets.forEach(tweet => {
313
+ const date = new Date(tweet.created_at);
314
+ let bucketKey: string;
315
+
316
+ switch (granularity) {
317
+ case 'hour':
318
+ bucketKey = `${date.toISOString().slice(0, 13)}:00:00Z`;
319
+ break;
320
+ case 'day':
321
+ bucketKey = date.toISOString().slice(0, 10);
322
+ break;
323
+ case 'total':
324
+ bucketKey = 'total';
325
+ break;
326
+ default:
327
+ bucketKey = date.toISOString().slice(0, 10);
328
+ }
329
+
330
+ if (!buckets.has(bucketKey)) {
331
+ buckets.set(bucketKey, {
332
+ period: bucketKey,
333
+ tweets: 0,
334
+ impressions: 0,
335
+ engagements: 0,
336
+ likes: 0,
337
+ retweets: 0,
338
+ replies: 0
339
+ });
340
+ }
341
+
342
+ const bucket = buckets.get(bucketKey)!;
343
+ bucket.tweets++;
344
+
345
+ if (tweet.public_metrics) {
346
+ bucket.impressions += tweet.public_metrics.impression_count || 0;
347
+ bucket.likes += tweet.public_metrics.like_count || 0;
348
+ bucket.retweets += tweet.public_metrics.retweet_count || 0;
349
+ bucket.replies += tweet.public_metrics.reply_count || 0;
350
+ bucket.engagements += this.calculateTweetEngagement(tweet.public_metrics);
351
+ }
352
+ });
353
+
354
+ return Array.from(buckets.values()).sort((a, b) =>
355
+ a.period.localeCompare(b.period)
221
356
  );
222
- }
223
-
224
- // Get top performing tweets
225
- overallMetrics.topPerformingTweets = tweets
226
- .filter((t) => t.public_metrics)
227
- .sort((a, b) => {
228
- const aEngagement = this.calculateTweetEngagement(a.public_metrics!);
229
- const bEngagement = this.calculateTweetEngagement(b.public_metrics!);
230
- return bEngagement - aEngagement;
231
- })
232
- .slice(0, 5)
233
- .map((tweet) => ({
234
- id: tweet.id,
235
- text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''),
236
- createdAt: tweet.created_at,
237
- metrics: tweet.public_metrics,
238
- engagement: this.calculateTweetEngagement(tweet.public_metrics!),
239
- }));
240
-
241
- // Update output parameters
242
- const outputParams = [...params];
243
- const overallParam = outputParams.find((p) => p.Name === 'OverallMetrics');
244
- if (overallParam) overallParam.Value = overallMetrics;
245
- const timeBasedParam = outputParams.find((p) => p.Name === 'TimeBasedAnalytics');
246
- if (timeBasedParam) timeBasedParam.Value = timeBasedAnalytics;
247
-
248
- return {
249
- Success: true,
250
- ResultCode: 'SUCCESS',
251
- Message: `Successfully retrieved account analytics for ${tweets.length} tweets`,
252
- Params: outputParams,
253
- };
254
- } catch (error) {
255
- throw error;
256
357
  }
257
- }
258
-
259
- /**
260
- * Calculate aggregate metrics from tweet analytics
261
- */
262
- private calculateAggregateMetrics(tweetAnalytics: any[]): any {
263
- const aggregate = {
264
- totalImpressions: 0,
265
- totalEngagements: 0,
266
- totalLikes: 0,
267
- totalRetweets: 0,
268
- totalReplies: 0,
269
- averageEngagementRate: 0,
270
- bestPerformingTweet: null as any,
271
- worstPerformingTweet: null as any,
272
- };
273
-
274
- let bestEngagement = -1;
275
- let worstEngagement = Infinity;
276
-
277
- tweetAnalytics.forEach((analytics) => {
278
- const metrics = analytics.metrics;
279
- aggregate.totalImpressions += metrics.impressions;
280
- aggregate.totalEngagements += metrics.engagements;
281
- aggregate.totalLikes += metrics.likes;
282
- aggregate.totalRetweets += metrics.shares;
283
- aggregate.totalReplies += metrics.comments;
284
-
285
- if (metrics.engagements > bestEngagement) {
286
- bestEngagement = metrics.engagements;
287
- aggregate.bestPerformingTweet = analytics;
288
- }
289
- if (metrics.engagements < worstEngagement) {
290
- worstEngagement = metrics.engagements;
291
- aggregate.worstPerformingTweet = analytics;
292
- }
293
- });
294
-
295
- if (aggregate.totalImpressions > 0) {
296
- aggregate.averageEngagementRate = parseFloat(((aggregate.totalEngagements / aggregate.totalImpressions) * 100).toFixed(2));
358
+
359
+ /**
360
+ * Calculate tweet engagement from public metrics
361
+ */
362
+ private calculateTweetEngagement(metrics: any): number {
363
+ return (metrics.like_count || 0) +
364
+ (metrics.retweet_count || 0) +
365
+ (metrics.reply_count || 0) +
366
+ (metrics.quote_count || 0);
297
367
  }
298
368
 
299
- return aggregate;
300
- }
301
-
302
- /**
303
- * Calculate time-based analytics
304
- */
305
- private calculateTimeBasedAnalytics(tweets: Tweet[], granularity: string): any[] {
306
- const buckets: Map<string, any> = new Map();
307
-
308
- tweets.forEach((tweet) => {
309
- const date = new Date(tweet.created_at);
310
- let bucketKey: string;
311
-
312
- switch (granularity) {
313
- case 'hour':
314
- bucketKey = `${date.toISOString().slice(0, 13)}:00:00Z`;
315
- break;
316
- case 'day':
317
- bucketKey = date.toISOString().slice(0, 10);
318
- break;
319
- case 'total':
320
- bucketKey = 'total';
321
- break;
322
- default:
323
- bucketKey = date.toISOString().slice(0, 10);
324
- }
325
-
326
- if (!buckets.has(bucketKey)) {
327
- buckets.set(bucketKey, {
328
- period: bucketKey,
329
- tweets: 0,
330
- impressions: 0,
331
- engagements: 0,
332
- likes: 0,
333
- retweets: 0,
334
- replies: 0,
335
- });
336
- }
337
-
338
- const bucket = buckets.get(bucketKey)!;
339
- bucket.tweets++;
340
-
341
- if (tweet.public_metrics) {
342
- bucket.impressions += tweet.public_metrics.impression_count || 0;
343
- bucket.likes += tweet.public_metrics.like_count || 0;
344
- bucket.retweets += tweet.public_metrics.retweet_count || 0;
345
- bucket.replies += tweet.public_metrics.reply_count || 0;
346
- bucket.engagements += this.calculateTweetEngagement(tweet.public_metrics);
347
- }
348
- });
349
-
350
- return Array.from(buckets.values()).sort((a, b) => a.period.localeCompare(b.period));
351
- }
352
-
353
- /**
354
- * Calculate tweet engagement from public metrics
355
- */
356
- private calculateTweetEngagement(metrics: any): number {
357
- return (metrics.like_count || 0) + (metrics.retweet_count || 0) + (metrics.reply_count || 0) + (metrics.quote_count || 0);
358
- }
359
-
360
- /**
361
- * Get error code based on error type
362
- */
363
- private getErrorCode(error: any): string {
364
- if (error instanceof Error) {
365
- if (error.message.includes('Rate Limit')) return 'RATE_LIMIT';
366
- if (error.message.includes('Unauthorized')) return 'INVALID_TOKEN';
367
- if (error.message.includes('Forbidden')) return 'INSUFFICIENT_PERMISSIONS';
369
+ /**
370
+ * Get error code based on error type
371
+ */
372
+ private getErrorCode(error: any): string {
373
+ if (error instanceof Error) {
374
+ if (error.message.includes('Rate Limit')) return 'RATE_LIMIT';
375
+ if (error.message.includes('Unauthorized')) return 'INVALID_TOKEN';
376
+ if (error.message.includes('Forbidden')) return 'INSUFFICIENT_PERMISSIONS';
377
+ }
378
+ return 'ERROR';
379
+ }
380
+
381
+ /**
382
+ * Define the parameters this action expects
383
+ */
384
+ public get Params(): ActionParam[] {
385
+ return [
386
+ ...this.commonSocialParams,
387
+ {
388
+ Name: 'AnalyticsType',
389
+ Type: 'Input',
390
+ Value: 'account' // 'account' or 'tweets'
391
+ },
392
+ {
393
+ Name: 'TweetIDs',
394
+ Type: 'Input',
395
+ Value: null
396
+ },
397
+ {
398
+ Name: 'StartDate',
399
+ Type: 'Input',
400
+ Value: null
401
+ },
402
+ {
403
+ Name: 'EndDate',
404
+ Type: 'Input',
405
+ Value: null
406
+ },
407
+ {
408
+ Name: 'Granularity',
409
+ Type: 'Input',
410
+ Value: 'day' // 'hour', 'day', 'total'
411
+ },
412
+ {
413
+ Name: 'Analytics',
414
+ Type: 'Output',
415
+ Value: null
416
+ },
417
+ {
418
+ Name: 'AggregateMetrics',
419
+ Type: 'Output',
420
+ Value: null
421
+ },
422
+ {
423
+ Name: 'OverallMetrics',
424
+ Type: 'Output',
425
+ Value: null
426
+ },
427
+ {
428
+ Name: 'TimeBasedAnalytics',
429
+ Type: 'Output',
430
+ Value: null
431
+ }
432
+ ];
433
+ }
434
+
435
+ /**
436
+ * Get action description
437
+ */
438
+ public get Description(): string {
439
+ return 'Gets analytics data from Twitter/X for specific tweets or account-level metrics with time-based analysis';
368
440
  }
369
- return 'ERROR';
370
- }
371
-
372
- /**
373
- * Define the parameters this action expects
374
- */
375
- public get Params(): ActionParam[] {
376
- return [
377
- ...this.commonSocialParams,
378
- {
379
- Name: 'AnalyticsType',
380
- Type: 'Input',
381
- Value: 'account', // 'account' or 'tweets'
382
- },
383
- {
384
- Name: 'TweetIDs',
385
- Type: 'Input',
386
- Value: null,
387
- },
388
- {
389
- Name: 'StartDate',
390
- Type: 'Input',
391
- Value: null,
392
- },
393
- {
394
- Name: 'EndDate',
395
- Type: 'Input',
396
- Value: null,
397
- },
398
- {
399
- Name: 'Granularity',
400
- Type: 'Input',
401
- Value: 'day', // 'hour', 'day', 'total'
402
- },
403
- {
404
- Name: 'Analytics',
405
- Type: 'Output',
406
- Value: null,
407
- },
408
- {
409
- Name: 'AggregateMetrics',
410
- Type: 'Output',
411
- Value: null,
412
- },
413
- {
414
- Name: 'OverallMetrics',
415
- Type: 'Output',
416
- Value: null,
417
- },
418
- {
419
- Name: 'TimeBasedAnalytics',
420
- Type: 'Output',
421
- Value: null,
422
- },
423
- ];
424
- }
425
-
426
- /**
427
- * Get action description
428
- */
429
- public get Description(): string {
430
- return 'Gets analytics data from Twitter/X for specific tweets or account-level metrics with time-based analysis';
431
- }
432
- }
441
+ }