@memberjunction/actions-bizapps-social 2.111.1 → 2.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -6
- package/dist/base/base-social.action.d.ts.map +1 -1
- package/dist/base/base-social.action.js +18 -24
- package/dist/base/base-social.action.js.map +1 -1
- package/dist/providers/buffer/buffer-base.action.d.ts.map +1 -1
- package/dist/providers/buffer/buffer-base.action.js +35 -34
- package/dist/providers/buffer/buffer-base.action.js.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.js +33 -33
- package/dist/providers/facebook/actions/boost-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.js +34 -36
- package/dist/providers/facebook/actions/create-album.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.js +20 -20
- package/dist/providers/facebook/actions/create-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.js +25 -27
- package/dist/providers/facebook/actions/get-page-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.js +19 -23
- package/dist/providers/facebook/actions/get-page-posts.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.js +28 -32
- package/dist/providers/facebook/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.js +42 -44
- package/dist/providers/facebook/actions/respond-to-comments.action.js.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.js +29 -29
- package/dist/providers/facebook/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.js +37 -39
- package/dist/providers/facebook/actions/search-posts.action.js.map +1 -1
- package/dist/providers/facebook/facebook-base.action.d.ts.map +1 -1
- package/dist/providers/facebook/facebook-base.action.js +44 -59
- package/dist/providers/facebook/facebook-base.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js +33 -31
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js +28 -32
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js +19 -19
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.js +24 -26
- package/dist/providers/hootsuite/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js +22 -22
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js +32 -34
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.js +43 -52
- package/dist/providers/hootsuite/actions/search-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js +30 -28
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.js +18 -20
- package/dist/providers/hootsuite/hootsuite-base.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.js +27 -26
- package/dist/providers/instagram/actions/create-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.js +35 -35
- package/dist/providers/instagram/actions/create-story.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.js +38 -59
- package/dist/providers/instagram/actions/get-account-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.js +29 -29
- package/dist/providers/instagram/actions/get-business-posts.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.js +36 -36
- package/dist/providers/instagram/actions/get-comments.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.js +23 -25
- package/dist/providers/instagram/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.js +25 -25
- package/dist/providers/instagram/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.js +56 -60
- package/dist/providers/instagram/actions/search-posts.action.js.map +1 -1
- package/dist/providers/instagram/instagram-base.action.d.ts.map +1 -1
- package/dist/providers/instagram/instagram-base.action.js +25 -27
- package/dist/providers/instagram/instagram-base.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.js +55 -45
- package/dist/providers/linkedin/actions/create-article.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.js +31 -29
- package/dist/providers/linkedin/actions/create-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.js +28 -28
- package/dist/providers/linkedin/actions/get-followers.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.js +20 -20
- package/dist/providers/linkedin/actions/get-organization-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.js +19 -19
- package/dist/providers/linkedin/actions/get-personal-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.js +25 -23
- package/dist/providers/linkedin/actions/get-post-analytics.action.js.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.js +32 -30
- package/dist/providers/linkedin/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.js +28 -30
- package/dist/providers/linkedin/actions/search-posts.action.js.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.d.ts.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.js +33 -38
- package/dist/providers/linkedin/linkedin-base.action.js.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.d.ts.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.js +25 -26
- package/dist/providers/tiktok/tiktok-base.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.js +25 -29
- package/dist/providers/twitter/actions/create-thread.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.js +23 -23
- package/dist/providers/twitter/actions/create-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.js +19 -19
- package/dist/providers/twitter/actions/delete-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.js +40 -47
- package/dist/providers/twitter/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.js +30 -31
- package/dist/providers/twitter/actions/get-mentions.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.js +29 -29
- package/dist/providers/twitter/actions/get-timeline.action.js.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.js +26 -26
- package/dist/providers/twitter/actions/schedule-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.js +56 -58
- package/dist/providers/twitter/actions/search-tweets.action.js.map +1 -1
- package/dist/providers/twitter/twitter-base.action.d.ts.map +1 -1
- package/dist/providers/twitter/twitter-base.action.js +58 -68
- package/dist/providers/twitter/twitter-base.action.js.map +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts.map +1 -1
- package/dist/providers/youtube/youtube-base.action.js +22 -25
- package/dist/providers/youtube/youtube-base.action.js.map +1 -1
- package/package.json +5 -6
- package/src/base/base-social.action.ts +217 -224
- package/src/providers/buffer/buffer-base.action.ts +435 -441
- package/src/providers/facebook/actions/boost-post.action.ts +350 -386
- package/src/providers/facebook/actions/create-album.action.ts +291 -307
- package/src/providers/facebook/actions/create-post.action.ts +224 -227
- package/src/providers/facebook/actions/get-page-insights.action.ts +383 -403
- package/src/providers/facebook/actions/get-page-posts.action.ts +214 -225
- package/src/providers/facebook/actions/get-post-insights.action.ts +300 -316
- package/src/providers/facebook/actions/respond-to-comments.action.ts +319 -336
- package/src/providers/facebook/actions/schedule-post.action.ts +289 -292
- package/src/providers/facebook/actions/search-posts.action.ts +399 -413
- package/src/providers/facebook/facebook-base.action.ts +653 -670
- package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
- package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +184 -189
- package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +160 -161
- package/src/providers/hootsuite/actions/get-analytics.action.ts +249 -254
- package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +206 -207
- package/src/providers/hootsuite/actions/get-social-profiles.action.ts +206 -205
- package/src/providers/hootsuite/actions/search-posts.action.ts +351 -369
- package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +211 -209
- package/src/providers/hootsuite/hootsuite-base.action.ts +301 -307
- package/src/providers/instagram/actions/create-post.action.ts +276 -296
- package/src/providers/instagram/actions/create-story.action.ts +378 -394
- package/src/providers/instagram/actions/get-account-insights.action.ts +384 -420
- package/src/providers/instagram/actions/get-business-posts.action.ts +233 -242
- package/src/providers/instagram/actions/get-comments.action.ts +365 -377
- package/src/providers/instagram/actions/get-post-insights.action.ts +265 -273
- package/src/providers/instagram/actions/schedule-post.action.ts +233 -235
- package/src/providers/instagram/actions/search-posts.action.ts +512 -538
- package/src/providers/instagram/instagram-base.action.ts +368 -393
- package/src/providers/linkedin/actions/create-article.action.ts +275 -266
- package/src/providers/linkedin/actions/create-post.action.ts +179 -177
- package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
- package/src/providers/linkedin/actions/get-organization-posts.action.ts +146 -147
- package/src/providers/linkedin/actions/get-personal-posts.action.ts +138 -139
- package/src/providers/linkedin/actions/get-post-analytics.action.ts +190 -189
- package/src/providers/linkedin/actions/schedule-post.action.ts +191 -189
- package/src/providers/linkedin/actions/search-posts.action.ts +275 -283
- package/src/providers/linkedin/linkedin-base.action.ts +407 -421
- package/src/providers/tiktok/tiktok-base.action.ts +305 -320
- package/src/providers/twitter/actions/create-thread.action.ts +203 -207
- package/src/providers/twitter/actions/create-tweet.action.ts +187 -188
- package/src/providers/twitter/actions/delete-tweet.action.ts +128 -129
- package/src/providers/twitter/actions/get-analytics.action.ts +402 -411
- package/src/providers/twitter/actions/get-mentions.action.ts +218 -219
- package/src/providers/twitter/actions/get-timeline.action.ts +232 -233
- package/src/providers/twitter/actions/schedule-tweet.action.ts +221 -222
- package/src/providers/twitter/actions/search-tweets.action.ts +540 -543
- package/src/providers/twitter/twitter-base.action.ts +541 -560
- package/src/providers/youtube/youtube-base.action.ts +320 -333
|
@@ -2,7 +2,7 @@ import { RegisterClass } from '@memberjunction/global';
|
|
|
2
2
|
import { FacebookBaseAction, FacebookPost } from '../facebook-base.action';
|
|
3
3
|
import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
|
|
4
4
|
import { SocialPost, SearchParams, SocialMediaErrorCode } from '../../../base/base-social.action';
|
|
5
|
-
import { LogStatus, LogError } from '@memberjunction/
|
|
5
|
+
import { LogStatus, LogError } from '@memberjunction/global';
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
import { BaseAction } from '@memberjunction/actions';
|
|
8
8
|
|
|
@@ -12,439 +12,425 @@ import { BaseAction } from '@memberjunction/actions';
|
|
|
12
12
|
*/
|
|
13
13
|
@RegisterClass(BaseAction, 'FacebookSearchPostsAction')
|
|
14
14
|
export class FacebookSearchPostsAction extends FacebookBaseAction {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Get action description
|
|
17
|
+
*/
|
|
18
|
+
public get Description(): string {
|
|
19
|
+
return 'Searches for historical posts on Facebook pages with filters for date ranges, keywords, hashtags, and content types';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Define the parameters for this action
|
|
24
|
+
*/
|
|
25
|
+
public get Params(): ActionParam[] {
|
|
26
|
+
return [
|
|
27
|
+
...this.commonSocialParams,
|
|
28
|
+
{
|
|
29
|
+
Name: 'PageIDs',
|
|
30
|
+
Type: 'Input',
|
|
31
|
+
Value: null,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
Name: 'Query',
|
|
35
|
+
Type: 'Input',
|
|
36
|
+
Value: null,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
Name: 'Hashtags',
|
|
40
|
+
Type: 'Input',
|
|
41
|
+
Value: null,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
Name: 'StartDate',
|
|
45
|
+
Type: 'Input',
|
|
46
|
+
Value: null,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
Name: 'EndDate',
|
|
50
|
+
Type: 'Input',
|
|
51
|
+
Value: null,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
Name: 'PostTypes',
|
|
55
|
+
Type: 'Input',
|
|
56
|
+
Value: null,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
Name: 'MinEngagements',
|
|
60
|
+
Type: 'Input',
|
|
61
|
+
Value: null,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
Name: 'IncludeMetrics',
|
|
65
|
+
Type: 'Input',
|
|
66
|
+
Value: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
Name: 'Limit',
|
|
70
|
+
Type: 'Input',
|
|
71
|
+
Value: 100,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
Name: 'SortBy',
|
|
75
|
+
Type: 'Input',
|
|
76
|
+
Value: 'created_time',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
Name: 'SortOrder',
|
|
80
|
+
Type: 'Input',
|
|
81
|
+
Value: 'DESC',
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Execute the action
|
|
88
|
+
*/
|
|
89
|
+
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
|
|
90
|
+
const { Params, ContextUser } = params;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Validate required parameters
|
|
94
|
+
const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
|
|
95
|
+
|
|
96
|
+
if (!companyIntegrationId) {
|
|
97
|
+
return {
|
|
98
|
+
Success: false,
|
|
99
|
+
Message: 'CompanyIntegrationID is required',
|
|
100
|
+
ResultCode: 'INVALID_TOKEN',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
// Initialize OAuth
|
|
105
|
+
if (!(await this.initializeOAuth(companyIntegrationId))) {
|
|
106
|
+
return {
|
|
107
|
+
Success: false,
|
|
108
|
+
Message: 'Failed to initialize Facebook OAuth connection',
|
|
109
|
+
ResultCode: 'INVALID_TOKEN',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get parameters
|
|
114
|
+
const pageIds = this.getParamValue(Params, 'PageIDs') as string[];
|
|
115
|
+
const query = this.getParamValue(Params, 'Query') as string;
|
|
116
|
+
const hashtags = this.getParamValue(Params, 'Hashtags') as string[];
|
|
117
|
+
const startDate = this.getParamValue(Params, 'StartDate') as string;
|
|
118
|
+
const endDate = this.getParamValue(Params, 'EndDate') as string;
|
|
119
|
+
const postTypes = this.getParamValue(Params, 'PostTypes') as string[];
|
|
120
|
+
const minEngagements = this.getParamValue(Params, 'MinEngagements') as number;
|
|
121
|
+
const includeMetrics = this.getParamValue(Params, 'IncludeMetrics') !== false;
|
|
122
|
+
const limit = (this.getParamValue(Params, 'Limit') as number) || 100;
|
|
123
|
+
const sortBy = (this.getParamValue(Params, 'SortBy') as string) || 'created_time';
|
|
124
|
+
const sortOrder = (this.getParamValue(Params, 'SortOrder') as string) || 'DESC';
|
|
125
|
+
|
|
126
|
+
// Build search parameters
|
|
127
|
+
const searchParams: SearchParams = {
|
|
128
|
+
query,
|
|
129
|
+
hashtags,
|
|
130
|
+
startDate: startDate ? new Date(startDate) : undefined,
|
|
131
|
+
endDate: endDate ? new Date(endDate) : undefined,
|
|
132
|
+
limit,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
LogStatus('Starting Facebook post search...');
|
|
136
|
+
|
|
137
|
+
// Get pages to search
|
|
138
|
+
let pagesToSearch = pageIds;
|
|
139
|
+
if (!pagesToSearch || pagesToSearch.length === 0) {
|
|
140
|
+
// Get all accessible pages
|
|
141
|
+
const userPages = await this.getUserPages();
|
|
142
|
+
pagesToSearch = userPages.map((p) => p.id);
|
|
143
|
+
LogStatus(`Searching across ${pagesToSearch.length} accessible pages`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Search posts from each page
|
|
147
|
+
const allPosts: FacebookPost[] = [];
|
|
148
|
+
for (const pageId of pagesToSearch) {
|
|
92
149
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!companyIntegrationId) {
|
|
97
|
-
return {
|
|
98
|
-
Success: false,
|
|
99
|
-
Message: 'CompanyIntegrationID is required',
|
|
100
|
-
ResultCode: 'INVALID_TOKEN'
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Initialize OAuth
|
|
105
|
-
if (!await this.initializeOAuth(companyIntegrationId)) {
|
|
106
|
-
return {
|
|
107
|
-
Success: false,
|
|
108
|
-
Message: 'Failed to initialize Facebook OAuth connection',
|
|
109
|
-
ResultCode: 'INVALID_TOKEN'
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Get parameters
|
|
114
|
-
const pageIds = this.getParamValue(Params, 'PageIDs') as string[];
|
|
115
|
-
const query = this.getParamValue(Params, 'Query') as string;
|
|
116
|
-
const hashtags = this.getParamValue(Params, 'Hashtags') as string[];
|
|
117
|
-
const startDate = this.getParamValue(Params, 'StartDate') as string;
|
|
118
|
-
const endDate = this.getParamValue(Params, 'EndDate') as string;
|
|
119
|
-
const postTypes = this.getParamValue(Params, 'PostTypes') as string[];
|
|
120
|
-
const minEngagements = this.getParamValue(Params, 'MinEngagements') as number;
|
|
121
|
-
const includeMetrics = this.getParamValue(Params, 'IncludeMetrics') !== false;
|
|
122
|
-
const limit = this.getParamValue(Params, 'Limit') as number || 100;
|
|
123
|
-
const sortBy = this.getParamValue(Params, 'SortBy') as string || 'created_time';
|
|
124
|
-
const sortOrder = this.getParamValue(Params, 'SortOrder') as string || 'DESC';
|
|
125
|
-
|
|
126
|
-
// Build search parameters
|
|
127
|
-
const searchParams: SearchParams = {
|
|
128
|
-
query,
|
|
129
|
-
hashtags,
|
|
130
|
-
startDate: startDate ? new Date(startDate) : undefined,
|
|
131
|
-
endDate: endDate ? new Date(endDate) : undefined,
|
|
132
|
-
limit
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
LogStatus('Starting Facebook post search...');
|
|
136
|
-
|
|
137
|
-
// Get pages to search
|
|
138
|
-
let pagesToSearch = pageIds;
|
|
139
|
-
if (!pagesToSearch || pagesToSearch.length === 0) {
|
|
140
|
-
// Get all accessible pages
|
|
141
|
-
const userPages = await this.getUserPages();
|
|
142
|
-
pagesToSearch = userPages.map(p => p.id);
|
|
143
|
-
LogStatus(`Searching across ${pagesToSearch.length} accessible pages`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Search posts from each page
|
|
147
|
-
const allPosts: FacebookPost[] = [];
|
|
148
|
-
for (const pageId of pagesToSearch) {
|
|
149
|
-
try {
|
|
150
|
-
const posts = await this.searchPagePosts(
|
|
151
|
-
pageId,
|
|
152
|
-
searchParams,
|
|
153
|
-
postTypes,
|
|
154
|
-
includeMetrics
|
|
155
|
-
);
|
|
156
|
-
allPosts.push(...posts);
|
|
157
|
-
LogStatus(`Found ${posts.length} posts from page ${pageId}`);
|
|
158
|
-
} catch (error) {
|
|
159
|
-
LogError(`Failed to search page ${pageId}: ${error}`);
|
|
160
|
-
// Continue with other pages
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Filter by minimum engagements if specified
|
|
165
|
-
let filteredPosts = allPosts;
|
|
166
|
-
if (minEngagements && minEngagements > 0) {
|
|
167
|
-
filteredPosts = allPosts.filter(post => {
|
|
168
|
-
const engagements = this.calculateEngagements(post);
|
|
169
|
-
return engagements >= minEngagements;
|
|
170
|
-
});
|
|
171
|
-
LogStatus(`Filtered to ${filteredPosts.length} posts with at least ${minEngagements} engagements`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Sort posts
|
|
175
|
-
const sortedPosts = this.sortPosts(filteredPosts, sortBy, sortOrder);
|
|
176
|
-
|
|
177
|
-
// Limit results
|
|
178
|
-
const limitedPosts = sortedPosts.slice(0, limit);
|
|
179
|
-
|
|
180
|
-
// Normalize posts to common format
|
|
181
|
-
const normalizedPosts = limitedPosts.map(post => this.normalizePost(post));
|
|
182
|
-
|
|
183
|
-
// Calculate search summary
|
|
184
|
-
const summary = this.calculateSearchSummary(normalizedPosts, searchParams);
|
|
185
|
-
|
|
186
|
-
LogStatus(`Search completed. Found ${normalizedPosts.length} matching posts`);
|
|
187
|
-
|
|
188
|
-
// Update output parameters
|
|
189
|
-
const outputParams = [...Params];
|
|
190
|
-
// TODO: Set output parameters based on result
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
Success: true,
|
|
194
|
-
Message: `Found ${normalizedPosts.length} posts matching search criteria`,
|
|
195
|
-
ResultCode: 'SUCCESS',
|
|
196
|
-
Params: outputParams
|
|
197
|
-
};
|
|
198
|
-
|
|
150
|
+
const posts = await this.searchPagePosts(pageId, searchParams, postTypes, includeMetrics);
|
|
151
|
+
allPosts.push(...posts);
|
|
152
|
+
LogStatus(`Found ${posts.length} posts from page ${pageId}`);
|
|
199
153
|
} catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (this.isAuthError(error)) {
|
|
203
|
-
return this.handleOAuthError(error);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
Success: false,
|
|
208
|
-
Message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
209
|
-
ResultCode: 'ERROR'
|
|
210
|
-
};
|
|
154
|
+
LogError(`Failed to search page ${pageId}: ${error}`);
|
|
155
|
+
// Continue with other pages
|
|
211
156
|
}
|
|
212
|
-
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Filter by minimum engagements if specified
|
|
160
|
+
let filteredPosts = allPosts;
|
|
161
|
+
if (minEngagements && minEngagements > 0) {
|
|
162
|
+
filteredPosts = allPosts.filter((post) => {
|
|
163
|
+
const engagements = this.calculateEngagements(post);
|
|
164
|
+
return engagements >= minEngagements;
|
|
165
|
+
});
|
|
166
|
+
LogStatus(`Filtered to ${filteredPosts.length} posts with at least ${minEngagements} engagements`);
|
|
167
|
+
}
|
|
213
168
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*/
|
|
217
|
-
private async searchPagePosts(
|
|
218
|
-
pageId: string,
|
|
219
|
-
searchParams: SearchParams,
|
|
220
|
-
postTypes: string[] | null,
|
|
221
|
-
includeMetrics: boolean
|
|
222
|
-
): Promise<FacebookPost[]> {
|
|
223
|
-
const pageToken = await this.getPageAccessToken(pageId);
|
|
224
|
-
|
|
225
|
-
// Build fields parameter
|
|
226
|
-
const fields = includeMetrics
|
|
227
|
-
? this.getPostFields()
|
|
228
|
-
: 'id,message,created_time,updated_time,from,story,permalink_url,attachments';
|
|
229
|
-
|
|
230
|
-
// Build API parameters
|
|
231
|
-
const apiParams: any = {
|
|
232
|
-
access_token: pageToken,
|
|
233
|
-
fields,
|
|
234
|
-
limit: 100 // Get max per request, we'll filter later
|
|
235
|
-
};
|
|
169
|
+
// Sort posts
|
|
170
|
+
const sortedPosts = this.sortPosts(filteredPosts, sortBy, sortOrder);
|
|
236
171
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
apiParams.since = Math.floor(searchParams.startDate.getTime() / 1000);
|
|
240
|
-
}
|
|
241
|
-
if (searchParams.endDate) {
|
|
242
|
-
apiParams.until = Math.floor(searchParams.endDate.getTime() / 1000);
|
|
243
|
-
}
|
|
172
|
+
// Limit results
|
|
173
|
+
const limitedPosts = sortedPosts.slice(0, limit);
|
|
244
174
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
`${this.apiBaseUrl}/${pageId}/posts`,
|
|
248
|
-
apiParams,
|
|
249
|
-
searchParams.limit ? searchParams.limit * 2 : undefined // Get extra to account for filtering
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// Filter posts based on search criteria
|
|
253
|
-
let filteredPosts = allPosts;
|
|
254
|
-
|
|
255
|
-
// Filter by content/query
|
|
256
|
-
if (searchParams.query) {
|
|
257
|
-
const queryLower = searchParams.query.toLowerCase();
|
|
258
|
-
filteredPosts = filteredPosts.filter(post => {
|
|
259
|
-
const message = (post.message || '').toLowerCase();
|
|
260
|
-
const story = (post.story || '').toLowerCase();
|
|
261
|
-
return message.includes(queryLower) || story.includes(queryLower);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
175
|
+
// Normalize posts to common format
|
|
176
|
+
const normalizedPosts = limitedPosts.map((post) => this.normalizePost(post));
|
|
264
177
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const hashtagsToFind = searchParams.hashtags.map(tag =>
|
|
268
|
-
tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase()
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
filteredPosts = filteredPosts.filter(post => {
|
|
272
|
-
const content = (post.message || '').toLowerCase();
|
|
273
|
-
return hashtagsToFind.some(hashtag => content.includes(hashtag));
|
|
274
|
-
});
|
|
275
|
-
}
|
|
178
|
+
// Calculate search summary
|
|
179
|
+
const summary = this.calculateSearchSummary(normalizedPosts, searchParams);
|
|
276
180
|
|
|
277
|
-
|
|
278
|
-
if (postTypes && postTypes.length > 0) {
|
|
279
|
-
filteredPosts = filteredPosts.filter(post => {
|
|
280
|
-
const postType = this.getPostType(post);
|
|
281
|
-
return postTypes.includes(postType);
|
|
282
|
-
});
|
|
283
|
-
}
|
|
181
|
+
LogStatus(`Search completed. Found ${normalizedPosts.length} matching posts`);
|
|
284
182
|
|
|
285
|
-
|
|
286
|
-
|
|
183
|
+
// Update output parameters
|
|
184
|
+
const outputParams = [...Params];
|
|
185
|
+
// TODO: Set output parameters based on result
|
|
287
186
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
187
|
+
return {
|
|
188
|
+
Success: true,
|
|
189
|
+
Message: `Found ${normalizedPosts.length} posts matching search criteria`,
|
|
190
|
+
ResultCode: 'SUCCESS',
|
|
191
|
+
Params: outputParams,
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
LogError(`Failed to search Facebook posts: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
296
195
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
private getPostType(post: FacebookPost): string {
|
|
301
|
-
if (post.attachments?.data?.[0]?.type) {
|
|
302
|
-
return post.attachments.data[0].type.toLowerCase();
|
|
303
|
-
}
|
|
304
|
-
return 'status';
|
|
305
|
-
}
|
|
196
|
+
if (this.isAuthError(error)) {
|
|
197
|
+
return this.handleOAuthError(error);
|
|
198
|
+
}
|
|
306
199
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const comments = post.comments?.summary?.total_count || 0;
|
|
313
|
-
const shares = post.shares?.count || 0;
|
|
314
|
-
|
|
315
|
-
return reactions + comments + shares;
|
|
200
|
+
return {
|
|
201
|
+
Success: false,
|
|
202
|
+
Message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
203
|
+
ResultCode: 'ERROR',
|
|
204
|
+
};
|
|
316
205
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
return sorted;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Search posts from a specific page
|
|
210
|
+
*/
|
|
211
|
+
private async searchPagePosts(
|
|
212
|
+
pageId: string,
|
|
213
|
+
searchParams: SearchParams,
|
|
214
|
+
postTypes: string[] | null,
|
|
215
|
+
includeMetrics: boolean
|
|
216
|
+
): Promise<FacebookPost[]> {
|
|
217
|
+
const pageToken = await this.getPageAccessToken(pageId);
|
|
218
|
+
|
|
219
|
+
// Build fields parameter
|
|
220
|
+
const fields = includeMetrics ? this.getPostFields() : 'id,message,created_time,updated_time,from,story,permalink_url,attachments';
|
|
221
|
+
|
|
222
|
+
// Build API parameters
|
|
223
|
+
const apiParams: any = {
|
|
224
|
+
access_token: pageToken,
|
|
225
|
+
fields,
|
|
226
|
+
limit: 100, // Get max per request, we'll filter later
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Add date range
|
|
230
|
+
if (searchParams.startDate) {
|
|
231
|
+
apiParams.since = Math.floor(searchParams.startDate.getTime() / 1000);
|
|
346
232
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
* Get post reach from insights
|
|
350
|
-
*/
|
|
351
|
-
private getPostReach(post: FacebookPost): number {
|
|
352
|
-
if (post.insights?.data) {
|
|
353
|
-
const reachInsight = post.insights.data.find(i =>
|
|
354
|
-
i.name === 'post_impressions_unique' || i.name === 'post_reach'
|
|
355
|
-
);
|
|
356
|
-
return reachInsight?.values?.[0]?.value || 0;
|
|
357
|
-
}
|
|
358
|
-
return 0;
|
|
233
|
+
if (searchParams.endDate) {
|
|
234
|
+
apiParams.until = Math.floor(searchParams.endDate.getTime() / 1000);
|
|
359
235
|
}
|
|
360
236
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
latest: new Date(Math.max(...dates))
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
// Extract hashtags
|
|
383
|
-
const hashtagCounts: Record<string, number> = {};
|
|
384
|
-
posts.forEach(post => {
|
|
385
|
-
const hashtags = post.content.match(/#\w+/g) || [];
|
|
386
|
-
hashtags.forEach(tag => {
|
|
387
|
-
hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Top hashtags
|
|
392
|
-
const topHashtags = Object.entries(hashtagCounts)
|
|
393
|
-
.sort(([, a], [, b]) => b - a)
|
|
394
|
-
.slice(0, 10)
|
|
395
|
-
.map(([tag, count]) => ({ tag, count }));
|
|
396
|
-
|
|
397
|
-
// Post types
|
|
398
|
-
const postTypes: Record<string, number> = {};
|
|
399
|
-
posts.forEach(post => {
|
|
400
|
-
const type = post.platformSpecificData.postType || 'status';
|
|
401
|
-
postTypes[type] = (postTypes[type] || 0) + 1;
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Engagement statistics
|
|
405
|
-
const engagements = posts.map(p => p.analytics?.engagements || 0);
|
|
406
|
-
const totalEngagements = engagements.reduce((sum, e) => sum + e, 0);
|
|
407
|
-
const avgEngagements = totalEngagements / posts.length;
|
|
408
|
-
const maxEngagements = Math.max(...engagements);
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
totalPosts: posts.length,
|
|
412
|
-
dateRange,
|
|
413
|
-
topHashtags,
|
|
414
|
-
postTypes,
|
|
415
|
-
engagementStats: {
|
|
416
|
-
total: totalEngagements,
|
|
417
|
-
average: Math.round(avgEngagements),
|
|
418
|
-
max: maxEngagements,
|
|
419
|
-
distribution: this.getEngagementDistribution(engagements)
|
|
420
|
-
}
|
|
421
|
-
};
|
|
237
|
+
// Get all posts in date range
|
|
238
|
+
const allPosts = await this.getPaginatedResults<FacebookPost>(
|
|
239
|
+
`${this.apiBaseUrl}/${pageId}/posts`,
|
|
240
|
+
apiParams,
|
|
241
|
+
searchParams.limit ? searchParams.limit * 2 : undefined // Get extra to account for filtering
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Filter posts based on search criteria
|
|
245
|
+
let filteredPosts = allPosts;
|
|
246
|
+
|
|
247
|
+
// Filter by content/query
|
|
248
|
+
if (searchParams.query) {
|
|
249
|
+
const queryLower = searchParams.query.toLowerCase();
|
|
250
|
+
filteredPosts = filteredPosts.filter((post) => {
|
|
251
|
+
const message = (post.message || '').toLowerCase();
|
|
252
|
+
const story = (post.story || '').toLowerCase();
|
|
253
|
+
return message.includes(queryLower) || story.includes(queryLower);
|
|
254
|
+
});
|
|
422
255
|
}
|
|
423
256
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
private getEngagementDistribution(engagements: number[]): Record<string, number> {
|
|
428
|
-
const distribution = {
|
|
429
|
-
'0-10': 0,
|
|
430
|
-
'11-50': 0,
|
|
431
|
-
'51-100': 0,
|
|
432
|
-
'101-500': 0,
|
|
433
|
-
'501-1000': 0,
|
|
434
|
-
'1000+': 0
|
|
435
|
-
};
|
|
257
|
+
// Filter by hashtags
|
|
258
|
+
if (searchParams.hashtags && searchParams.hashtags.length > 0) {
|
|
259
|
+
const hashtagsToFind = searchParams.hashtags.map((tag) => (tag.startsWith('#') ? tag.toLowerCase() : `#${tag}`.toLowerCase()));
|
|
436
260
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
else if (e <= 1000) distribution['501-1000']++;
|
|
443
|
-
else distribution['1000+']++;
|
|
444
|
-
});
|
|
261
|
+
filteredPosts = filteredPosts.filter((post) => {
|
|
262
|
+
const content = (post.message || '').toLowerCase();
|
|
263
|
+
return hashtagsToFind.some((hashtag) => content.includes(hashtag));
|
|
264
|
+
});
|
|
265
|
+
}
|
|
445
266
|
|
|
446
|
-
|
|
267
|
+
// Filter by post types
|
|
268
|
+
if (postTypes && postTypes.length > 0) {
|
|
269
|
+
filteredPosts = filteredPosts.filter((post) => {
|
|
270
|
+
const postType = this.getPostType(post);
|
|
271
|
+
return postTypes.includes(postType);
|
|
272
|
+
});
|
|
447
273
|
}
|
|
448
274
|
|
|
275
|
+
return filteredPosts;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Implement the abstract searchPosts method
|
|
280
|
+
*/
|
|
281
|
+
protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
|
|
282
|
+
// This is called by the base class, but we implement our own search logic
|
|
283
|
+
// in RunAction, so we'll just throw an error here
|
|
284
|
+
throw new Error('Use FacebookSearchPostsAction.RunAction instead');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get post type from Facebook post
|
|
289
|
+
*/
|
|
290
|
+
private getPostType(post: FacebookPost): string {
|
|
291
|
+
if (post.attachments?.data?.[0]?.type) {
|
|
292
|
+
return post.attachments.data[0].type.toLowerCase();
|
|
293
|
+
}
|
|
294
|
+
return 'status';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Calculate total engagements for a post
|
|
299
|
+
*/
|
|
300
|
+
private calculateEngagements(post: FacebookPost): number {
|
|
301
|
+
const reactions = post.reactions?.summary?.total_count || 0;
|
|
302
|
+
const comments = post.comments?.summary?.total_count || 0;
|
|
303
|
+
const shares = post.shares?.count || 0;
|
|
304
|
+
|
|
305
|
+
return reactions + comments + shares;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Sort posts by specified criteria
|
|
310
|
+
*/
|
|
311
|
+
private sortPosts(posts: FacebookPost[], sortBy: string, sortOrder: string): FacebookPost[] {
|
|
312
|
+
const sorted = [...posts].sort((a, b) => {
|
|
313
|
+
let aValue: number;
|
|
314
|
+
let bValue: number;
|
|
315
|
+
|
|
316
|
+
switch (sortBy) {
|
|
317
|
+
case 'engagement':
|
|
318
|
+
aValue = this.calculateEngagements(a);
|
|
319
|
+
bValue = this.calculateEngagements(b);
|
|
320
|
+
break;
|
|
321
|
+
case 'reach':
|
|
322
|
+
aValue = this.getPostReach(a);
|
|
323
|
+
bValue = this.getPostReach(b);
|
|
324
|
+
break;
|
|
325
|
+
case 'created_time':
|
|
326
|
+
default:
|
|
327
|
+
aValue = new Date(a.created_time).getTime();
|
|
328
|
+
bValue = new Date(b.created_time).getTime();
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return sortOrder === 'DESC' ? bValue - aValue : aValue - bValue;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return sorted;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get post reach from insights
|
|
340
|
+
*/
|
|
341
|
+
private getPostReach(post: FacebookPost): number {
|
|
342
|
+
if (post.insights?.data) {
|
|
343
|
+
const reachInsight = post.insights.data.find((i) => i.name === 'post_impressions_unique' || i.name === 'post_reach');
|
|
344
|
+
return reachInsight?.values?.[0]?.value || 0;
|
|
345
|
+
}
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Calculate search summary statistics
|
|
351
|
+
*/
|
|
352
|
+
private calculateSearchSummary(posts: SocialPost[], searchParams: SearchParams): any {
|
|
353
|
+
if (posts.length === 0) {
|
|
354
|
+
return {
|
|
355
|
+
totalPosts: 0,
|
|
356
|
+
dateRange: null,
|
|
357
|
+
topHashtags: [],
|
|
358
|
+
postTypes: {},
|
|
359
|
+
engagementStats: null,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
449
362
|
|
|
450
|
-
|
|
363
|
+
// Date range
|
|
364
|
+
const dates = posts.map((p) => p.publishedAt.getTime());
|
|
365
|
+
const dateRange = {
|
|
366
|
+
earliest: new Date(Math.min(...dates)),
|
|
367
|
+
latest: new Date(Math.max(...dates)),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Extract hashtags
|
|
371
|
+
const hashtagCounts: Record<string, number> = {};
|
|
372
|
+
posts.forEach((post) => {
|
|
373
|
+
const hashtags = post.content.match(/#\w+/g) || [];
|
|
374
|
+
hashtags.forEach((tag) => {
|
|
375
|
+
hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Top hashtags
|
|
380
|
+
const topHashtags = Object.entries(hashtagCounts)
|
|
381
|
+
.sort(([, a], [, b]) => b - a)
|
|
382
|
+
.slice(0, 10)
|
|
383
|
+
.map(([tag, count]) => ({ tag, count }));
|
|
384
|
+
|
|
385
|
+
// Post types
|
|
386
|
+
const postTypes: Record<string, number> = {};
|
|
387
|
+
posts.forEach((post) => {
|
|
388
|
+
const type = post.platformSpecificData.postType || 'status';
|
|
389
|
+
postTypes[type] = (postTypes[type] || 0) + 1;
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Engagement statistics
|
|
393
|
+
const engagements = posts.map((p) => p.analytics?.engagements || 0);
|
|
394
|
+
const totalEngagements = engagements.reduce((sum, e) => sum + e, 0);
|
|
395
|
+
const avgEngagements = totalEngagements / posts.length;
|
|
396
|
+
const maxEngagements = Math.max(...engagements);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
totalPosts: posts.length,
|
|
400
|
+
dateRange,
|
|
401
|
+
topHashtags,
|
|
402
|
+
postTypes,
|
|
403
|
+
engagementStats: {
|
|
404
|
+
total: totalEngagements,
|
|
405
|
+
average: Math.round(avgEngagements),
|
|
406
|
+
max: maxEngagements,
|
|
407
|
+
distribution: this.getEngagementDistribution(engagements),
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get engagement distribution
|
|
414
|
+
*/
|
|
415
|
+
private getEngagementDistribution(engagements: number[]): Record<string, number> {
|
|
416
|
+
const distribution = {
|
|
417
|
+
'0-10': 0,
|
|
418
|
+
'11-50': 0,
|
|
419
|
+
'51-100': 0,
|
|
420
|
+
'101-500': 0,
|
|
421
|
+
'501-1000': 0,
|
|
422
|
+
'1000+': 0,
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
engagements.forEach((e) => {
|
|
426
|
+
if (e <= 10) distribution['0-10']++;
|
|
427
|
+
else if (e <= 50) distribution['11-50']++;
|
|
428
|
+
else if (e <= 100) distribution['51-100']++;
|
|
429
|
+
else if (e <= 500) distribution['101-500']++;
|
|
430
|
+
else if (e <= 1000) distribution['501-1000']++;
|
|
431
|
+
else distribution['1000+']++;
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return distribution;
|
|
435
|
+
}
|
|
436
|
+
}
|