@memberjunction/actions-bizapps-social 2.111.0 → 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.
Files changed (204) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +6 -6
  3. package/dist/base/base-social.action.d.ts.map +1 -1
  4. package/dist/base/base-social.action.js +18 -24
  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 +35 -34
  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 +34 -36
  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 +25 -27
  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 +19 -23
  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 +28 -32
  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 +42 -44
  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 +37 -39
  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 +44 -59
  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 +33 -31
  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 +28 -32
  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 +24 -26
  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 +32 -34
  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 +43 -52
  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 +30 -28
  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 +18 -20
  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 +27 -26
  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 +38 -59
  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 +23 -25
  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 +56 -60
  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 +25 -27
  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 +55 -45
  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 +31 -29
  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 +25 -23
  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 +32 -30
  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 +28 -30
  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 +33 -38
  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 +25 -26
  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 +25 -29
  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 +40 -47
  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 +30 -31
  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 +56 -58
  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 +58 -68
  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 +22 -25
  153. package/dist/providers/youtube/youtube-base.action.js.map +1 -1
  154. package/package.json +5 -6
  155. package/src/base/base-social.action.ts +217 -224
  156. package/src/providers/buffer/buffer-base.action.ts +435 -441
  157. package/src/providers/facebook/actions/boost-post.action.ts +350 -386
  158. package/src/providers/facebook/actions/create-album.action.ts +291 -307
  159. package/src/providers/facebook/actions/create-post.action.ts +224 -227
  160. package/src/providers/facebook/actions/get-page-insights.action.ts +383 -403
  161. package/src/providers/facebook/actions/get-page-posts.action.ts +214 -225
  162. package/src/providers/facebook/actions/get-post-insights.action.ts +300 -316
  163. package/src/providers/facebook/actions/respond-to-comments.action.ts +319 -336
  164. package/src/providers/facebook/actions/schedule-post.action.ts +289 -292
  165. package/src/providers/facebook/actions/search-posts.action.ts +399 -413
  166. package/src/providers/facebook/facebook-base.action.ts +653 -670
  167. package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
  168. package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +184 -189
  169. package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +160 -161
  170. package/src/providers/hootsuite/actions/get-analytics.action.ts +249 -254
  171. package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +206 -207
  172. package/src/providers/hootsuite/actions/get-social-profiles.action.ts +206 -205
  173. package/src/providers/hootsuite/actions/search-posts.action.ts +351 -369
  174. package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +211 -209
  175. package/src/providers/hootsuite/hootsuite-base.action.ts +301 -307
  176. package/src/providers/instagram/actions/create-post.action.ts +276 -296
  177. package/src/providers/instagram/actions/create-story.action.ts +378 -394
  178. package/src/providers/instagram/actions/get-account-insights.action.ts +384 -420
  179. package/src/providers/instagram/actions/get-business-posts.action.ts +233 -242
  180. package/src/providers/instagram/actions/get-comments.action.ts +365 -377
  181. package/src/providers/instagram/actions/get-post-insights.action.ts +265 -273
  182. package/src/providers/instagram/actions/schedule-post.action.ts +233 -235
  183. package/src/providers/instagram/actions/search-posts.action.ts +512 -538
  184. package/src/providers/instagram/instagram-base.action.ts +368 -393
  185. package/src/providers/linkedin/actions/create-article.action.ts +275 -266
  186. package/src/providers/linkedin/actions/create-post.action.ts +179 -177
  187. package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
  188. package/src/providers/linkedin/actions/get-organization-posts.action.ts +146 -147
  189. package/src/providers/linkedin/actions/get-personal-posts.action.ts +138 -139
  190. package/src/providers/linkedin/actions/get-post-analytics.action.ts +190 -189
  191. package/src/providers/linkedin/actions/schedule-post.action.ts +191 -189
  192. package/src/providers/linkedin/actions/search-posts.action.ts +275 -283
  193. package/src/providers/linkedin/linkedin-base.action.ts +407 -421
  194. package/src/providers/tiktok/tiktok-base.action.ts +305 -320
  195. package/src/providers/twitter/actions/create-thread.action.ts +203 -207
  196. package/src/providers/twitter/actions/create-tweet.action.ts +187 -188
  197. package/src/providers/twitter/actions/delete-tweet.action.ts +128 -129
  198. package/src/providers/twitter/actions/get-analytics.action.ts +402 -411
  199. package/src/providers/twitter/actions/get-mentions.action.ts +218 -219
  200. package/src/providers/twitter/actions/get-timeline.action.ts +232 -233
  201. package/src/providers/twitter/actions/schedule-tweet.action.ts +221 -222
  202. package/src/providers/twitter/actions/search-tweets.action.ts +540 -543
  203. package/src/providers/twitter/twitter-base.action.ts +541 -560
  204. package/src/providers/youtube/youtube-base.action.ts +320 -333
