my-youtube-api 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ import axios from "axios";
2
+ /*
3
+ . Enable YouTube Analytics API
4
+
5
+ Go to your Google Cloud Console → APIs & Services → Library → Search for and enable:
6
+
7
+ YouTube Analytics API
8
+
9
+ YouTube Reporting API
10
+
11
+ for report and analytics
12
+ */
13
+ export class YoutubeChannelHandler {
14
+ constructor(access_token, channel_id) {
15
+ this.access_token = access_token;
16
+ this.channel_id = channel_id;
17
+ }
18
+ /**
19
+ * Fetches basic channel statistics and information
20
+ * @returns Channel analytics including subscriber count, view count, and video count
21
+ */
22
+ async fetchChannelAnalytics() {
23
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
24
+ params: {
25
+ part: "snippet,statistics,contentDetails",
26
+ mine: true // This gets the authenticated user's channel
27
+ },
28
+ headers: { Authorization: `Bearer ${this.access_token}` },
29
+ });
30
+ const channel = response.data.items[0];
31
+ return {
32
+ channelId: channel.id,
33
+ title: channel.snippet.title,
34
+ description: channel.snippet.description,
35
+ thumbnail: channel.snippet.thumbnails?.high?.url,
36
+ subscriberCount: parseInt(channel.statistics.subscriberCount || '0'),
37
+ viewCount: parseInt(channel.statistics.viewCount || '0'),
38
+ videoCount: parseInt(channel.statistics.videoCount || '0'),
39
+ hiddenSubscriberCount: channel.statistics.hiddenSubscriberCount || false
40
+ };
41
+ }
42
+ /**
43
+ * Fetches analytics for all videos in the channel
44
+ * @param maxResults Maximum number of videos to fetch (default: 50, max: 50)
45
+ * @returns Array of video analytics including views, likes, and comments
46
+ */
47
+ async fetchVideos(maxResults = 50) {
48
+ // 1️⃣ Get uploads playlist ID
49
+ const channelRes = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
50
+ params: { part: "contentDetails", id: this.channel_id },
51
+ headers: { Authorization: `Bearer ${this.access_token}` },
52
+ });
53
+ const uploadsPlaylistId = channelRes.data.items[0]?.contentDetails?.relatedPlaylists?.uploads;
54
+ if (!uploadsPlaylistId)
55
+ return [];
56
+ // 2️⃣ Fetch videos from the uploads playlist
57
+ const videosRes = await axios.get("https://www.googleapis.com/youtube/v3/playlistItems", {
58
+ params: {
59
+ part: "snippet,contentDetails",
60
+ playlistId: uploadsPlaylistId,
61
+ maxResults: Math.min(maxResults, 50),
62
+ },
63
+ headers: { Authorization: `Bearer ${this.access_token}` },
64
+ });
65
+ const videoIds = videosRes.data.items.map((v) => v.contentDetails.videoId);
66
+ if (videoIds.length === 0)
67
+ return [];
68
+ // 3️⃣ Fetch analytics & contentDetails for each video
69
+ const analyticsRes = await axios.get("https://www.googleapis.com/youtube/v3/videos", {
70
+ params: {
71
+ part: "snippet,statistics,contentDetails",
72
+ id: videoIds.join(","),
73
+ maxResults: Math.min(maxResults, 50),
74
+ },
75
+ headers: { Authorization: `Bearer ${this.access_token}` },
76
+ });
77
+ return analyticsRes.data.items.map((video) => {
78
+ // Convert ISO 8601 duration to seconds
79
+ const duration = video.contentDetails?.duration || "PT0S";
80
+ const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
81
+ const seconds = (parseInt(match?.[1] || "0") * 3600) +
82
+ (parseInt(match?.[2] || "0") * 60) +
83
+ parseInt(match?.[3] || "0");
84
+ return {
85
+ videoId: video.id,
86
+ title: video.snippet.title,
87
+ views: parseInt(video.statistics.viewCount || 0),
88
+ likes: parseInt(video.statistics.likeCount || 0),
89
+ comments: parseInt(video.statistics.commentCount || 0),
90
+ publishedAt: video.snippet.publishedAt,
91
+ thumbnail: video.snippet.thumbnails?.high?.url,
92
+ videoType: seconds < 60 ? "short" : "regular",
93
+ };
94
+ });
95
+ }
96
+ /**
97
+ * Fetches audience analytics and engagement metrics for the channel
98
+ * @returns Audience analytics including view duration and subscriber changes
99
+ */
100
+ async fetchAudienceAnalytics() {
101
+ try {
102
+ // 1️⃣ Compute proper date range (last 30 days)
103
+ const endDate = new Date();
104
+ const startDate = new Date();
105
+ startDate.setDate(endDate.getDate() - 30);
106
+ const formatDate = (date) => date.toISOString().split("T")[0]; // YYYY-MM-DD
107
+ // 2️⃣ Call YouTube Analytics API
108
+ const response = await axios.get("https://youtubeanalytics.googleapis.com/v2/reports", {
109
+ params: {
110
+ ids: `channel==${this.channel_id}`,
111
+ startDate: formatDate(startDate),
112
+ endDate: formatDate(endDate),
113
+ metrics: "views,estimatedMinutesWatched,averageViewDuration,subscribersGained,subscribersLost",
114
+ dimensions: "day",
115
+ },
116
+ headers: { Authorization: `Bearer ${this.access_token}` },
117
+ });
118
+ const rows = response.data.rows || [];
119
+ if (rows.length === 0) {
120
+ return {
121
+ views: 0,
122
+ estimatedMinutesWatched: 0,
123
+ averageViewDuration: 0,
124
+ subscribersGained: 0,
125
+ subscribersLost: 0,
126
+ };
127
+ }
128
+ // 3️⃣ Sum up all daily metrics
129
+ const totals = rows.reduce((acc, row) => ({
130
+ views: acc.views + Number(row[1] || 0),
131
+ estimatedMinutesWatched: acc.estimatedMinutesWatched + Number(row[2] || 0),
132
+ averageViewDuration: acc.averageViewDuration + Number(row[3] || 0),
133
+ subscribersGained: acc.subscribersGained + Number(row[4] || 0),
134
+ subscribersLost: acc.subscribersLost + Number(row[5] || 0),
135
+ }), {
136
+ views: 0,
137
+ estimatedMinutesWatched: 0,
138
+ averageViewDuration: 0,
139
+ subscribersGained: 0,
140
+ subscribersLost: 0,
141
+ });
142
+ // 4️⃣ Return final analytics
143
+ return {
144
+ views: totals.views,
145
+ estimatedMinutesWatched: totals.estimatedMinutesWatched,
146
+ averageViewDuration: totals.averageViewDuration / rows.length, // Average of averages
147
+ subscribersGained: totals.subscribersGained,
148
+ subscribersLost: totals.subscribersLost,
149
+ };
150
+ }
151
+ catch (err) {
152
+ console.error("Error fetching audience analytics:", err);
153
+ return {
154
+ views: 0,
155
+ estimatedMinutesWatched: 0,
156
+ averageViewDuration: 0,
157
+ subscribersGained: 0,
158
+ subscribersLost: 0,
159
+ };
160
+ }
161
+ }
162
+ async fetchChannelRevenue(startDate = "2023-01-01", endDate = new Date().toISOString().split("T")[0]) {
163
+ try {
164
+ const response = await axios.get("https://youtubeanalytics.googleapis.com/v2/reports", {
165
+ params: {
166
+ ids: `channel==${this.channel_id}`,
167
+ startDate,
168
+ endDate,
169
+ metrics: "estimatedRevenue",
170
+ dimensions: "day", // optional: can remove to get total
171
+ currency: "USD"
172
+ },
173
+ headers: {
174
+ Authorization: `Bearer ${this.access_token}`
175
+ }
176
+ });
177
+ // Sum revenue if using "day" dimension
178
+ const totalRevenue = response.data.rows
179
+ ? response.data.rows.reduce((acc, row) => acc + parseFloat(row[1] || 0), 0)
180
+ : 0;
181
+ return {
182
+ estimatedRevenue: totalRevenue,
183
+ currency: "USD",
184
+ startDate,
185
+ endDate
186
+ };
187
+ }
188
+ catch (err) {
189
+ console.error("Failed to fetch channel revenue:", err.response?.data || err.message);
190
+ return { estimatedRevenue: 0, currency: "USD", startDate, endDate };
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,65 @@
1
+ interface GrowthMetrics {
2
+ subscribers: number;
3
+ totalVideoViews: number;
4
+ estimatedRevenue: number;
5
+ videosCount: number;
6
+ period: string;
7
+ }
8
+ interface VideoPerformance {
9
+ videoId: string;
10
+ title: string;
11
+ publishedAt: string;
12
+ views: number;
13
+ watchTime: number;
14
+ avgViewDuration: number;
15
+ likes: number;
16
+ dislikes: number;
17
+ comments: number;
18
+ shares: number;
19
+ thumbnail: string;
20
+ }
21
+ interface PublishedVideosStats {
22
+ totalViews: number;
23
+ totalLikes: number;
24
+ totalDislikes: number;
25
+ totalComments: number;
26
+ totalShares: number;
27
+ totalVideos: number;
28
+ averageEngagementRate: number;
29
+ period: string;
30
+ }
31
+ export declare class YoutubeAnalyticsHandler {
32
+ private access_token;
33
+ private channel_id;
34
+ constructor(access_token: string, channel_id: string);
35
+ /**
36
+ * Fetches growth metrics for the channel over a specified period
37
+ * @param period Time period for growth analysis (7days, 30days, 90days, 365days)
38
+ * @returns Growth metrics including subscribers, views, revenue, and video count
39
+ * . Enable YouTube Analytics API
40
+
41
+ Go to your Google Cloud Console → APIs & Services → Library → Search for and enable:
42
+
43
+ YouTube Analytics API
44
+
45
+ YouTube Reporting API
46
+ */
47
+ fetchGrowthMetrics(period?: string): Promise<GrowthMetrics>;
48
+ /**
49
+ * Fetches aggregated statistics for all published videos in a period
50
+ * @param period Time period for analysis (7days, 30days, 90days)
51
+ * @returns Combined statistics for all published videos
52
+ */
53
+ fetchPublishedVideosStats(period?: string): Promise<PublishedVideosStats>;
54
+ /**
55
+ * Fetches detailed list of videos with comprehensive performance metrics
56
+ * @param maxResults Maximum number of videos to return (default: 50, max: 50)
57
+ * @returns Array of video performance metrics
58
+ */
59
+ fetchVideosList(maxResults?: number): Promise<VideoPerformance[]>;
60
+ private fetchChannelBasicStats;
61
+ private fetchVideosPublishedInPeriod;
62
+ private fetchVideosDetailedStats;
63
+ private getStartDate;
64
+ }
65
+ export {};
@@ -0,0 +1,177 @@
1
+ import axios from "axios";
2
+ export class YoutubeAnalyticsHandler {
3
+ constructor(access_token, channel_id) {
4
+ this.access_token = access_token;
5
+ this.channel_id = channel_id;
6
+ }
7
+ /**
8
+ * Fetches growth metrics for the channel over a specified period
9
+ * @param period Time period for growth analysis (7days, 30days, 90days, 365days)
10
+ * @returns Growth metrics including subscribers, views, revenue, and video count
11
+ * . Enable YouTube Analytics API
12
+
13
+ Go to your Google Cloud Console → APIs & Services → Library → Search for and enable:
14
+
15
+ YouTube Analytics API
16
+
17
+ YouTube Reporting API
18
+ */
19
+ async fetchGrowthMetrics(period = "30days") {
20
+ const endDate = "today";
21
+ const startDate = this.getStartDate(period);
22
+ // Fetch analytics data
23
+ const [analyticsResponse, channelResponse] = await Promise.all([
24
+ axios.get("https://youtubeanalytics.googleapis.com/v2/reports", {
25
+ params: {
26
+ ids: `channel==${this.channel_id}`,
27
+ startDate,
28
+ endDate,
29
+ metrics: "subscribersGained,views,estimatedRevenue",
30
+ dimensions: "day"
31
+ },
32
+ headers: { Authorization: `Bearer ${this.access_token}` },
33
+ }),
34
+ this.fetchChannelBasicStats()
35
+ ]);
36
+ const rows = analyticsResponse.data.rows || [];
37
+ const totals = rows.reduce((acc, row) => ({
38
+ subscribersGained: acc.subscribersGained + (row[1] || 0),
39
+ views: acc.views + (row[2] || 0),
40
+ estimatedRevenue: acc.estimatedRevenue + (row[3] || 0)
41
+ }), { subscribersGained: 0, views: 0, estimatedRevenue: 0 });
42
+ return {
43
+ subscribers: totals.subscribersGained,
44
+ totalVideoViews: totals.views,
45
+ estimatedRevenue: totals.estimatedRevenue,
46
+ videosCount: channelResponse.videoCount,
47
+ period: `${startDate} to ${endDate}`
48
+ };
49
+ }
50
+ /**
51
+ * Fetches aggregated statistics for all published videos in a period
52
+ * @param period Time period for analysis (7days, 30days, 90days)
53
+ * @returns Combined statistics for all published videos
54
+ */
55
+ async fetchPublishedVideosStats(period = "30days") {
56
+ const endDate = "today";
57
+ const startDate = this.getStartDate(period);
58
+ const videos = await this.fetchVideosPublishedInPeriod(startDate, endDate);
59
+ if (videos.length === 0) {
60
+ return {
61
+ totalViews: 0,
62
+ totalLikes: 0,
63
+ totalDislikes: 0,
64
+ totalComments: 0,
65
+ totalShares: 0,
66
+ totalVideos: 0,
67
+ averageEngagementRate: 0,
68
+ period: `${startDate} to ${endDate}`
69
+ };
70
+ }
71
+ const videoIds = videos.map(v => v.videoId);
72
+ const videoStats = await this.fetchVideosDetailedStats(videoIds);
73
+ const totals = videoStats.reduce((acc, video) => ({
74
+ totalViews: acc.totalViews + video.views,
75
+ totalLikes: acc.totalLikes + video.likes,
76
+ totalDislikes: acc.totalDislikes + video.dislikes,
77
+ totalComments: acc.totalComments + video.comments,
78
+ totalShares: acc.totalShares + video.shares
79
+ }), { totalViews: 0, totalLikes: 0, totalDislikes: 0, totalComments: 0, totalShares: 0 });
80
+ const averageEngagementRate = ((totals.totalLikes + totals.totalComments + totals.totalShares) / totals.totalViews) * 100;
81
+ return {
82
+ totalViews: totals.totalViews,
83
+ totalLikes: totals.totalLikes,
84
+ totalDislikes: totals.totalDislikes,
85
+ totalComments: totals.totalComments,
86
+ totalShares: totals.totalShares,
87
+ totalVideos: videos.length,
88
+ averageEngagementRate: isNaN(averageEngagementRate) ? 0 : averageEngagementRate,
89
+ period: `${startDate} to ${endDate}`
90
+ };
91
+ }
92
+ /**
93
+ * Fetches detailed list of videos with comprehensive performance metrics
94
+ * @param maxResults Maximum number of videos to return (default: 50, max: 50)
95
+ * @returns Array of video performance metrics
96
+ */
97
+ async fetchVideosList(maxResults = 50) {
98
+ // Get uploads playlist ID
99
+ const channelRes = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
100
+ params: { part: "contentDetails", id: this.channel_id },
101
+ headers: { Authorization: `Bearer ${this.access_token}` },
102
+ });
103
+ const uploadsPlaylistId = channelRes.data.items[0]?.contentDetails?.relatedPlaylists?.uploads;
104
+ if (!uploadsPlaylistId)
105
+ return [];
106
+ // Fetch videos from uploads playlist
107
+ const videosRes = await axios.get("https://www.googleapis.com/youtube/v3/playlistItems", {
108
+ params: {
109
+ part: "snippet,contentDetails",
110
+ playlistId: uploadsPlaylistId,
111
+ maxResults: Math.min(maxResults, 50)
112
+ },
113
+ headers: { Authorization: `Bearer ${this.access_token}` },
114
+ });
115
+ const videoIds = videosRes.data.items.map((v) => v.contentDetails.videoId);
116
+ if (videoIds.length === 0)
117
+ return [];
118
+ // Fetch detailed statistics for each video
119
+ return await this.fetchVideosDetailedStats(videoIds);
120
+ }
121
+ // ============ HELPER METHODS ============
122
+ async fetchChannelBasicStats() {
123
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/channels", {
124
+ params: {
125
+ part: "statistics",
126
+ id: this.channel_id
127
+ },
128
+ headers: { Authorization: `Bearer ${this.access_token}` },
129
+ });
130
+ return {
131
+ videoCount: parseInt(response.data.items[0]?.statistics?.videoCount || 0)
132
+ };
133
+ }
134
+ async fetchVideosPublishedInPeriod(startDate, endDate) {
135
+ const videos = await this.fetchVideosList(100); // Get more videos for period filtering
136
+ return videos.filter(video => {
137
+ const publishedDate = new Date(video.publishedAt).toISOString().split('T')[0];
138
+ return publishedDate >= startDate && publishedDate <= endDate;
139
+ });
140
+ }
141
+ async fetchVideosDetailedStats(videoIds) {
142
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/videos", {
143
+ params: {
144
+ part: "snippet,statistics,contentDetails",
145
+ id: videoIds.join(','),
146
+ maxResults: videoIds.length
147
+ },
148
+ headers: { Authorization: `Bearer ${this.access_token}` },
149
+ });
150
+ return response.data.items.map((video) => ({
151
+ videoId: video.id,
152
+ title: video.snippet.title,
153
+ publishedAt: video.snippet.publishedAt,
154
+ views: parseInt(video.statistics.viewCount || 0),
155
+ watchTime: parseInt(video.statistics.watchTime || 0) / 60, // Convert to minutes
156
+ avgViewDuration: parseInt(video.statistics.averageViewDuration || 0),
157
+ likes: parseInt(video.statistics.likeCount || 0),
158
+ dislikes: parseInt(video.statistics.dislikeCount || 0),
159
+ comments: parseInt(video.statistics.commentCount || 0),
160
+ shares: parseInt(video.statistics.shareCount || 0),
161
+ thumbnail: video.snippet.thumbnails?.high?.url
162
+ }));
163
+ }
164
+ getStartDate(period) {
165
+ const days = parseInt(period);
166
+ if (!isNaN(days)) {
167
+ return `${days}daysAgo`;
168
+ }
169
+ const periodMap = {
170
+ "7days": "7daysAgo",
171
+ "30days": "30daysAgo",
172
+ "90days": "90daysAgo",
173
+ "365days": "365daysAgo"
174
+ };
175
+ return periodMap[period] || "30daysAgo";
176
+ }
177
+ }
@@ -0,0 +1,30 @@
1
+ interface CommentReply {
2
+ replyId: string;
3
+ author: string;
4
+ text: string;
5
+ likeCount: number;
6
+ publishedAt: string;
7
+ }
8
+ interface CommentAnalyticsWithReplies {
9
+ commentId: string;
10
+ videoId: string;
11
+ author: string;
12
+ text: string;
13
+ likeCount: number;
14
+ publishedAt: string;
15
+ replyCount: number;
16
+ replies: CommentReply[];
17
+ }
18
+ export declare class YoutubeVideoHandler {
19
+ private video_id;
20
+ private access_token;
21
+ constructor(access_token: string, video_id: string);
22
+ /**
23
+ * Fetches comments analytics for a specific video or all channel videos
24
+ * @param videoId Optional video ID - if not provided, fetches comments from all channel videos
25
+ * @param maxResults Maximum number of comments to fetch per video (default: 20, max: 100)
26
+ * @returns Array of comment analytics including like counts and reply counts
27
+ */
28
+ fetchCommentsAnalytics(maxResults?: number): Promise<CommentAnalyticsWithReplies[]>;
29
+ }
30
+ export {};
@@ -0,0 +1,55 @@
1
+ import axios from "axios";
2
+ export class YoutubeVideoHandler {
3
+ constructor(access_token, video_id) {
4
+ this.access_token = access_token;
5
+ this.video_id = video_id;
6
+ }
7
+ /**
8
+ * Fetches comments analytics for a specific video or all channel videos
9
+ * @param videoId Optional video ID - if not provided, fetches comments from all channel videos
10
+ * @param maxResults Maximum number of comments to fetch per video (default: 20, max: 100)
11
+ * @returns Array of comment analytics including like counts and reply counts
12
+ */
13
+ async fetchCommentsAnalytics(maxResults = 20) {
14
+ const targetVideoIds = [this.video_id];
15
+ const allComments = [];
16
+ for (const vid of targetVideoIds) {
17
+ try {
18
+ const response = await axios.get("https://www.googleapis.com/youtube/v3/commentThreads", {
19
+ params: {
20
+ part: "snippet,replies",
21
+ videoId: vid,
22
+ maxResults: Math.min(maxResults, 100),
23
+ order: "relevance"
24
+ },
25
+ headers: { Authorization: `Bearer ${this.access_token}` },
26
+ });
27
+ for (const item of response.data.items) {
28
+ const topComment = item.snippet.topLevelComment.snippet;
29
+ const replies = (item.replies?.comments || []).map((rep) => ({
30
+ replyId: rep.id,
31
+ author: rep.snippet.authorDisplayName,
32
+ text: rep.snippet.textDisplay,
33
+ likeCount: rep.snippet.likeCount,
34
+ publishedAt: rep.snippet.publishedAt
35
+ }));
36
+ allComments.push({
37
+ commentId: item.id,
38
+ videoId: vid,
39
+ author: topComment.authorDisplayName,
40
+ text: topComment.textDisplay,
41
+ likeCount: topComment.likeCount,
42
+ publishedAt: topComment.publishedAt,
43
+ replyCount: item.snippet.totalReplyCount,
44
+ replies
45
+ });
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.warn(`Failed to fetch comments for video ${vid}:`, error);
50
+ continue;
51
+ }
52
+ }
53
+ return allComments;
54
+ }
55
+ }
@@ -0,0 +1,23 @@
1
+ import { youtube_v3 } from "googleapis";
2
+ export interface VideoMetadata {
3
+ title: string;
4
+ description?: string;
5
+ tags?: string[];
6
+ categoryId?: string;
7
+ privacyStatus?: "public" | "private" | "unlisted";
8
+ madeForKids?: boolean;
9
+ license?: "youtube" | "creativeCommon";
10
+ embeddable?: boolean;
11
+ publicStatsViewable?: boolean;
12
+ }
13
+ export declare class YoutubeVideoPoster {
14
+ private access_token;
15
+ private youtube;
16
+ constructor(access_token: string);
17
+ /**
18
+ * Upload a video from a Cloudinary URL to YouTube
19
+ * @param videoUrl URL of the video on Cloudinary
20
+ * @param metadata Video metadata
21
+ */
22
+ uploadFromCloudUrl(videoUrl: string, metadata: VideoMetadata): Promise<youtube_v3.Schema$Video>;
23
+ }
@@ -0,0 +1,47 @@
1
+ import { google } from "googleapis";
2
+ import axios from "axios";
3
+ export class YoutubeVideoPoster {
4
+ constructor(access_token) {
5
+ this.access_token = access_token;
6
+ this.youtube = google.youtube({ version: "v3", auth: this.access_token });
7
+ }
8
+ /**
9
+ * Upload a video from a Cloudinary URL to YouTube
10
+ * @param videoUrl URL of the video on Cloudinary
11
+ * @param metadata Video metadata
12
+ */
13
+ async uploadFromCloudUrl(videoUrl, metadata) {
14
+ try {
15
+ // Fetch video as stream from Cloudinary
16
+ const response = await axios.get(videoUrl, { responseType: "stream" });
17
+ const videoStream = response.data;
18
+ // Prepare metadata payload
19
+ const metadataPayload = {
20
+ part: ["snippet", "status"],
21
+ requestBody: {
22
+ snippet: {
23
+ title: metadata.title.substring(0, 100),
24
+ description: (metadata.description || "").substring(0, 5000),
25
+ tags: (metadata.tags || []).slice(0, 5),
26
+ categoryId: metadata.categoryId || "22"
27
+ },
28
+ status: {
29
+ privacyStatus: metadata.privacyStatus || "private",
30
+ embeddable: metadata.embeddable !== false,
31
+ publicStatsViewable: metadata.publicStatsViewable !== false,
32
+ license: metadata.license || "youtube",
33
+ selfDeclaredMadeForKids: metadata.madeForKids || false
34
+ }
35
+ },
36
+ media: { body: videoStream }
37
+ };
38
+ // Upload video
39
+ const res = await this.youtube.videos.insert(metadataPayload);
40
+ return res.data;
41
+ }
42
+ catch (error) {
43
+ console.error("YouTube upload failed:", error.response?.data || error.message);
44
+ throw new Error(`YouTube upload failed: ${error.response?.data?.error?.message || error.message}`);
45
+ }
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-youtube-api",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "prepublishOnly": "npm run build",
@@ -0,0 +1,72 @@
1
+ // src/errors/youtube-api-errors.ts
2
+ export class YouTubeApiError extends Error {
3
+ public readonly code: string;
4
+ public readonly status?: number;
5
+ public readonly details?: any;
6
+
7
+ constructor(message: string, code: string = 'YOUTUBE_API_ERROR', status?: number, details?: any) {
8
+ super(message);
9
+ this.name = 'YouTubeApiError';
10
+ this.code = code;
11
+ this.status = status;
12
+ this.details = details;
13
+
14
+ Object.setPrototypeOf(this, YouTubeApiError.prototype);
15
+ }
16
+ }
17
+
18
+ export class YouTubeAuthenticationError extends YouTubeApiError {
19
+ constructor(message: string, details?: any) {
20
+ super(message, 'AUTHENTICATION_FAILED', 401, details);
21
+ this.name = 'YouTubeAuthenticationError';
22
+ }
23
+ }
24
+
25
+ export class YouTubeQuotaError extends YouTubeApiError {
26
+ constructor(message: string, details?: any) {
27
+ super(message, 'QUOTA_EXCEEDED', 429, details);
28
+ this.name = 'YouTubeQuotaError';
29
+ }
30
+ }
31
+
32
+ export class YouTubeRateLimitError extends YouTubeApiError {
33
+ constructor(message: string, details?: any) {
34
+ super(message, 'RATE_LIMIT_EXCEEDED', 429, details);
35
+ this.name = 'YouTubeRateLimitError';
36
+ }
37
+ }
38
+
39
+ export class YouTubeMediaError extends YouTubeApiError {
40
+ constructor(message: string, details?: any) {
41
+ super(message, 'MEDIA_ERROR', 400, details);
42
+ this.name = 'YouTubeMediaError';
43
+ }
44
+ }
45
+
46
+ export class YouTubeUploadError extends YouTubeApiError {
47
+ constructor(message: string, details?: any) {
48
+ super(message, 'UPLOAD_FAILED', 500, details);
49
+ this.name = 'YouTubeUploadError';
50
+ }
51
+ }
52
+
53
+ export class YouTubeValidationError extends YouTubeApiError {
54
+ constructor(message: string, details?: any) {
55
+ super(message, 'VALIDATION_ERROR', 400, details);
56
+ this.name = 'YouTubeValidationError';
57
+ }
58
+ }
59
+
60
+ export class YouTubeProcessingError extends YouTubeApiError {
61
+ constructor(message: string, details?: any) {
62
+ super(message, 'PROCESSING_ERROR', 500, details);
63
+ this.name = 'YouTubeProcessingError';
64
+ }
65
+ }
66
+
67
+ export class YouTubeTimeoutError extends YouTubeApiError {
68
+ constructor(message: string, details?: any) {
69
+ super(message, 'PROCESSING_TIMEOUT', 408, details);
70
+ this.name = 'YouTubeTimeoutError';
71
+ }
72
+ }