my-youtube-api 1.0.0 → 1.0.2

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 (25) hide show
  1. package/dist/errors/youtube-api-errors.d.ts +30 -0
  2. package/dist/errors/youtube-api-errors.js +59 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/youtube-handlers/youtube-video-poster.d.ts +39 -3
  6. package/dist/youtube-handlers/youtube-video-poster.js +291 -10
  7. package/dist/youtube-handlers copy/youtube-account-handler.d.ts +21 -0
  8. package/dist/youtube-handlers copy/youtube-account-handler.js +33 -0
  9. package/dist/youtube-handlers copy/youtube-channel-handler.d.ts +65 -0
  10. package/dist/youtube-handlers copy/youtube-channel-handler.js +193 -0
  11. package/dist/youtube-handlers copy/youtube-channel-handler2.d.ts +65 -0
  12. package/dist/youtube-handlers copy/youtube-channel-handler2.js +177 -0
  13. package/dist/youtube-handlers copy/youtube-video-handler.d.ts +30 -0
  14. package/dist/youtube-handlers copy/youtube-video-handler.js +55 -0
  15. package/dist/youtube-handlers copy/youtube-video-poster.d.ts +23 -0
  16. package/dist/youtube-handlers copy/youtube-video-poster.js +47 -0
  17. package/package.json +1 -1
  18. package/src/errors/youtube-api-errors.ts +72 -0
  19. package/src/index.ts +1 -1
  20. package/src/youtube-handlers/youtube-video-poster.ts +361 -12
  21. package/src/youtube-handlers copy/youtube-account-handler.ts +51 -0
  22. package/src/youtube-handlers copy/youtube-channel-handler.ts +275 -0
  23. package/src/youtube-handlers copy/youtube-channel-handler2.ts +249 -0
  24. package/src/youtube-handlers copy/youtube-video-handler.ts +87 -0
  25. package/src/youtube-handlers copy/youtube-video-poster.ts +65 -0
