my-youtube-api 1.0.3 → 1.0.5
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/dist/youtube-handlers/youtube-video-poster.d.ts +3 -39
- package/dist/youtube-handlers/youtube-video-poster.js +10 -291
- package/dist/youtube-handlers2/youtube-account-handler.d.ts +21 -0
- package/dist/youtube-handlers2/youtube-account-handler.js +33 -0
- package/dist/youtube-handlers2/youtube-channel-handler.d.ts +65 -0
- package/dist/youtube-handlers2/youtube-channel-handler.js +193 -0
- package/dist/youtube-handlers2/youtube-channel-handler2.d.ts +65 -0
- package/dist/youtube-handlers2/youtube-channel-handler2.js +177 -0
- package/dist/youtube-handlers2/youtube-video-handler.d.ts +30 -0
- package/dist/youtube-handlers2/youtube-video-handler.js +55 -0
- package/dist/youtube-handlers2/youtube-video-poster.d.ts +65 -0
- package/dist/youtube-handlers2/youtube-video-poster.js +367 -0
- package/package.json +1 -1
- package/src/youtube-handlers/youtube-video-poster.ts +13 -362
- package/src/youtube-handlers2/youtube-video-poster.ts +456 -0
- package/src/youtube-handlers copy/youtube-video-poster.ts +0 -65
- /package/src/{youtube-handlers copy → youtube-handlers2}/youtube-account-handler.ts +0 -0
- /package/src/{youtube-handlers copy → youtube-handlers2}/youtube-channel-handler.ts +0 -0
- /package/src/{youtube-handlers copy → youtube-handlers2}/youtube-channel-handler2.ts +0 -0
- /package/src/{youtube-handlers copy → youtube-handlers2}/youtube-video-handler.ts +0 -0
|
@@ -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,65 @@
|
|
|
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
|
+
playlistId?: string;
|
|
13
|
+
notifySubscribers?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface YouTubeUploadResult {
|
|
16
|
+
id: string;
|
|
17
|
+
status: string;
|
|
18
|
+
url: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
thumbnailUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class YoutubeVideoPoster {
|
|
24
|
+
private access_token;
|
|
25
|
+
private youtube;
|
|
26
|
+
constructor(access_token: string);
|
|
27
|
+
/**
|
|
28
|
+
* Handle YouTube API errors and throw appropriate custom errors
|
|
29
|
+
*/
|
|
30
|
+
private handleYouTubeError;
|
|
31
|
+
/**
|
|
32
|
+
* Validate video metadata
|
|
33
|
+
*/
|
|
34
|
+
private validateMetadata;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch video stream from URL with proper error handling
|
|
37
|
+
*/
|
|
38
|
+
private fetchVideoStream;
|
|
39
|
+
/**
|
|
40
|
+
* Wait for video processing to complete
|
|
41
|
+
*/
|
|
42
|
+
private waitForVideoProcessing;
|
|
43
|
+
/**
|
|
44
|
+
* Add video to playlist if specified
|
|
45
|
+
*/
|
|
46
|
+
private addToPlaylist;
|
|
47
|
+
/**
|
|
48
|
+
* Upload a video from a Cloudinary URL to YouTube
|
|
49
|
+
*/
|
|
50
|
+
uploadFromCloudUrl2(videoUrl: string, metadata: VideoMetadata): Promise<YouTubeUploadResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Upload a video from a Cloudinary URL to YouTube
|
|
53
|
+
* @param videoUrl URL of the video on Cloudinary
|
|
54
|
+
* @param metadata Video metadata
|
|
55
|
+
*/
|
|
56
|
+
uploadFromCloudUrl(videoUrl: string, metadata: VideoMetadata): Promise<youtube_v3.Schema$Video>;
|
|
57
|
+
/**
|
|
58
|
+
* Get video details
|
|
59
|
+
*/
|
|
60
|
+
getVideoDetails(videoId: string): Promise<youtube_v3.Schema$Video>;
|
|
61
|
+
/**
|
|
62
|
+
* Delete a video
|
|
63
|
+
*/
|
|
64
|
+
deleteVideo(videoId: string): Promise<void>;
|
|
65
|
+
}
|