@@ -1,6 +1,6 @@
1
1
  import { RegisterClass } from '@memberjunction/global';
2
2
  import { BaseSocialMediaAction } from '../../base/base-social.action';
3
- import { UserInfo } from '@memberjunction/core';
3
+ import { UserInfo } from '@memberjunction/global';
4
4
  import axios, { AxiosInstance, AxiosError } from 'axios';
5
5
  import { SocialPost, MediaFile } from '../../base/base-social.action';
6
6
  import { BaseAction } from '@memberjunction/actions';
@@ -11,364 +11,351 @@ import { BaseAction } from '@memberjunction/actions';
11
11
  */
12
12
  @RegisterClass(BaseAction, 'YouTubeBaseAction')
13
13
  export abstract class YouTubeBaseAction extends BaseSocialMediaAction {
14
- protected override get platformName(): string {
15
- return 'YouTube';
14
+ protected override get platformName(): string {
15
+ return 'YouTube';
16
+ }
17
+
18
+ protected override get apiBaseUrl(): string {
19
+ return 'https://www.googleapis.com/youtube/v3';
20
+ }
21
+
22
+ /**
23
+ * Axios instance for API requests
24
+ */
25
+ protected axiosInstance: AxiosInstance;
26
+
27
+ /**
28
+ * Initialize the axios instance with base configuration
29
+ */
30
+ protected initializeAxios(): void {
31
+ this.axiosInstance = axios.create({
32
+ baseURL: this.apiBaseUrl,
33
+ headers: {
34
+ Accept: 'application/json',
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ });
38
+ }
39
+
40
+ /**
41
+ * YouTube-specific OAuth token refresh
42
+ */
43
+ protected async refreshAccessToken(): Promise<void> {
44
+ const refreshToken = this.getRefreshToken();
45
+ if (!refreshToken) {
46
+ throw new Error('No refresh token available for YouTube');
16
47
  }
17
48
 
18
- protected override get apiBaseUrl(): string {
19
- return 'https://www.googleapis.com/youtube/v3';
49
+ try {
50
+ const response = await axios.post('https://oauth2.googleapis.com/token', {
51
+ client_id: this.getCustomAttribute(2), // Store client ID in CustomAttribute2
52
+ client_secret: this.getCustomAttribute(3), // Store client secret in CustomAttribute3
53
+ refresh_token: refreshToken,
54
+ grant_type: 'refresh_token',
55
+ });
56
+
57
+ const { access_token, expires_in } = response.data;
58
+ await this.updateStoredTokens(access_token, undefined, expires_in);
59
+ } catch (error) {
60
+ throw new Error(`Failed to refresh YouTube access token: ${error.message}`);
20
61
  }
21
-
22
- /**
23
- * Axios instance for API requests
24
- */
25
- protected axiosInstance: AxiosInstance;
26
-
27
- /**
28
- * Initialize the axios instance with base configuration
29
- */
30
- protected initializeAxios(): void {
31
- this.axiosInstance = axios.create({
32
- baseURL: this.apiBaseUrl,
33
- headers: {
34
- 'Accept': 'application/json',
35
- 'Content-Type': 'application/json'
36
- }
37
- });
38
- }
39
-
40
- /**
41
- * YouTube-specific OAuth token refresh
42
- */
43
- protected async refreshAccessToken(): Promise<void> {
44
- const refreshToken = this.getRefreshToken();
45
- if (!refreshToken) {
46
- throw new Error('No refresh token available for YouTube');
47
- }
48
-
49
- try {
50
- const response = await axios.post('https://oauth2.googleapis.com/token', {
51
- client_id: this.getCustomAttribute(2), // Store client ID in CustomAttribute2
52
- client_secret: this.getCustomAttribute(3), // Store client secret in CustomAttribute3
53
- refresh_token: refreshToken,
54
- grant_type: 'refresh_token'
55
- });
56
-
57
- const { access_token, expires_in } = response.data;
58
- await this.updateStoredTokens(access_token, undefined, expires_in);
59
- } catch (error) {
60
- throw new Error(`Failed to refresh YouTube access token: ${error.message}`);
61
- }
62
+ }
63
+
64
+ /**
65
+ * Make authenticated request to YouTube API
66
+ */
67
+ protected async makeYouTubeRequest<T = any>(
68
+ endpoint: string,
69
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
70
+ data?: any,
71
+ params?: Record<string, any>,
72
+ contextUser?: UserInfo
73
+ ): Promise<T> {
74
+ if (!this.axiosInstance) {
75
+ this.initializeAxios();
62
76
  }
63
77
 
64
- /**
65
- * Make authenticated request to YouTube API
66
- */
67
- protected async makeYouTubeRequest<T = any>(
68
- endpoint: string,
69
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
70
- data?: any,
71
- params?: Record<string, any>,
72
- contextUser?: UserInfo
73
- ): Promise<T> {
74
- if (!this.axiosInstance) {
75
- this.initializeAxios();
76
- }
77
-
78
- return this.makeAuthenticatedRequest(async (token) => {
79
- try {
80
- const response = await this.axiosInstance.request<T>({
81
- url: endpoint,
82
- method,
83
- data,
84
- params,
85
- headers: {
86
- 'Authorization': `Bearer ${token}`
87
- }
88
- });
89
-
90
- return response.data;
91
- } catch (error) {
92
- if (axios.isAxiosError(error)) {
93
- this.handleYouTubeApiError(error);
94
- }
95
- throw error;
96
- }
78
+ return this.makeAuthenticatedRequest(async (token) => {
79
+ try {
80
+ const response = await this.axiosInstance.request<T>({
81
+ url: endpoint,
82
+ method,
83
+ data,
84
+ params,
85
+ headers: {
86
+ Authorization: `Bearer ${token}`,
87
+ },
97
88
  });
98
- }
99
89
 
100
- /**
101
- * Handle YouTube API errors
102
- */
103
- protected handleYouTubeApiError(error: AxiosError): void {
104
- const response = error.response;
105
- if (!response) {
106
- throw new Error('Network error occurred');
90
+ return response.data;
91
+ } catch (error) {
92
+ if (axios.isAxiosError(error)) {
93
+ this.handleYouTubeApiError(error);
107
94
  }
108
-
109
- const errorData: any = response.data;
110
- const errorMessage = errorData?.error?.message || response.statusText;
111
- const errorCode = errorData?.error?.code || response.status;
112
-
113
- // Check for quota exceeded
114
- if (errorCode === 403 && errorMessage.includes('quota')) {
115
- throw new Error(`YouTube API quota exceeded. ${errorMessage}`);
116
- }
117
-
118
- // Check for rate limiting
119
- if (errorCode === 429) {
120
- const retryAfter = response.headers['retry-after'];
121
- throw new Error(`Rate limit exceeded. Retry after ${retryAfter || '60'} seconds`);
122
- }
123
-
124
- throw new Error(`YouTube API error (${errorCode}): ${errorMessage}`);
95
+ throw error;
96
+ }
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Handle YouTube API errors
102
+ */
103
+ protected handleYouTubeApiError(error: AxiosError): void {
104
+ const response = error.response;
105
+ if (!response) {
106
+ throw new Error('Network error occurred');
125
107
  }
126
108
 
127
- /**
128
- * Upload video to YouTube
129
- */
130
- protected async uploadVideo(
131
- videoFile: MediaFile,
132
- metadata: {
133
- title: string;
134
- description?: string;
135
- tags?: string[];
136
- categoryId?: string;
137
- privacyStatus?: 'private' | 'unlisted' | 'public';
138
- }
139
- ): Promise<string> {
140
- // YouTube requires a resumable upload for videos
141
- const uploadUrl = await this.initiateResumableUpload(metadata);
142
- const videoId = await this.performResumableUpload(uploadUrl, videoFile);
143
- return videoId;
144
- }
109
+ const errorData: any = response.data;
110
+ const errorMessage = errorData?.error?.message || response.statusText;
111
+ const errorCode = errorData?.error?.code || response.status;
145
112
 
146
- /**
147
- * Initiate resumable upload session
148
- */
149
- private async initiateResumableUpload(metadata: any): Promise<string> {
150
- const response = await this.makeYouTubeRequest<any>(
151
- '/videos',
152
- 'POST',
153
- {
154
- snippet: {
155
- title: metadata.title,
156
- description: metadata.description || '',
157
- tags: metadata.tags || [],
158
- categoryId: metadata.categoryId || '22' // Default to People & Blogs
159
- },
160
- status: {
161
- privacyStatus: metadata.privacyStatus || 'private'
162
- }
163
- },
164
- {
165
- uploadType: 'resumable',
166
- part: 'snippet,status'
167
- }
168
- );
169
-
170
- return response.headers.location;
113
+ // Check for quota exceeded
114
+ if (errorCode === 403 && errorMessage.includes('quota')) {
115
+ throw new Error(`YouTube API quota exceeded. ${errorMessage}`);
171
116
  }
172
117
 
173
- /**
174
- * Perform the actual video upload
175
- */
176
- private async performResumableUpload(uploadUrl: string, videoFile: MediaFile): Promise<string> {
177
- const videoData = Buffer.isBuffer(videoFile.data)
178
- ? videoFile.data
179
- : Buffer.from(videoFile.data, 'base64');
180
-
181
- const response = await axios.put(uploadUrl, videoData, {
182
- headers: {
183
- 'Content-Type': videoFile.mimeType,
184
- 'Content-Length': videoData.length.toString(),
185
- 'Authorization': `Bearer ${this.getAccessToken()}`
186
- }
187
- });
188
-
189
- return response.data.id;
118
+ // Check for rate limiting
119
+ if (errorCode === 429) {
120
+ const retryAfter = response.headers['retry-after'];
121
+ throw new Error(`Rate limit exceeded. Retry after ${retryAfter || '60'} seconds`);
190
122
  }
191
123
 
192
- /**
193
- * Upload single media file (thumbnail)
194
- */
195
- protected async uploadSingleMedia(file: MediaFile): Promise<string> {
196
- // For YouTube, this would typically be used for thumbnails
197
- // The actual implementation would upload to YouTube's thumbnail endpoint
198
- throw new Error('Direct media upload not supported. Use uploadVideo for videos or setThumbnail for thumbnails.');
124
+ throw new Error(`YouTube API error (${errorCode}): ${errorMessage}`);
125
+ }
126
+
127
+ /**
128
+ * Upload video to YouTube
129
+ */
130
+ protected async uploadVideo(
131
+ videoFile: MediaFile,
132
+ metadata: {
133
+ title: string;
134
+ description?: string;
135
+ tags?: string[];
136
+ categoryId?: string;
137
+ privacyStatus?: 'private' | 'unlisted' | 'public';
199
138
  }
200
-
201
- /**
202
- * Convert YouTube video to standard social post format
203
- */
204
- protected normalizePost(youtubeVideo: any): SocialPost {
205
- return {
206
- id: youtubeVideo.id,
207
- platform: 'YouTube',
208
- profileId: youtubeVideo.snippet.channelId,
209
- content: youtubeVideo.snippet.description || '',
210
- mediaUrls: [`https://www.youtube.com/watch?v=${youtubeVideo.id}`],
211
- publishedAt: new Date(youtubeVideo.snippet.publishedAt),
212
- scheduledFor: youtubeVideo.status.publishAt ? new Date(youtubeVideo.status.publishAt) : undefined,
213
- analytics: this.extractVideoAnalytics(youtubeVideo),
214
- platformSpecificData: {
215
- title: youtubeVideo.snippet.title,
216
- tags: youtubeVideo.snippet.tags || [],
217
- categoryId: youtubeVideo.snippet.categoryId,
218
- duration: youtubeVideo.contentDetails?.duration,
219
- definition: youtubeVideo.contentDetails?.definition,
220
- privacyStatus: youtubeVideo.status.privacyStatus,
221
- embeddable: youtubeVideo.status.embeddable,
222
- thumbnails: youtubeVideo.snippet.thumbnails
223
- }
224
- };
139
+ ): Promise<string> {
140
+ // YouTube requires a resumable upload for videos
141
+ const uploadUrl = await this.initiateResumableUpload(metadata);
142
+ const videoId = await this.performResumableUpload(uploadUrl, videoFile);
143
+ return videoId;
144
+ }
145
+
146
+ /**
147
+ * Initiate resumable upload session
148
+ */
149
+ private async initiateResumableUpload(metadata: any): Promise<string> {
150
+ const response = await this.makeYouTubeRequest<any>(
151
+ '/videos',
152
+ 'POST',
153
+ {
154
+ snippet: {
155
+ title: metadata.title,
156
+ description: metadata.description || '',
157
+ tags: metadata.tags || [],
158
+ categoryId: metadata.categoryId || '22', // Default to People & Blogs
159
+ },
160
+ status: {
161
+ privacyStatus: metadata.privacyStatus || 'private',
162
+ },
163
+ },
164
+ {
165
+ uploadType: 'resumable',
166
+ part: 'snippet,status',
167
+ }
168
+ );
169
+
170
+ return response.headers.location;
171
+ }
172
+
173
+ /**
174
+ * Perform the actual video upload
175
+ */
176
+ private async performResumableUpload(uploadUrl: string, videoFile: MediaFile): Promise<string> {
177
+ const videoData = Buffer.isBuffer(videoFile.data) ? videoFile.data : Buffer.from(videoFile.data, 'base64');
178
+
179
+ const response = await axios.put(uploadUrl, videoData, {
180
+ headers: {
181
+ 'Content-Type': videoFile.mimeType,
182
+ 'Content-Length': videoData.length.toString(),
183
+ Authorization: `Bearer ${this.getAccessToken()}`,
184
+ },
185
+ });
186
+
187
+ return response.data.id;
188
+ }
189
+
190
+ /**
191
+ * Upload single media file (thumbnail)
192
+ */
193
+ protected async uploadSingleMedia(file: MediaFile): Promise<string> {
194
+ // For YouTube, this would typically be used for thumbnails
195
+ // The actual implementation would upload to YouTube's thumbnail endpoint
196
+ throw new Error('Direct media upload not supported. Use uploadVideo for videos or setThumbnail for thumbnails.');
197
+ }
198
+
199
+ /**
200
+ * Convert YouTube video to standard social post format
201
+ */
202
+ protected normalizePost(youtubeVideo: any): SocialPost {
203
+ return {
204
+ id: youtubeVideo.id,
205
+ platform: 'YouTube',
206
+ profileId: youtubeVideo.snippet.channelId,
207
+ content: youtubeVideo.snippet.description || '',
208
+ mediaUrls: [`https://www.youtube.com/watch?v=${youtubeVideo.id}`],
209
+ publishedAt: new Date(youtubeVideo.snippet.publishedAt),
210
+ scheduledFor: youtubeVideo.status.publishAt ? new Date(youtubeVideo.status.publishAt) : undefined,
211
+ analytics: this.extractVideoAnalytics(youtubeVideo),
212
+ platformSpecificData: {
213
+ title: youtubeVideo.snippet.title,
214
+ tags: youtubeVideo.snippet.tags || [],
215
+ categoryId: youtubeVideo.snippet.categoryId,
216
+ duration: youtubeVideo.contentDetails?.duration,
217
+ definition: youtubeVideo.contentDetails?.definition,
218
+ privacyStatus: youtubeVideo.status.privacyStatus,
219
+ embeddable: youtubeVideo.status.embeddable,
220
+ thumbnails: youtubeVideo.snippet.thumbnails,
221
+ },
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Extract analytics from video statistics
227
+ */
228
+ private extractVideoAnalytics(video: any): any {
229
+ if (!video.statistics) {
230
+ return undefined;
225
231
  }
226
232
 
227
- /**
228
- * Extract analytics from video statistics
229
- */
230
- private extractVideoAnalytics(video: any): any {
231
- if (!video.statistics) {
232
- return undefined;
233
- }
234
-
235
- return this.normalizeAnalytics({
236
- impressions: parseInt(video.statistics.viewCount || '0'),
237
- engagements: parseInt(video.statistics.likeCount || '0') +
238
- parseInt(video.statistics.commentCount || '0'),
239
- clicks: 0, // YouTube doesn't provide click data
240
- shares: 0, // YouTube doesn't provide share count
241
- comments: parseInt(video.statistics.commentCount || '0'),
242
- likes: parseInt(video.statistics.likeCount || '0'),
243
- reach: parseInt(video.statistics.viewCount || '0'),
244
- saves: parseInt(video.statistics.favoriteCount || '0'),
245
- videoViews: parseInt(video.statistics.viewCount || '0'),
246
- dislikes: parseInt(video.statistics.dislikeCount || '0')
247
- });
233
+ return this.normalizeAnalytics({
234
+ impressions: parseInt(video.statistics.viewCount || '0'),
235
+ engagements: parseInt(video.statistics.likeCount || '0') + parseInt(video.statistics.commentCount || '0'),
236
+ clicks: 0, // YouTube doesn't provide click data
237
+ shares: 0, // YouTube doesn't provide share count
238
+ comments: parseInt(video.statistics.commentCount || '0'),
239
+ likes: parseInt(video.statistics.likeCount || '0'),
240
+ reach: parseInt(video.statistics.viewCount || '0'),
241
+ saves: parseInt(video.statistics.favoriteCount || '0'),
242
+ videoViews: parseInt(video.statistics.viewCount || '0'),
243
+ dislikes: parseInt(video.statistics.dislikeCount || '0'),
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Search for videos
249
+ */
250
+ protected async searchPosts(params: any): Promise<SocialPost[]> {
251
+ const searchParams: any = {
252
+ part: 'snippet',
253
+ type: 'video',
254
+ maxResults: params.limit || 50,
255
+ order: params.sortBy || 'relevance',
256
+ };
257
+
258
+ // Add search query
259
+ if (params.query) {
260
+ searchParams.q = params.query;
248
261
  }
249
262
 
250
- /**
251
- * Search for videos
252
- */
253
- protected async searchPosts(params: any): Promise<SocialPost[]> {
254
- const searchParams: any = {
255
- part: 'snippet',
256
- type: 'video',
257
- maxResults: params.limit || 50,
258
- order: params.sortBy || 'relevance'
259
- };
260
-
261
- // Add search query
262
- if (params.query) {
263
- searchParams.q = params.query;
264
- }
265
-
266
- // Add channel filter if searching within a specific channel
267
- if (params.channelId || this.getCustomAttribute(1)) {
268
- searchParams.channelId = params.channelId || this.getCustomAttribute(1);
269
- }
270
-
271
- // Add date filters
272
- if (params.startDate) {
273
- searchParams.publishedAfter = this.formatDate(params.startDate);
274
- }
275
- if (params.endDate) {
276
- searchParams.publishedBefore = this.formatDate(params.endDate);
277
- }
278
-
279
- // Add pagination
280
- if (params.pageToken) {
281
- searchParams.pageToken = params.pageToken;
282
- }
283
-
284
- const response = await this.makeYouTubeRequest<any>(
285
- '/search',
286
- 'GET',
287
- undefined,
288
- searchParams
289
- );
290
-
291
- // Get full video details for search results
292
- const videoIds = response.items.map((item: any) => item.id.videoId).join(',');
293
- const videosResponse = await this.makeYouTubeRequest<any>(
294
- '/videos',
295
- 'GET',
296
- undefined,
297
- {
298
- part: 'snippet,statistics,status,contentDetails',
299
- id: videoIds
300
- }
301
- );
302
-
303
- return videosResponse.items.map((video: any) => this.normalizePost(video));
263
+ // Add channel filter if searching within a specific channel
264
+ if (params.channelId || this.getCustomAttribute(1)) {
265
+ searchParams.channelId = params.channelId || this.getCustomAttribute(1);
304
266
  }
305
267
 
306
- /**
307
- * Get quota cost for an operation
308
- */
309
- protected getQuotaCost(operation: string): number {
310
- const quotaCosts: Record<string, number> = {
311
- 'videos.list': 1,
312
- 'videos.insert': 1600,
313
- 'videos.update': 50,
314
- 'videos.delete': 50,
315
- 'search.list': 100,
316
- 'channels.list': 1,
317
- 'playlists.list': 1,
318
- 'playlists.insert': 50,
319
- 'playlistItems.insert': 50,
320
- 'comments.list': 1,
321
- 'commentThreads.list': 1
322
- };
323
-
324
- return quotaCosts[operation] || 1;
268
+ // Add date filters
269
+ if (params.startDate) {
270
+ searchParams.publishedAfter = this.formatDate(params.startDate);
325
271
  }
326
-
327
- /**
328
- * Parse ISO 8601 duration to seconds
329
- */
330
- protected parseDuration(isoDuration: string): number {
331
- const matches = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
332
- if (!matches) return 0;
333
-
334
- const hours = parseInt(matches[1] || '0');
335
- const minutes = parseInt(matches[2] || '0');
336
- const seconds = parseInt(matches[3] || '0');
337
-
338
- return hours * 3600 + minutes * 60 + seconds;
272
+ if (params.endDate) {
273
+ searchParams.publishedBefore = this.formatDate(params.endDate);
339
274
  }
340
275
 
341
- /**
342
- * Format bytes to human readable size
343
- */
344
- protected formatBytes(bytes: number): string {
345
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
346
- if (bytes === 0) return '0 Bytes';
347
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
348
- return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
276
+ // Add pagination
277
+ if (params.pageToken) {
278
+ searchParams.pageToken = params.pageToken;
349
279
  }
350
280
 
351
- /**
352
- * Validate video file
353
- */
354
- protected validateVideoFile(file: MediaFile): void {
355
- const allowedTypes = [
356
- 'video/mp4',
357
- 'video/x-msvideo', // AVI
358
- 'video/quicktime', // MOV
359
- 'video/x-ms-wmv', // WMV
360
- 'video/x-flv', // FLV
361
- 'video/webm'
362
- ];
363
-
364
- if (!allowedTypes.includes(file.mimeType)) {
365
- throw new Error(`Unsupported video format: ${file.mimeType}`);
366
- }
281
+ const response = await this.makeYouTubeRequest<any>('/search', 'GET', undefined, searchParams);
282
+
283
+ // Get full video details for search results
284
+ const videoIds = response.items.map((item: any) => item.id.videoId).join(',');
285
+ const videosResponse = await this.makeYouTubeRequest<any>('/videos', 'GET', undefined, {
286
+ part: 'snippet,statistics,status,contentDetails',
287
+ id: videoIds,
288
+ });
289
+
290
+ return videosResponse.items.map((video: any) => this.normalizePost(video));
291
+ }
292
+
293
+ /**
294
+ * Get quota cost for an operation
295
+ */
296
+ protected getQuotaCost(operation: string): number {
297
+ const quotaCosts: Record<string, number> = {
298
+ 'videos.list': 1,
299
+ 'videos.insert': 1600,
300
+ 'videos.update': 50,
301
+ 'videos.delete': 50,
302
+ 'search.list': 100,
303
+ 'channels.list': 1,
304
+ 'playlists.list': 1,
305
+ 'playlists.insert': 50,
306
+ 'playlistItems.insert': 50,
307
+ 'comments.list': 1,
308
+ 'commentThreads.list': 1,
309
+ };
310
+
311
+ return quotaCosts[operation] || 1;
312
+ }
313
+
314
+ /**
315
+ * Parse ISO 8601 duration to seconds
316
+ */
317
+ protected parseDuration(isoDuration: string): number {
318
+ const matches = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
319
+ if (!matches) return 0;
320
+
321
+ const hours = parseInt(matches[1] || '0');
322
+ const minutes = parseInt(matches[2] || '0');
323
+ const seconds = parseInt(matches[3] || '0');
324
+
325
+ return hours * 3600 + minutes * 60 + seconds;
326
+ }
327
+
328
+ /**
329
+ * Format bytes to human readable size
330
+ */
331
+ protected formatBytes(bytes: number): string {
332
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
333
+ if (bytes === 0) return '0 Bytes';
334
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
335
+ return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
336
+ }
337
+
338
+ /**
339
+ * Validate video file
340
+ */
341
+ protected validateVideoFile(file: MediaFile): void {
342
+ const allowedTypes = [
343
+ 'video/mp4',
344
+ 'video/x-msvideo', // AVI
345
+ 'video/quicktime', // MOV
346
+ 'video/x-ms-wmv', // WMV
347
+ 'video/x-flv', // FLV
348
+ 'video/webm',
349
+ ];
350
+
351
+ if (!allowedTypes.includes(file.mimeType)) {
352
+ throw new Error(`Unsupported video format: ${file.mimeType}`);
353
+ }
367
354
 
368
- // YouTube max file size is 128GB or 12 hours
369
- const maxSize = 128 * 1024 * 1024 * 1024; // 128GB
370
- if (file.size > maxSize) {
371
- throw new Error(`Video file too large. Maximum size is ${this.formatBytes(maxSize)}`);
372
- }
355
+ // YouTube max file size is 128GB or 12 hours
356
+ const maxSize = 128 * 1024 * 1024 * 1024; // 128GB
357
+ if (file.size > maxSize) {
358
+ throw new Error(`Video file too large. Maximum size is ${this.formatBytes(maxSize)}`);
373
359
  }
374
- }
360
+ }
361
+ }