@@ -0,0 +1,275 @@
1
+ import axios from "axios";
2
+
3
+ interface VideoAnalytics {
4
+ videoId: string;
5
+ title: string;
6
+ views: number;
7
+ likes: number;
8
+ comments: number;
9
+ publishedAt: string;
10
+ thumbnail: string;
11
+ }
12
+
13
+ interface ChannelAnalytics {
14
+ channelId: string;
15
+ title: string;
16
+ description: string;
17
+ thumbnail: string;
18
+ subscriberCount: number;
19
+ viewCount: number;
20
+ videoCount: number;
21
+ hiddenSubscriberCount: boolean;
22
+ }
23
+
24
+
25
+ interface AudienceAnalytics {
26
+ views: number;
27
+ estimatedMinutesWatched: number;
28
+ averageViewDuration: number;
29
+ subscribersGained: number;
30
+ subscribersLost: number;
31
+ }
32
+
33
+
34
+ interface VideoAnalytics {
35
+ videoId: string;
36
+ title: string;
37
+ views: number;
38
+ likes: number;
39
+ comments: number;
40
+ publishedAt: string;
41
+ thumbneil?: string;
42
+ videoType: "short" | "regular";
43
+ }
44
+
45
+ interface ChannelRevenue {
46
+ estimatedRevenue: number; // in USD
47
+ currency: string;
48
+ startDate: string;
49
+ endDate: string;
50
+ }
51
+
52
+ /*
53
+ . Enable YouTube Analytics API
54
+
55
+ Go to your Google Cloud Console → APIs & Services → Library → Search for and enable:
56
+
57
+ YouTube Analytics API
58
+
59
+ YouTube Reporting API
60
+
61
+ for report and analytics
62
+ */
63
+
64
+ export class YoutubeChannelHandler {
65
+ private access_token: string;
66
+ private channel_id: string;
67
+
68
+ constructor(access_token: string, channel_id: string) {
69
+ this.access_token = access_token;
70
+ this.channel_id = channel_id;
71
+ }
72
+
73
+ /**
74
+ * Fetches basic channel statistics and information
75
+ * @returns Channel analytics including subscriber count, view count, and video count
76
+ */
77
+ async fetchChannelAnalytics(): Promise<ChannelAnalytics> {
78
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
79
+ params: {
80
+ part: "snippet,statistics,contentDetails",
81
+ mine: true // This gets the authenticated user's channel
82
+ },
83
+ headers: { Authorization: `Bearer ${this.access_token}` },
84
+ });
85
+
86
+ const channel = response.data.items[0];
87
+
88
+ return {
89
+ channelId: channel.id,
90
+ title: channel.snippet.title,
91
+ description: channel.snippet.description,
92
+ thumbnail: channel.snippet.thumbnails?.high?.url,
93
+ subscriberCount: parseInt(channel.statistics.subscriberCount || '0'),
94
+ viewCount: parseInt(channel.statistics.viewCount || '0'),
95
+ videoCount: parseInt(channel.statistics.videoCount || '0'),
96
+ hiddenSubscriberCount: channel.statistics.hiddenSubscriberCount || false
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Fetches analytics for all videos in the channel
102
+ * @param maxResults Maximum number of videos to fetch (default: 50, max: 50)
103
+ * @returns Array of video analytics including views, likes, and comments
104
+ */
105
+ async fetchVideos(maxResults: number = 50): Promise<VideoAnalytics[]> {
106
+ // 1️⃣ Get uploads playlist ID
107
+ const channelRes = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
108
+ params: { part: "contentDetails", id: this.channel_id },
109
+ headers: { Authorization: `Bearer ${this.access_token}` },
110
+ });
111
+
112
+ const uploadsPlaylistId = channelRes.data.items[0]?.contentDetails?.relatedPlaylists?.uploads;
113
+ if (!uploadsPlaylistId) return [];
114
+
115
+ // 2️⃣ Fetch videos from the uploads playlist
116
+ const videosRes = await axios.get("https://www.googleapis.com/youtube/v3/playlistItems", {
117
+ params: {
118
+ part: "snippet,contentDetails",
119
+ playlistId: uploadsPlaylistId,
120
+ maxResults: Math.min(maxResults, 50),
121
+ },
122
+ headers: { Authorization: `Bearer ${this.access_token}` },
123
+ });
124
+
125
+ const videoIds = videosRes.data.items.map((v: any) => v.contentDetails.videoId);
126
+ if (videoIds.length === 0) return [];
127
+
128
+ // 3️⃣ Fetch analytics & contentDetails for each video
129
+ const analyticsRes = await axios.get("https://www.googleapis.com/youtube/v3/videos", {
130
+ params: {
131
+ part: "snippet,statistics,contentDetails",
132
+ id: videoIds.join(","),
133
+ maxResults: Math.min(maxResults, 50),
134
+ },
135
+ headers: { Authorization: `Bearer ${this.access_token}` },
136
+ });
137
+
138
+ return analyticsRes.data.items.map((video: any) => {
139
+ // Convert ISO 8601 duration to seconds
140
+ const duration = video.contentDetails?.duration || "PT0S";
141
+ const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
142
+ const seconds =
143
+ (parseInt(match?.[1] || "0") * 3600) +
144
+ (parseInt(match?.[2] || "0") * 60) +
145
+ parseInt(match?.[3] || "0");
146
+
147
+ return {
148
+ videoId: video.id,
149
+ title: video.snippet.title,
150
+ views: parseInt(video.statistics.viewCount || 0),
151
+ likes: parseInt(video.statistics.likeCount || 0),
152
+ comments: parseInt(video.statistics.commentCount || 0),
153
+ publishedAt: video.snippet.publishedAt,
154
+ thumbnail: video.snippet.thumbnails?.high?.url,
155
+ videoType: seconds < 60 ? "short" : "regular",
156
+ };
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Fetches audience analytics and engagement metrics for the channel
162
+ * @returns Audience analytics including view duration and subscriber changes
163
+ */
164
+ async fetchAudienceAnalytics(): Promise<AudienceAnalytics> {
165
+ try {
166
+
167
+ // 1️⃣ Compute proper date range (last 30 days)
168
+ const endDate = new Date();
169
+ const startDate = new Date();
170
+ startDate.setDate(endDate.getDate() - 30);
171
+
172
+ const formatDate = (date: Date) =>
173
+ date.toISOString().split("T")[0]; // YYYY-MM-DD
174
+
175
+ // 2️⃣ Call YouTube Analytics API
176
+ const response = await axios.get(
177
+ "https://youtubeanalytics.googleapis.com/v2/reports",
178
+ {
179
+ params: {
180
+ ids: `channel==${this.channel_id}`,
181
+ startDate: formatDate(startDate),
182
+ endDate: formatDate(endDate),
183
+ metrics:
184
+ "views,estimatedMinutesWatched,averageViewDuration,subscribersGained,subscribersLost",
185
+ dimensions: "day",
186
+ },
187
+ headers: { Authorization: `Bearer ${this.access_token}` },
188
+ }
189
+ );
190
+
191
+ const rows: any[] = response.data.rows || [];
192
+
193
+ if (rows.length === 0) {
194
+ return {
195
+ views: 0,
196
+ estimatedMinutesWatched: 0,
197
+ averageViewDuration: 0,
198
+ subscribersGained: 0,
199
+ subscribersLost: 0,
200
+ };
201
+ }
202
+
203
+ // 3️⃣ Sum up all daily metrics
204
+ const totals = rows.reduce(
205
+ (acc: any, row: any[]) => ({
206
+ views: acc.views + Number(row[1] || 0),
207
+ estimatedMinutesWatched:
208
+ acc.estimatedMinutesWatched + Number(row[2] || 0),
209
+ averageViewDuration: acc.averageViewDuration + Number(row[3] || 0),
210
+ subscribersGained: acc.subscribersGained + Number(row[4] || 0),
211
+ subscribersLost: acc.subscribersLost + Number(row[5] || 0),
212
+ }),
213
+ {
214
+ views: 0,
215
+ estimatedMinutesWatched: 0,
216
+ averageViewDuration: 0,
217
+ subscribersGained: 0,
218
+ subscribersLost: 0,
219
+ }
220
+ );
221
+
222
+ // 4️⃣ Return final analytics
223
+ return {
224
+ views: totals.views,
225
+ estimatedMinutesWatched: totals.estimatedMinutesWatched,
226
+ averageViewDuration: totals.averageViewDuration / rows.length, // Average of averages
227
+ subscribersGained: totals.subscribersGained,
228
+ subscribersLost: totals.subscribersLost,
229
+ };
230
+ } catch (err) {
231
+ console.error("Error fetching audience analytics:", err);
232
+ return {
233
+ views: 0,
234
+ estimatedMinutesWatched: 0,
235
+ averageViewDuration: 0,
236
+ subscribersGained: 0,
237
+ subscribersLost: 0,
238
+ };
239
+ }
240
+ }
241
+
242
+ async fetchChannelRevenue(startDate: string = "2023-01-01", endDate: string = new Date().toISOString().split("T")[0]): Promise<ChannelRevenue> {
243
+ try {
244
+ const response = await axios.get("https://youtubeanalytics.googleapis.com/v2/reports", {
245
+ params: {
246
+ ids: `channel==${this.channel_id}`,
247
+ startDate,
248
+ endDate,
249
+ metrics: "estimatedRevenue",
250
+ dimensions: "day", // optional: can remove to get total
251
+ currency: "USD"
252
+ },
253
+ headers: {
254
+ Authorization: `Bearer ${this.access_token}`
255
+ }
256
+ });
257
+
258
+ // Sum revenue if using "day" dimension
259
+ const totalRevenue = response.data.rows
260
+ ? response.data.rows.reduce((acc: number, row: any[]) => acc + parseFloat(row[1] || 0), 0)
261
+ : 0;
262
+
263
+ return {
264
+ estimatedRevenue: totalRevenue,
265
+ currency: "USD",
266
+ startDate,
267
+ endDate
268
+ };
269
+ } catch (err: any) {
270
+ console.error("Failed to fetch channel revenue:", err.response?.data || err.message);
271
+ return { estimatedRevenue: 0, currency: "USD", startDate, endDate };
272
+ }
273
+ }
274
+
275
+ }
@@ -0,0 +1,249 @@
1
+ import axios from "axios";
2
+
3
+ interface GrowthMetrics {
4
+ subscribers: number;
5
+ totalVideoViews: number;
6
+ estimatedRevenue: number;
7
+ videosCount: number;
8
+ period: string;
9
+ }
10
+
11
+ interface SubscriberBalance {
12
+ subscribersGained: number;
13
+ subscribersLost: number;
14
+ netSubscribers: number;
15
+ videosPublished: number;
16
+ period: string;
17
+ }
18
+
19
+ interface VideoPerformance {
20
+ videoId: string;
21
+ title: string;
22
+ publishedAt: string;
23
+ views: number;
24
+ watchTime: number; // in minutes
25
+ avgViewDuration: number; // in seconds
26
+ likes: number;
27
+ dislikes: number;
28
+ comments: number;
29
+ shares: number;
30
+ thumbnail: string;
31
+ }
32
+
33
+ interface PublishedVideosStats {
34
+ totalViews: number;
35
+ totalLikes: number;
36
+ totalDislikes: number;
37
+ totalComments: number;
38
+ totalShares: number;
39
+ totalVideos: number;
40
+ averageEngagementRate: number;
41
+ period: string;
42
+ }
43
+
44
+ export class YoutubeAnalyticsHandler {
45
+ private access_token: string;
46
+ private channel_id: string;
47
+
48
+ constructor(access_token: string, channel_id: string) {
49
+ this.access_token = access_token;
50
+ this.channel_id = channel_id;
51
+ }
52
+
53
+ /**
54
+ * Fetches growth metrics for the channel over a specified period
55
+ * @param period Time period for growth analysis (7days, 30days, 90days, 365days)
56
+ * @returns Growth metrics including subscribers, views, revenue, and video count
57
+ * . Enable YouTube Analytics API
58
+
59
+ Go to your Google Cloud Console → APIs & Services → Library → Search for and enable:
60
+
61
+ YouTube Analytics API
62
+
63
+ YouTube Reporting API
64
+ */
65
+ async fetchGrowthMetrics(period: string = "30days"): Promise<GrowthMetrics> {
66
+ const endDate = "today";
67
+ const startDate = this.getStartDate(period);
68
+
69
+ // Fetch analytics data
70
+ const [analyticsResponse, channelResponse] = await Promise.all([
71
+ axios.get("https://youtubeanalytics.googleapis.com/v2/reports", {
72
+ params: {
73
+ ids: `channel==${this.channel_id}`,
74
+ startDate,
75
+ endDate,
76
+ metrics: "subscribersGained,views,estimatedRevenue",
77
+ dimensions: "day"
78
+ },
79
+ headers: { Authorization: `Bearer ${this.access_token}` },
80
+ }),
81
+ this.fetchChannelBasicStats()
82
+ ]);
83
+
84
+ const rows = analyticsResponse.data.rows || [];
85
+
86
+ const totals = rows.reduce((acc: any, row: any[]) => ({
87
+ subscribersGained: acc.subscribersGained + (row[1] || 0),
88
+ views: acc.views + (row[2] || 0),
89
+ estimatedRevenue: acc.estimatedRevenue + (row[3] || 0)
90
+ }), { subscribersGained: 0, views: 0, estimatedRevenue: 0 });
91
+
92
+ return {
93
+ subscribers: totals.subscribersGained,
94
+ totalVideoViews: totals.views,
95
+ estimatedRevenue: totals.estimatedRevenue,
96
+ videosCount: channelResponse.videoCount,
97
+ period: `${startDate} to ${endDate}`
98
+ };
99
+ }
100
+
101
+
102
+
103
+ /**
104
+ * Fetches aggregated statistics for all published videos in a period
105
+ * @param period Time period for analysis (7days, 30days, 90days)
106
+ * @returns Combined statistics for all published videos
107
+ */
108
+ async fetchPublishedVideosStats(period: string = "30days"): Promise<PublishedVideosStats> {
109
+ const endDate = "today";
110
+ const startDate = this.getStartDate(period);
111
+
112
+ const videos = await this.fetchVideosPublishedInPeriod(startDate, endDate);
113
+
114
+ if (videos.length === 0) {
115
+ return {
116
+ totalViews: 0,
117
+ totalLikes: 0,
118
+ totalDislikes: 0,
119
+ totalComments: 0,
120
+ totalShares: 0,
121
+ totalVideos: 0,
122
+ averageEngagementRate: 0,
123
+ period: `${startDate} to ${endDate}`
124
+ };
125
+ }
126
+
127
+ const videoIds = videos.map(v => v.videoId);
128
+ const videoStats = await this.fetchVideosDetailedStats(videoIds);
129
+
130
+ const totals = videoStats.reduce((acc: any, video: VideoPerformance) => ({
131
+ totalViews: acc.totalViews + video.views,
132
+ totalLikes: acc.totalLikes + video.likes,
133
+ totalDislikes: acc.totalDislikes + video.dislikes,
134
+ totalComments: acc.totalComments + video.comments,
135
+ totalShares: acc.totalShares + video.shares
136
+ }), { totalViews: 0, totalLikes: 0, totalDislikes: 0, totalComments: 0, totalShares: 0 });
137
+
138
+ const averageEngagementRate = ((totals.totalLikes + totals.totalComments + totals.totalShares) / totals.totalViews) * 100;
139
+
140
+ return {
141
+ totalViews: totals.totalViews,
142
+ totalLikes: totals.totalLikes,
143
+ totalDislikes: totals.totalDislikes,
144
+ totalComments: totals.totalComments,
145
+ totalShares: totals.totalShares,
146
+ totalVideos: videos.length,
147
+ averageEngagementRate: isNaN(averageEngagementRate) ? 0 : averageEngagementRate,
148
+ period: `${startDate} to ${endDate}`
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Fetches detailed list of videos with comprehensive performance metrics
154
+ * @param maxResults Maximum number of videos to return (default: 50, max: 50)
155
+ * @returns Array of video performance metrics
156
+ */
157
+ async fetchVideosList(maxResults: number = 50): Promise<VideoPerformance[]> {
158
+ // Get uploads playlist ID
159
+ const channelRes = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
160
+ params: { part: "contentDetails", id: this.channel_id },
161
+ headers: { Authorization: `Bearer ${this.access_token}` },
162
+ });
163
+
164
+ const uploadsPlaylistId = channelRes.data.items[0]?.contentDetails?.relatedPlaylists?.uploads;
165
+ if (!uploadsPlaylistId) return [];
166
+
167
+ // Fetch videos from uploads playlist
168
+ const videosRes = await axios.get("https://www.googleapis.com/youtube/v3/playlistItems", {
169
+ params: {
170
+ part: "snippet,contentDetails",
171
+ playlistId: uploadsPlaylistId,
172
+ maxResults: Math.min(maxResults, 50)
173
+ },
174
+ headers: { Authorization: `Bearer ${this.access_token}` },
175
+ });
176
+
177
+ const videoIds = videosRes.data.items.map((v: any) => v.contentDetails.videoId);
178
+ if (videoIds.length === 0) return [];
179
+
180
+ // Fetch detailed statistics for each video
181
+ return await this.fetchVideosDetailedStats(videoIds);
182
+ }
183
+
184
+ // ============ HELPER METHODS ============
185
+
186
+ private async fetchChannelBasicStats(): Promise<{ videoCount: number }> {
187
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
188
+ params: {
189
+ part: "statistics",
190
+ id: this.channel_id
191
+ },
192
+ headers: { Authorization: `Bearer ${this.access_token}` },
193
+ });
194
+
195
+ return {
196
+ videoCount: parseInt(response.data.items[0]?.statistics?.videoCount || 0)
197
+ };
198
+ }
199
+
200
+ private async fetchVideosPublishedInPeriod(startDate: string, endDate: string): Promise<any[]> {
201
+ const videos = await this.fetchVideosList(100); // Get more videos for period filtering
202
+
203
+ return videos.filter(video => {
204
+ const publishedDate = new Date(video.publishedAt).toISOString().split('T')[0];
205
+ return publishedDate >= startDate && publishedDate <= endDate;
206
+ });
207
+ }
208
+
209
+ private async fetchVideosDetailedStats(videoIds: string[]): Promise<VideoPerformance[]> {
210
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/videos", {
211
+ params: {
212
+ part: "snippet,statistics,contentDetails",
213
+ id: videoIds.join(','),
214
+ maxResults: videoIds.length
215
+ },
216
+ headers: { Authorization: `Bearer ${this.access_token}` },
217
+ });
218
+
219
+ return response.data.items.map((video: any) => ({
220
+ videoId: video.id,
221
+ title: video.snippet.title,
222
+ publishedAt: video.snippet.publishedAt,
223
+ views: parseInt(video.statistics.viewCount || 0),
224
+ watchTime: parseInt(video.statistics.watchTime || 0) / 60, // Convert to minutes
225
+ avgViewDuration: parseInt(video.statistics.averageViewDuration || 0),
226
+ likes: parseInt(video.statistics.likeCount || 0),
227
+ dislikes: parseInt(video.statistics.dislikeCount || 0),
228
+ comments: parseInt(video.statistics.commentCount || 0),
229
+ shares: parseInt(video.statistics.shareCount || 0),
230
+ thumbnail: video.snippet.thumbnails?.high?.url
231
+ }));
232
+ }
233
+
234
+ private getStartDate(period: string): string {
235
+ const days = parseInt(period);
236
+ if (!isNaN(days)) {
237
+ return `${days}daysAgo`;
238
+ }
239
+
240
+ const periodMap: { [key: string]: string } = {
241
+ "7days": "7daysAgo",
242
+ "30days": "30daysAgo",
243
+ "90days": "90daysAgo",
244
+ "365days": "365daysAgo"
245
+ };
246
+
247
+ return periodMap[period] || "30daysAgo";
248
+ }
249
+ }
@@ -0,0 +1,87 @@
1
+ import axios from "axios";
2
+
3
+
4
+ interface CommentReply {
5
+ replyId: string;
6
+ author: string;
7
+ text: string;
8
+ likeCount: number;
9
+ publishedAt: string;
10
+ }
11
+ interface CommentAnalyticsWithReplies {
12
+ commentId: string;
13
+ videoId: string;
14
+ author: string;
15
+ text: string;
16
+ likeCount: number;
17
+ publishedAt: string;
18
+ replyCount: number;
19
+ replies: CommentReply[];
20
+ }
21
+ export class YoutubeVideoHandler {
22
+
23
+ private video_id: string;
24
+ private access_token: string;
25
+
26
+ constructor(access_token: string, video_id: string) {
27
+ this.access_token = access_token;
28
+ this.video_id = video_id;
29
+ }
30
+
31
+ /**
32
+ * Fetches comments analytics for a specific video or all channel videos
33
+ * @param videoId Optional video ID - if not provided, fetches comments from all channel videos
34
+ * @param maxResults Maximum number of comments to fetch per video (default: 20, max: 100)
35
+ * @returns Array of comment analytics including like counts and reply counts
36
+ */
37
+ async fetchCommentsAnalytics(maxResults: number = 20): Promise<CommentAnalyticsWithReplies[]> {
38
+ const targetVideoIds: string[] = [this.video_id];
39
+ const allComments: CommentAnalyticsWithReplies[] = [];
40
+
41
+ for (const vid of targetVideoIds) {
42
+ try {
43
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/commentThreads", {
44
+ params: {
45
+ part: "snippet,replies",
46
+ videoId: vid,
47
+ maxResults: Math.min(maxResults, 100),
48
+ order: "relevance"
49
+ },
50
+ headers: { Authorization: `Bearer ${this.access_token}` },
51
+ });
52
+
53
+ for (const item of response.data.items) {
54
+ const topComment = item.snippet.topLevelComment.snippet;
55
+
56
+ const replies: CommentReply[] = (item.replies?.comments || []).map((rep: any) => ({
57
+ replyId: rep.id,
58
+ author: rep.snippet.authorDisplayName,
59
+ text: rep.snippet.textDisplay,
60
+ likeCount: rep.snippet.likeCount,
61
+ publishedAt: rep.snippet.publishedAt
62
+ }));
63
+
64
+ allComments.push({
65
+ commentId: item.id,
66
+ videoId: vid,
67
+ author: topComment.authorDisplayName,
68
+ text: topComment.textDisplay,
69
+ likeCount: topComment.likeCount,
70
+ publishedAt: topComment.publishedAt,
71
+ replyCount: item.snippet.totalReplyCount,
72
+ replies
73
+ });
74
+ }
75
+ } catch (error) {
76
+ console.warn(`Failed to fetch comments for video ${vid}:`, error);
77
+ continue;
78
+ }
79
+ }
80
+
81
+ return allComments;
82
+ }
83
+
84
+
85
+
86
+
87
+ }
@@ -0,0 +1,65 @@
1
+ import { google, youtube_v3 } from "googleapis";
2
+ import axios from "axios";
3
+ import { Readable } from "stream";
4
+
5
+ export interface VideoMetadata {
6
+ title: string;
7
+ description?: string;
8
+ tags?: string[];
9
+ categoryId?: string;
10
+ privacyStatus?: "public" | "private" | "unlisted";
11
+ madeForKids?: boolean;
12
+ license?: "youtube" | "creativeCommon";
13
+ embeddable?: boolean;
14
+ publicStatsViewable?: boolean;
15
+ }
16
+
17
+ export class YoutubeVideoPoster {
18
+ private youtube: youtube_v3.Youtube;
19
+
20
+ constructor(private access_token: string) {
21
+ this.youtube = google.youtube({ version: "v3", auth: this.access_token });
22
+ }
23
+
24
+ /**
25
+ * Upload a video from a Cloudinary URL to YouTube
26
+ * @param videoUrl URL of the video on Cloudinary
27
+ * @param metadata Video metadata
28
+ */
29
+ public async uploadFromCloudUrl(videoUrl: string, metadata: VideoMetadata): Promise<youtube_v3.Schema$Video> {
30
+ try {
31
+ // Fetch video as stream from Cloudinary
32
+ const response = await axios.get(videoUrl, { responseType: "stream" });
33
+ const videoStream = response.data as Readable;
34
+
35
+ // Prepare metadata payload
36
+ const metadataPayload: youtube_v3.Params$Resource$Videos$Insert = {
37
+ part: ["snippet", "status"],
38
+ requestBody: {
39
+ snippet: {
40
+ title: metadata.title.substring(0, 100),
41
+ description: (metadata.description || "").substring(0, 5000),
42
+ tags: (metadata.tags || []).slice(0, 5),
43
+ categoryId: metadata.categoryId || "22"
44
+ },
45
+ status: {
46
+ privacyStatus: metadata.privacyStatus || "private",
47
+ embeddable: metadata.embeddable !== false,
48
+ publicStatsViewable: metadata.publicStatsViewable !== false,
49
+ license: metadata.license || "youtube",
50
+ selfDeclaredMadeForKids: metadata.madeForKids || false
51
+ }
52
+ },
53
+ media: { body: videoStream }
54
+ };
55
+
56
+ // Upload video
57
+ const res = await this.youtube.videos.insert(metadataPayload);
58
+ return res.data;
59
+
60
+ } catch (error: any) {
61
+ console.error("YouTube upload failed:", error.response?.data || error.message);
62
+ throw new Error(`YouTube upload failed: ${error.response?.data?.error?.message || error.message}`);
63
+ }
64
+ }
65
+ }