@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 { InstagramBaseAction } from '../instagram-base.action';
3
3
  import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
4
- import { LogError } from '@memberjunction/global';
4
+ import { LogError } from '@memberjunction/core';
5
5
  import { SocialPost, SearchParams } from '../../../base/base-social.action';
6
6
  import { BaseAction } from '@memberjunction/actions';
7
7
 
@@ -12,533 +12,559 @@ import { BaseAction } from '@memberjunction/actions';
12
12
  */
13
13
  @RegisterClass(BaseAction, 'Instagram - Search Posts')
14
14
  export class InstagramSearchPostsAction extends InstagramBaseAction {
15
- protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
16
- try {
17
- const companyIntegrationId = this.getParamValue(params.Params, 'CompanyIntegrationID');
18
- const query = this.getParamValue(params.Params, 'Query');
19
- const hashtags = this.getParamValue(params.Params, 'Hashtags') as string[];
20
- const startDate = this.getParamValue(params.Params, 'StartDate');
21
- const endDate = this.getParamValue(params.Params, 'EndDate');
22
- const mediaType = this.getParamValue(params.Params, 'MediaType');
23
- const minEngagement = this.getParamValue(params.Params, 'MinEngagement') || 0;
24
- const limit = this.getParamValue(params.Params, 'Limit') || 100;
25
- const includeArchived = this.getParamValue(params.Params, 'IncludeArchived') || false;
26
-
27
- // Initialize OAuth
28
- if (!(await this.initializeOAuth(companyIntegrationId))) {
29
- return {
30
- Success: false,
31
- Message: 'Failed to initialize Instagram authentication',
32
- ResultCode: 'AUTH_FAILED',
33
- };
34
- }
35
-
36
- // Build search parameters
37
- const searchParams: SearchParams = {
38
- query,
39
- hashtags,
40
- startDate: startDate ? new Date(startDate) : undefined,
41
- endDate: endDate ? new Date(endDate) : undefined,
42
- limit,
43
- };
44
-
45
- // Perform search
46
- const posts = await this.searchPosts(searchParams);
47
-
48
- // Filter by media type if specified
49
- let filteredPosts = posts;
50
- if (mediaType) {
51
- filteredPosts = posts.filter((post) => post.platformSpecificData.mediaType === mediaType);
52
- }
53
-
54
- // Filter by minimum engagement
55
- if (minEngagement > 0) {
56
- filteredPosts = filteredPosts.filter((post) => {
57
- const totalEngagement = (post.analytics?.likes || 0) + (post.analytics?.comments || 0);
58
- return totalEngagement >= minEngagement;
15
+
16
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
17
+ try {
18
+ const companyIntegrationId = this.getParamValue(params.Params, 'CompanyIntegrationID');
19
+ const query = this.getParamValue(params.Params, 'Query');
20
+ const hashtags = this.getParamValue(params.Params, 'Hashtags') as string[];
21
+ const startDate = this.getParamValue(params.Params, 'StartDate');
22
+ const endDate = this.getParamValue(params.Params, 'EndDate');
23
+ const mediaType = this.getParamValue(params.Params, 'MediaType');
24
+ const minEngagement = this.getParamValue(params.Params, 'MinEngagement') || 0;
25
+ const limit = this.getParamValue(params.Params, 'Limit') || 100;
26
+ const includeArchived = this.getParamValue(params.Params, 'IncludeArchived') || false;
27
+
28
+ // Initialize OAuth
29
+ if (!await this.initializeOAuth(companyIntegrationId)) {
30
+ return {
31
+ Success: false,
32
+ Message: 'Failed to initialize Instagram authentication',
33
+ ResultCode: 'AUTH_FAILED'
34
+ };
35
+ }
36
+
37
+ // Build search parameters
38
+ const searchParams: SearchParams = {
39
+ query,
40
+ hashtags,
41
+ startDate: startDate ? new Date(startDate) : undefined,
42
+ endDate: endDate ? new Date(endDate) : undefined,
43
+ limit
44
+ };
45
+
46
+ // Perform search
47
+ const posts = await this.searchPosts(searchParams);
48
+
49
+ // Filter by media type if specified
50
+ let filteredPosts = posts;
51
+ if (mediaType) {
52
+ filteredPosts = posts.filter(post =>
53
+ post.platformSpecificData.mediaType === mediaType
54
+ );
55
+ }
56
+
57
+ // Filter by minimum engagement
58
+ if (minEngagement > 0) {
59
+ filteredPosts = filteredPosts.filter(post => {
60
+ const totalEngagement =
61
+ (post.analytics?.likes || 0) +
62
+ (post.analytics?.comments || 0);
63
+ return totalEngagement >= minEngagement;
64
+ });
65
+ }
66
+
67
+ // Include archived posts if requested
68
+ if (includeArchived) {
69
+ const archivedPosts = await this.getArchivedPosts(searchParams);
70
+ filteredPosts = [...filteredPosts, ...archivedPosts];
71
+ }
72
+
73
+ // Sort posts by relevance
74
+ const sortedPosts = this.sortByRelevance(filteredPosts, query, hashtags);
75
+
76
+ // Analyze search results
77
+ const analysis = this.analyzeSearchResults(sortedPosts, searchParams);
78
+
79
+ // Store result in output params
80
+ const outputParams = [...params.Params];
81
+ outputParams.push({
82
+ Name: 'ResultData',
83
+ Type: 'Output',
84
+ Value: JSON.stringify({
85
+ posts: sortedPosts.slice(0, limit),
86
+ totalFound: sortedPosts.length,
87
+ searchCriteria: {
88
+ query,
89
+ hashtags,
90
+ dateRange: {
91
+ start: startDate,
92
+ end: endDate
93
+ },
94
+ mediaType,
95
+ minEngagement
96
+ },
97
+ analysis,
98
+ suggestions: this.generateSearchSuggestions(sortedPosts, searchParams)
99
+ })
100
+ });
101
+
102
+ return {
103
+ Success: true,
104
+ Message: `Found ${sortedPosts.length} matching posts`,
105
+ ResultCode: 'SUCCESS',
106
+ Params: outputParams
107
+ };
108
+
109
+ } catch (error: any) {
110
+ LogError('Failed to search Instagram posts', error);
111
+
112
+ if (error.code === 'RATE_LIMIT') {
113
+ return {
114
+ Success: false,
115
+ Message: 'Instagram API rate limit exceeded. Please try again later.',
116
+ ResultCode: 'RATE_LIMIT'
117
+ };
118
+ }
119
+
120
+ return {
121
+ Success: false,
122
+ Message: `Failed to search posts: ${error.message}`,
123
+ ResultCode: 'ERROR'
124
+ };
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Search posts implementation (Instagram only allows searching own posts)
130
+ */
131
+ protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
132
+ // Instagram doesn't have a public search API, so we fetch all posts
133
+ // and filter them client-side
134
+ const allPosts = await this.fetchAllAccountPosts(params.startDate, params.endDate);
135
+
136
+ // Filter posts based on search criteria
137
+ let filtered = allPosts;
138
+
139
+ // Filter by query in caption
140
+ if (params.query) {
141
+ const queryLower = params.query.toLowerCase();
142
+ filtered = filtered.filter(post =>
143
+ post.content.toLowerCase().includes(queryLower)
144
+ );
145
+ }
146
+
147
+ // Filter by hashtags
148
+ if (params.hashtags && params.hashtags.length > 0) {
149
+ const searchHashtags = params.hashtags.map(tag =>
150
+ tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase()
151
+ );
152
+
153
+ filtered = filtered.filter(post => {
154
+ const postHashtags = this.extractHashtags(post.content);
155
+ return searchHashtags.some(searchTag =>
156
+ postHashtags.includes(searchTag)
157
+ );
158
+ });
159
+ }
160
+
161
+ return filtered.slice(0, params.limit || 100);
162
+ }
163
+
164
+ /**
165
+ * Fetch all posts from the account within date range
166
+ */
167
+ private async fetchAllAccountPosts(startDate?: Date, endDate?: Date): Promise<SocialPost[]> {
168
+ const posts: SocialPost[] = [];
169
+ let hasNext = true;
170
+ let afterCursor: string | undefined;
171
+
172
+ while (hasNext) {
173
+ const queryParams: any = {
174
+ fields: 'id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count',
175
+ access_token: this.getAccessToken(),
176
+ limit: 100
177
+ };
178
+
179
+ if (afterCursor) {
180
+ queryParams.after = afterCursor;
181
+ }
182
+
183
+ if (startDate) {
184
+ queryParams.since = Math.floor(startDate.getTime() / 1000);
185
+ }
186
+ if (endDate) {
187
+ queryParams.until = Math.floor(endDate.getTime() / 1000);
188
+ }
189
+
190
+ const response = await this.makeInstagramRequest<{
191
+ data: any[];
192
+ paging?: {
193
+ cursors: { after: string };
194
+ next?: string;
195
+ };
196
+ }>(
197
+ `${this.instagramBusinessAccountId}/media`,
198
+ 'GET',
199
+ null,
200
+ queryParams
201
+ );
202
+
203
+ if (response.data) {
204
+ const normalizedPosts = response.data.map(post => this.normalizePost(post));
205
+ posts.push(...normalizedPosts);
206
+ }
207
+
208
+ if (response.paging?.next && response.paging?.cursors?.after) {
209
+ afterCursor = response.paging.cursors.after;
210
+ } else {
211
+ hasNext = false;
212
+ }
213
+
214
+ // Stop if we've reached the date range limit
215
+ if (posts.length > 0 && startDate) {
216
+ const oldestPost = posts[posts.length - 1];
217
+ if (oldestPost.publishedAt < startDate) {
218
+ hasNext = false;
219
+ }
220
+ }
221
+ }
222
+
223
+ return posts;
224
+ }
225
+
226
+ /**
227
+ * Get archived posts (if any)
228
+ */
229
+ private async getArchivedPosts(params: SearchParams): Promise<SocialPost[]> {
230
+ try {
231
+ // Instagram API doesn't have a specific endpoint for archived posts
232
+ // This would require additional implementation or different API access
233
+ return [];
234
+ } catch (error) {
235
+ LogError('Failed to get archived posts', error);
236
+ return [];
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Extract hashtags from content
242
+ */
243
+ private extractHashtags(content: string): string[] {
244
+ const hashtagRegex = /#[\w\u0590-\u05ff]+/g;
245
+ const matches = content.match(hashtagRegex) || [];
246
+ return matches.map(tag => tag.toLowerCase());
247
+ }
248
+
249
+ /**
250
+ * Sort posts by relevance
251
+ */
252
+ private sortByRelevance(
253
+ posts: SocialPost[],
254
+ query?: string,
255
+ hashtags?: string[]
256
+ ): SocialPost[] {
257
+ return posts.sort((a, b) => {
258
+ let scoreA = 0;
259
+ let scoreB = 0;
260
+
261
+ // Score based on query match position
262
+ if (query) {
263
+ const queryLower = query.toLowerCase();
264
+ const posA = a.content.toLowerCase().indexOf(queryLower);
265
+ const posB = b.content.toLowerCase().indexOf(queryLower);
266
+
267
+ if (posA === 0) scoreA += 10; // Starts with query
268
+ else if (posA > 0) scoreA += 5; // Contains query
269
+
270
+ if (posB === 0) scoreB += 10;
271
+ else if (posB > 0) scoreB += 5;
272
+ }
273
+
274
+ // Score based on hashtag matches
275
+ if (hashtags && hashtags.length > 0) {
276
+ const hashtagsA = this.extractHashtags(a.content);
277
+ const hashtagsB = this.extractHashtags(b.content);
278
+
279
+ hashtags.forEach(tag => {
280
+ const searchTag = tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase();
281
+ if (hashtagsA.includes(searchTag)) scoreA += 3;
282
+ if (hashtagsB.includes(searchTag)) scoreB += 3;
283
+ });
284
+ }
285
+
286
+ // Score based on engagement
287
+ const engagementA = (a.analytics?.likes || 0) + (a.analytics?.comments || 0);
288
+ const engagementB = (b.analytics?.likes || 0) + (b.analytics?.comments || 0);
289
+
290
+ scoreA += Math.log10(engagementA + 1);
291
+ scoreB += Math.log10(engagementB + 1);
292
+
293
+ // Sort by score (descending) then by date (descending)
294
+ if (scoreA !== scoreB) {
295
+ return scoreB - scoreA;
296
+ }
297
+ return b.publishedAt.getTime() - a.publishedAt.getTime();
59
298
  });
60
- }
61
-
62
- // Include archived posts if requested
63
- if (includeArchived) {
64
- const archivedPosts = await this.getArchivedPosts(searchParams);
65
- filteredPosts = [...filteredPosts, ...archivedPosts];
66
- }
67
-
68
- // Sort posts by relevance
69
- const sortedPosts = this.sortByRelevance(filteredPosts, query, hashtags);
70
-
71
- // Analyze search results
72
- const analysis = this.analyzeSearchResults(sortedPosts, searchParams);
73
-
74
- // Store result in output params
75
- const outputParams = [...params.Params];
76
- outputParams.push({
77
- Name: 'ResultData',
78
- Type: 'Output',
79
- Value: JSON.stringify({
80
- posts: sortedPosts.slice(0, limit),
81
- totalFound: sortedPosts.length,
82
- searchCriteria: {
83
- query,
84
- hashtags,
299
+ }
300
+
301
+ /**
302
+ * Analyze search results
303
+ */
304
+ private analyzeSearchResults(posts: SocialPost[], params: SearchParams): any {
305
+ const analysis = {
306
+ totalPosts: posts.length,
85
307
  dateRange: {
86
- start: startDate,
87
- end: endDate,
308
+ earliest: null as Date | null,
309
+ latest: null as Date | null
310
+ },
311
+ mediaTypes: {
312
+ IMAGE: 0,
313
+ VIDEO: 0,
314
+ CAROUSEL_ALBUM: 0,
315
+ REELS: 0
316
+ },
317
+ engagement: {
318
+ totalLikes: 0,
319
+ totalComments: 0,
320
+ avgLikesPerPost: 0,
321
+ avgCommentsPerPost: 0,
322
+ topPost: null as any
88
323
  },
89
- mediaType,
90
- minEngagement,
91
- },
92
- analysis,
93
- suggestions: this.generateSearchSuggestions(sortedPosts, searchParams),
94
- }),
95
- });
96
-
97
- return {
98
- Success: true,
99
- Message: `Found ${sortedPosts.length} matching posts`,
100
- ResultCode: 'SUCCESS',
101
- Params: outputParams,
102
- };
103
- } catch (error: any) {
104
- LogError('Failed to search Instagram posts', error);
105
-
106
- if (error.code === 'RATE_LIMIT') {
107
- return {
108
- Success: false,
109
- Message: 'Instagram API rate limit exceeded. Please try again later.',
110
- ResultCode: 'RATE_LIMIT',
324
+ hashtagFrequency: {} as Record<string, number>,
325
+ postingPatterns: {
326
+ byDayOfWeek: {} as Record<string, number>,
327
+ byHour: {} as Record<number, number>
328
+ }
111
329
  };
112
- }
113
330
 
114
- return {
115
- Success: false,
116
- Message: `Failed to search posts: ${error.message}`,
117
- ResultCode: 'ERROR',
118
- };
119
- }
120
- }
121
-
122
- /**
123
- * Search posts implementation (Instagram only allows searching own posts)
124
- */
125
- protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
126
- // Instagram doesn't have a public search API, so we fetch all posts
127
- // and filter them client-side
128
- const allPosts = await this.fetchAllAccountPosts(params.startDate, params.endDate);
129
-
130
- // Filter posts based on search criteria
131
- let filtered = allPosts;
132
-
133
- // Filter by query in caption
134
- if (params.query) {
135
- const queryLower = params.query.toLowerCase();
136
- filtered = filtered.filter((post) => post.content.toLowerCase().includes(queryLower));
137
- }
331
+ if (posts.length === 0) return analysis;
332
+
333
+ // Find date range
334
+ const dates = posts.map(p => p.publishedAt.getTime());
335
+ analysis.dateRange.earliest = new Date(Math.min(...dates));
336
+ analysis.dateRange.latest = new Date(Math.max(...dates));
337
+
338
+ // Analyze posts
339
+ let topEngagement = 0;
340
+ posts.forEach(post => {
341
+ // Media types
342
+ const mediaType = post.platformSpecificData.mediaType;
343
+ if (analysis.mediaTypes[mediaType] !== undefined) {
344
+ analysis.mediaTypes[mediaType]++;
345
+ }
346
+
347
+ // Engagement
348
+ const likes = post.analytics?.likes || 0;
349
+ const comments = post.analytics?.comments || 0;
350
+ const totalEngagement = likes + comments;
351
+
352
+ analysis.engagement.totalLikes += likes;
353
+ analysis.engagement.totalComments += comments;
354
+
355
+ if (totalEngagement > topEngagement) {
356
+ topEngagement = totalEngagement;
357
+ analysis.engagement.topPost = {
358
+ id: post.id,
359
+ content: post.content.substring(0, 100) + '...',
360
+ engagement: totalEngagement,
361
+ publishedAt: post.publishedAt
362
+ };
363
+ }
364
+
365
+ // Hashtags
366
+ const hashtags = this.extractHashtags(post.content);
367
+ hashtags.forEach(tag => {
368
+ analysis.hashtagFrequency[tag] = (analysis.hashtagFrequency[tag] || 0) + 1;
369
+ });
370
+
371
+ // Posting patterns
372
+ const dayOfWeek = post.publishedAt.toLocaleDateString('en-US', { weekday: 'long' });
373
+ const hour = post.publishedAt.getHours();
374
+
375
+ analysis.postingPatterns.byDayOfWeek[dayOfWeek] =
376
+ (analysis.postingPatterns.byDayOfWeek[dayOfWeek] || 0) + 1;
377
+ analysis.postingPatterns.byHour[hour] =
378
+ (analysis.postingPatterns.byHour[hour] || 0) + 1;
379
+ });
138
380
 
139
- // Filter by hashtags
140
- if (params.hashtags && params.hashtags.length > 0) {
141
- const searchHashtags = params.hashtags.map((tag) => (tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase()));
381
+ // Calculate averages
382
+ analysis.engagement.avgLikesPerPost =
383
+ Math.round(analysis.engagement.totalLikes / posts.length);
384
+ analysis.engagement.avgCommentsPerPost =
385
+ Math.round(analysis.engagement.totalComments / posts.length);
142
386
 
143
- filtered = filtered.filter((post) => {
144
- const postHashtags = this.extractHashtags(post.content);
145
- return searchHashtags.some((searchTag) => postHashtags.includes(searchTag));
146
- });
387
+ return analysis;
147
388
  }
148
389
 
149
- return filtered.slice(0, params.limit || 100);
150
- }
151
-
152
- /**
153
- * Fetch all posts from the account within date range
154
- */
155
- private async fetchAllAccountPosts(startDate?: Date, endDate?: Date): Promise<SocialPost[]> {
156
- const posts: SocialPost[] = [];
157
- let hasNext = true;
158
- let afterCursor: string | undefined;
159
-
160
- while (hasNext) {
161
- const queryParams: any = {
162
- fields: 'id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count',
163
- access_token: this.getAccessToken(),
164
- limit: 100,
165
- };
166
-
167
- if (afterCursor) {
168
- queryParams.after = afterCursor;
169
- }
170
-
171
- if (startDate) {
172
- queryParams.since = Math.floor(startDate.getTime() / 1000);
173
- }
174
- if (endDate) {
175
- queryParams.until = Math.floor(endDate.getTime() / 1000);
176
- }
177
-
178
- const response = await this.makeInstagramRequest<{
179
- data: any[];
180
- paging?: {
181
- cursors: { after: string };
182
- next?: string;
390
+ /**
391
+ * Generate search suggestions based on results
392
+ */
393
+ private generateSearchSuggestions(posts: SocialPost[], params: SearchParams): any {
394
+ const suggestions = {
395
+ relatedHashtags: [] as string[],
396
+ optimalPostingTimes: [] as any[],
397
+ contentThemes: [] as string[],
398
+ performanceInsights: [] as string[]
183
399
  };
184
- }>(`${this.instagramBusinessAccountId}/media`, 'GET', null, queryParams);
185
-
186
- if (response.data) {
187
- const normalizedPosts = response.data.map((post) => this.normalizePost(post));
188
- posts.push(...normalizedPosts);
189
- }
190
-
191
- if (response.paging?.next && response.paging?.cursors?.after) {
192
- afterCursor = response.paging.cursors.after;
193
- } else {
194
- hasNext = false;
195
- }
196
-
197
- // Stop if we've reached the date range limit
198
- if (posts.length > 0 && startDate) {
199
- const oldestPost = posts[posts.length - 1];
200
- if (oldestPost.publishedAt < startDate) {
201
- hasNext = false;
400
+
401
+ if (posts.length === 0) {
402
+ suggestions.performanceInsights.push('No posts found matching your criteria. Try broadening your search.');
403
+ return suggestions;
202
404
  }
203
- }
204
- }
205
405
 
206
- return posts;
207
- }
208
-
209
- /**
210
- * Get archived posts (if any)
211
- */
212
- private async getArchivedPosts(params: SearchParams): Promise<SocialPost[]> {
213
- try {
214
- // Instagram API doesn't have a specific endpoint for archived posts
215
- // This would require additional implementation or different API access
216
- return [];
217
- } catch (error) {
218
- LogError('Failed to get archived posts', error);
219
- return [];
406
+ // Related hashtags (most frequently used)
407
+ const hashtagCounts: Record<string, number> = {};
408
+ posts.forEach(post => {
409
+ const hashtags = this.extractHashtags(post.content);
410
+ hashtags.forEach(tag => {
411
+ if (!params.hashtags?.includes(tag)) {
412
+ hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;
413
+ }
414
+ });
415
+ });
416
+
417
+ suggestions.relatedHashtags = Object.entries(hashtagCounts)
418
+ .sort(([, a], [, b]) => b - a)
419
+ .slice(0, 10)
420
+ .map(([tag]) => tag);
421
+
422
+ // Optimal posting times (based on engagement)
423
+ const timeEngagement: Record<string, { total: number; count: number }> = {};
424
+ posts.forEach(post => {
425
+ const hour = post.publishedAt.getHours();
426
+ const day = post.publishedAt.toLocaleDateString('en-US', { weekday: 'long' });
427
+ const key = `${day} ${hour}:00`;
428
+
429
+ if (!timeEngagement[key]) {
430
+ timeEngagement[key] = { total: 0, count: 0 };
431
+ }
432
+
433
+ const engagement = (post.analytics?.likes || 0) + (post.analytics?.comments || 0);
434
+ timeEngagement[key].total += engagement;
435
+ timeEngagement[key].count++;
436
+ });
437
+
438
+ suggestions.optimalPostingTimes = Object.entries(timeEngagement)
439
+ .map(([time, data]) => ({
440
+ time,
441
+ avgEngagement: Math.round(data.total / data.count)
442
+ }))
443
+ .sort((a, b) => b.avgEngagement - a.avgEngagement)
444
+ .slice(0, 5);
445
+
446
+ // Performance insights
447
+ const avgEngagement = posts.reduce((sum, post) =>
448
+ sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0
449
+ ) / posts.length;
450
+
451
+ suggestions.performanceInsights.push(
452
+ `Average engagement per post: ${Math.round(avgEngagement)}`,
453
+ `Most successful media type: ${this.getMostSuccessfulMediaType(posts)}`,
454
+ `Posts with questions get ${this.getQuestionEngagementBoost(posts)}% more engagement`
455
+ );
456
+
457
+ return suggestions;
220
458
  }
221
- }
222
-
223
- /**
224
- * Extract hashtags from content
225
- */
226
- private extractHashtags(content: string): string[] {
227
- const hashtagRegex = /#[\w\u0590-\u05ff]+/g;
228
- const matches = content.match(hashtagRegex) || [];
229
- return matches.map((tag) => tag.toLowerCase());
230
- }
231
-
232
- /**
233
- * Sort posts by relevance
234
- */
235
- private sortByRelevance(posts: SocialPost[], query?: string, hashtags?: string[]): SocialPost[] {
236
- return posts.sort((a, b) => {
237
- let scoreA = 0;
238
- let scoreB = 0;
239
-
240
- // Score based on query match position
241
- if (query) {
242
- const queryLower = query.toLowerCase();
243
- const posA = a.content.toLowerCase().indexOf(queryLower);
244
- const posB = b.content.toLowerCase().indexOf(queryLower);
245
-
246
- if (posA === 0)
247
- scoreA += 10; // Starts with query
248
- else if (posA > 0) scoreA += 5; // Contains query
249
-
250
- if (posB === 0) scoreB += 10;
251
- else if (posB > 0) scoreB += 5;
252
- }
253
-
254
- // Score based on hashtag matches
255
- if (hashtags && hashtags.length > 0) {
256
- const hashtagsA = this.extractHashtags(a.content);
257
- const hashtagsB = this.extractHashtags(b.content);
258
-
259
- hashtags.forEach((tag) => {
260
- const searchTag = tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase();
261
- if (hashtagsA.includes(searchTag)) scoreA += 3;
262
- if (hashtagsB.includes(searchTag)) scoreB += 3;
459
+
460
+ /**
461
+ * Get most successful media type
462
+ */
463
+ private getMostSuccessfulMediaType(posts: SocialPost[]): string {
464
+ const typeEngagement: Record<string, { total: number; count: number }> = {};
465
+
466
+ posts.forEach(post => {
467
+ const type = post.platformSpecificData.mediaType;
468
+ if (!typeEngagement[type]) {
469
+ typeEngagement[type] = { total: 0, count: 0 };
470
+ }
471
+
472
+ const engagement = (post.analytics?.likes || 0) + (post.analytics?.comments || 0);
473
+ typeEngagement[type].total += engagement;
474
+ typeEngagement[type].count++;
263
475
  });
264
- }
265
-
266
- // Score based on engagement
267
- const engagementA = (a.analytics?.likes || 0) + (a.analytics?.comments || 0);
268
- const engagementB = (b.analytics?.likes || 0) + (b.analytics?.comments || 0);
269
-
270
- scoreA += Math.log10(engagementA + 1);
271
- scoreB += Math.log10(engagementB + 1);
272
-
273
- // Sort by score (descending) then by date (descending)
274
- if (scoreA !== scoreB) {
275
- return scoreB - scoreA;
276
- }
277
- return b.publishedAt.getTime() - a.publishedAt.getTime();
278
- });
279
- }
280
-
281
- /**
282
- * Analyze search results
283
- */
284
- private analyzeSearchResults(posts: SocialPost[], params: SearchParams): any {
285
- const analysis = {
286
- totalPosts: posts.length,
287
- dateRange: {
288
- earliest: null as Date | null,
289
- latest: null as Date | null,
290
- },
291
- mediaTypes: {
292
- IMAGE: 0,
293
- VIDEO: 0,
294
- CAROUSEL_ALBUM: 0,
295
- REELS: 0,
296
- },
297
- engagement: {
298
- totalLikes: 0,
299
- totalComments: 0,
300
- avgLikesPerPost: 0,
301
- avgCommentsPerPost: 0,
302
- topPost: null as any,
303
- },
304
- hashtagFrequency: {} as Record<string, number>,
305
- postingPatterns: {
306
- byDayOfWeek: {} as Record<string, number>,
307
- byHour: {} as Record<number, number>,
308
- },
309
- };
310
-
311
- if (posts.length === 0) return analysis;
312
-
313
- // Find date range
314
- const dates = posts.map((p) => p.publishedAt.getTime());
315
- analysis.dateRange.earliest = new Date(Math.min(...dates));
316
- analysis.dateRange.latest = new Date(Math.max(...dates));
317
-
318
- // Analyze posts
319
- let topEngagement = 0;
320
- posts.forEach((post) => {
321
- // Media types
322
- const mediaType = post.platformSpecificData.mediaType;
323
- if (analysis.mediaTypes[mediaType] !== undefined) {
324
- analysis.mediaTypes[mediaType]++;
325
- }
326
-
327
- // Engagement
328
- const likes = post.analytics?.likes || 0;
329
- const comments = post.analytics?.comments || 0;
330
- const totalEngagement = likes + comments;
331
-
332
- analysis.engagement.totalLikes += likes;
333
- analysis.engagement.totalComments += comments;
334
-
335
- if (totalEngagement > topEngagement) {
336
- topEngagement = totalEngagement;
337
- analysis.engagement.topPost = {
338
- id: post.id,
339
- content: post.content.substring(0, 100) + '...',
340
- engagement: totalEngagement,
341
- publishedAt: post.publishedAt,
342
- };
343
- }
344
-
345
- // Hashtags
346
- const hashtags = this.extractHashtags(post.content);
347
- hashtags.forEach((tag) => {
348
- analysis.hashtagFrequency[tag] = (analysis.hashtagFrequency[tag] || 0) + 1;
349
- });
350
-
351
- // Posting patterns
352
- const dayOfWeek = post.publishedAt.toLocaleDateString('en-US', { weekday: 'long' });
353
- const hour = post.publishedAt.getHours();
354
-
355
- analysis.postingPatterns.byDayOfWeek[dayOfWeek] = (analysis.postingPatterns.byDayOfWeek[dayOfWeek] || 0) + 1;
356
- analysis.postingPatterns.byHour[hour] = (analysis.postingPatterns.byHour[hour] || 0) + 1;
357
- });
358
-
359
- // Calculate averages
360
- analysis.engagement.avgLikesPerPost = Math.round(analysis.engagement.totalLikes / posts.length);
361
- analysis.engagement.avgCommentsPerPost = Math.round(analysis.engagement.totalComments / posts.length);
362
-
363
- return analysis;
364
- }
365
-
366
- /**
367
- * Generate search suggestions based on results
368
- */
369
- private generateSearchSuggestions(posts: SocialPost[], params: SearchParams): any {
370
- const suggestions = {
371
- relatedHashtags: [] as string[],
372
- optimalPostingTimes: [] as any[],
373
- contentThemes: [] as string[],
374
- performanceInsights: [] as string[],
375
- };
376
-
377
- if (posts.length === 0) {
378
- suggestions.performanceInsights.push('No posts found matching your criteria. Try broadening your search.');
379
- return suggestions;
476
+
477
+ let bestType = 'IMAGE';
478
+ let bestAvg = 0;
479
+
480
+ Object.entries(typeEngagement).forEach(([type, data]) => {
481
+ const avg = data.total / data.count;
482
+ if (avg > bestAvg) {
483
+ bestAvg = avg;
484
+ bestType = type;
485
+ }
486
+ });
487
+
488
+ return bestType;
380
489
  }
381
490
 
382
- // Related hashtags (most frequently used)
383
- const hashtagCounts: Record<string, number> = {};
384
- posts.forEach((post) => {
385
- const hashtags = this.extractHashtags(post.content);
386
- hashtags.forEach((tag) => {
387
- if (!params.hashtags?.includes(tag)) {
388
- hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;
491
+ /**
492
+ * Calculate engagement boost for posts with questions
493
+ */
494
+ private getQuestionEngagementBoost(posts: SocialPost[]): number {
495
+ const withQuestions = posts.filter(p => p.content.includes('?'));
496
+ const withoutQuestions = posts.filter(p => !p.content.includes('?'));
497
+
498
+ if (withQuestions.length === 0 || withoutQuestions.length === 0) {
499
+ return 0;
389
500
  }
390
- });
391
- });
392
-
393
- suggestions.relatedHashtags = Object.entries(hashtagCounts)
394
- .sort(([, a], [, b]) => b - a)
395
- .slice(0, 10)
396
- .map(([tag]) => tag);
397
-
398
- // Optimal posting times (based on engagement)
399
- const timeEngagement: Record<string, { total: number; count: number }> = {};
400
- posts.forEach((post) => {
401
- const hour = post.publishedAt.getHours();
402
- const day = post.publishedAt.toLocaleDateString('en-US', { weekday: 'long' });
403
- const key = `${day} ${hour}:00`;
404
-
405
- if (!timeEngagement[key]) {
406
- timeEngagement[key] = { total: 0, count: 0 };
407
- }
408
-
409
- const engagement = (post.analytics?.likes || 0) + (post.analytics?.comments || 0);
410
- timeEngagement[key].total += engagement;
411
- timeEngagement[key].count++;
412
- });
413
-
414
- suggestions.optimalPostingTimes = Object.entries(timeEngagement)
415
- .map(([time, data]) => ({
416
- time,
417
- avgEngagement: Math.round(data.total / data.count),
418
- }))
419
- .sort((a, b) => b.avgEngagement - a.avgEngagement)
420
- .slice(0, 5);
421
-
422
- // Performance insights
423
- const avgEngagement =
424
- posts.reduce((sum, post) => sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0) / posts.length;
425
-
426
- suggestions.performanceInsights.push(
427
- `Average engagement per post: ${Math.round(avgEngagement)}`,
428
- `Most successful media type: ${this.getMostSuccessfulMediaType(posts)}`,
429
- `Posts with questions get ${this.getQuestionEngagementBoost(posts)}% more engagement`
430
- );
431
-
432
- return suggestions;
433
- }
434
-
435
- /**
436
- * Get most successful media type
437
- */
438
- private getMostSuccessfulMediaType(posts: SocialPost[]): string {
439
- const typeEngagement: Record<string, { total: number; count: number }> = {};
440
-
441
- posts.forEach((post) => {
442
- const type = post.platformSpecificData.mediaType;
443
- if (!typeEngagement[type]) {
444
- typeEngagement[type] = { total: 0, count: 0 };
445
- }
446
-
447
- const engagement = (post.analytics?.likes || 0) + (post.analytics?.comments || 0);
448
- typeEngagement[type].total += engagement;
449
- typeEngagement[type].count++;
450
- });
451
-
452
- let bestType = 'IMAGE';
453
- let bestAvg = 0;
454
-
455
- Object.entries(typeEngagement).forEach(([type, data]) => {
456
- const avg = data.total / data.count;
457
- if (avg > bestAvg) {
458
- bestAvg = avg;
459
- bestType = type;
460
- }
461
- });
462
-
463
- return bestType;
464
- }
465
-
466
- /**
467
- * Calculate engagement boost for posts with questions
468
- */
469
- private getQuestionEngagementBoost(posts: SocialPost[]): number {
470
- const withQuestions = posts.filter((p) => p.content.includes('?'));
471
- const withoutQuestions = posts.filter((p) => !p.content.includes('?'));
472
-
473
- if (withQuestions.length === 0 || withoutQuestions.length === 0) {
474
- return 0;
501
+
502
+ const avgWithQuestions = withQuestions.reduce((sum, post) =>
503
+ sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0
504
+ ) / withQuestions.length;
505
+
506
+ const avgWithoutQuestions = withoutQuestions.reduce((sum, post) =>
507
+ sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0
508
+ ) / withoutQuestions.length;
509
+
510
+ if (avgWithoutQuestions === 0) return 0;
511
+
512
+ return Math.round(((avgWithQuestions - avgWithoutQuestions) / avgWithoutQuestions) * 100);
475
513
  }
476
514
 
477
- const avgWithQuestions =
478
- withQuestions.reduce((sum, post) => sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0) / withQuestions.length;
479
-
480
- const avgWithoutQuestions =
481
- withoutQuestions.reduce((sum, post) => sum + (post.analytics?.likes || 0) + (post.analytics?.comments || 0), 0) /
482
- withoutQuestions.length;
483
-
484
- if (avgWithoutQuestions === 0) return 0;
485
-
486
- return Math.round(((avgWithQuestions - avgWithoutQuestions) / avgWithoutQuestions) * 100);
487
- }
488
-
489
- /**
490
- * Define the parameters for this action
491
- */
492
- public get Params(): ActionParam[] {
493
- return [
494
- ...this.commonSocialParams,
495
- {
496
- Name: 'Query',
497
- Type: 'Input',
498
- Value: null,
499
- },
500
- {
501
- Name: 'Hashtags',
502
- Type: 'Input',
503
- Value: null,
504
- },
505
- {
506
- Name: 'StartDate',
507
- Type: 'Input',
508
- Value: null,
509
- },
510
- {
511
- Name: 'EndDate',
512
- Type: 'Input',
513
- Value: null,
514
- },
515
- {
516
- Name: 'MediaType',
517
- Type: 'Input',
518
- Value: null,
519
- },
520
- {
521
- Name: 'MinEngagement',
522
- Type: 'Input',
523
- Value: 0,
524
- },
525
- {
526
- Name: 'Limit',
527
- Type: 'Input',
528
- Value: 100,
529
- },
530
- {
531
- Name: 'IncludeArchived',
532
- Type: 'Input',
533
- Value: false,
534
- },
535
- ];
536
- }
537
-
538
- /**
539
- * Get the description for this action
540
- */
541
- public get Description(): string {
542
- return 'Searches historical Instagram posts from your business account with filters for date range, hashtags, content, and engagement metrics.';
543
- }
544
- }
515
+ /**
516
+ * Define the parameters for this action
517
+ */
518
+ public get Params(): ActionParam[] {
519
+ return [
520
+ ...this.commonSocialParams,
521
+ {
522
+ Name: 'Query',
523
+ Type: 'Input',
524
+ Value: null
525
+ },
526
+ {
527
+ Name: 'Hashtags',
528
+ Type: 'Input',
529
+ Value: null
530
+ },
531
+ {
532
+ Name: 'StartDate',
533
+ Type: 'Input',
534
+ Value: null
535
+ },
536
+ {
537
+ Name: 'EndDate',
538
+ Type: 'Input',
539
+ Value: null
540
+ },
541
+ {
542
+ Name: 'MediaType',
543
+ Type: 'Input',
544
+ Value: null
545
+ },
546
+ {
547
+ Name: 'MinEngagement',
548
+ Type: 'Input',
549
+ Value: 0
550
+ },
551
+ {
552
+ Name: 'Limit',
553
+ Type: 'Input',
554
+ Value: 100
555
+ },
556
+ {
557
+ Name: 'IncludeArchived',
558
+ Type: 'Input',
559
+ Value: false
560
+ }
561
+ ];
562
+ }
563
+
564
+ /**
565
+ * Get the description for this action
566
+ */
567
+ public get Description(): string {
568
+ return 'Searches historical Instagram posts from your business account with filters for date range, hashtags, content, and engagement metrics.';
569
+ }
570
+ }