my-youtube-api 1.0.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.
@@ -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
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "my-youtube-api",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "scripts": {
6
+ "prepublishOnly": "npm run build",
7
+ "build": "tsc",
8
+ "test": "ts-node src/test.ts"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "types": "dist/index.d.ts",
15
+ "type": "module",
16
+ "dependencies": {
17
+ "axios": "^1.12.2",
18
+ "googleapis": "^160.0.0",
19
+ "ts-node": "^10.9.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/dotenv": "^6.1.1",
23
+ "@types/node": "^24.5.1",
24
+ "typescript": "^5.9.2"
25
+ }
26
+ }
@@ -0,0 +1,38 @@
1
+ export const YOUTUBE_BASE_URL = "https://www.googleapis.com";
2
+
3
+
4
+ export const RUPLOAD_URL = 'https://rupload.facebook.com';
5
+
6
+
7
+ export enum FacebookErrorCode {
8
+ NO_ACCOUNT = 'NO_ACCOUNT',
9
+ ACCESS_TOKEN_EXPIRED = 'ACCESS_TOKEN_EXPIRED',
10
+ FAILED_GET_PAGES = 'FAILED_GET_PAGES',
11
+ ACCESS_TOKEN_INVALID = 'ACCESS_TOKEN_INVALID',
12
+ PAGE_NOT_FOUND = 'PAGE_NOT_FOUND',
13
+ PAGE_ACCESS_TOKEN_EXPIRE = 'PAGE_ACCESS_TOKEN_EXPIRE',
14
+ FAILED_FETCH_PAGES_FROM_API = 'FAILED_FETCH_PAGES_FROM_API'
15
+ }
16
+
17
+ const errorMessages: Record<FacebookErrorCode, string> = {
18
+ [FacebookErrorCode.NO_ACCOUNT]: 'Facebook account not connected',
19
+ [FacebookErrorCode.ACCESS_TOKEN_EXPIRED]: 'Facebook access token has expired',
20
+ [FacebookErrorCode.FAILED_GET_PAGES]: 'Failed to fetch Facebook pages',
21
+ [FacebookErrorCode.ACCESS_TOKEN_INVALID]: 'Facebook access token is invalid',
22
+ [FacebookErrorCode.PAGE_NOT_FOUND]: 'Facebook page not found',
23
+ [FacebookErrorCode.PAGE_ACCESS_TOKEN_EXPIRE]: 'Facebook page access token has expired',
24
+ [FacebookErrorCode.FAILED_FETCH_PAGES_FROM_API]: 'Failed to fetch pages from Facebook API'
25
+ };
26
+
27
+
28
+ export class FacebookError extends Error {
29
+ constructor(
30
+ public code: FacebookErrorCode,
31
+ public originalError?: any
32
+ ) {
33
+ super(errorMessages[code]);
34
+ this.name = 'FacebookError';
35
+ }
36
+ }
37
+
38
+
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { YoutubeAccountHandler } from "./youtube-handlers/youtube-account-handler";
2
+ import { YoutubeChannelHandler } from "./youtube-handlers/youtube-channel-handler";
3
+ import { YoutubeVideoHandler } from "./youtube-handlers/youtube-video-handler";
4
+ import { YoutubeVideoPoster } from "./youtube-handlers/youtube-video-poster";
5
+
6
+ /*
7
+ npm run build
8
+ npm pack
9
+
10
+ in nextjs go to this package/"my-youtube-api-1.0.0.tgz"
11
+ */
12
+
13
+ import { isValidAccessToken, fetchNewAccessToken } from './utils'
14
+
15
+ export {
16
+ YoutubeAccountHandler,
17
+ YoutubeChannelHandler,
18
+ YoutubeVideoHandler,
19
+ YoutubeVideoPoster,
20
+ isValidAccessToken,
21
+ fetchNewAccessToken
22
+ };
package/src/test.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { YoutubeChannelHandler } from "./youtube-handlers/youtube-channel-handler";
2
+ import { YoutubeAnalyticsHandler } from "./youtube-handlers/youtube-channel-handler2";
3
+ import { YoutubeVideoHandler } from "./youtube-handlers/youtube-video-handler";
4
+
5
+ const access_token="ya29.a0AQQ_BDSZ2rCrjvxUbkkVtbVtWdstheMCiFoBLDwCLcOkcPVdej3wmFbraxVoVuhTNd875VIOA_hjWj7bPom8AJbRzfDL3roL3OIaLVP2egffiMo0lPScLLK4RuwA16JmdVeJadeNiMofUtfhqdwjz9JRHQ2DBhUYRwzwCAZzPTX9kUU4d4v43TePdZ9Pm1AoCNnGLEQaCgYKAQYSARUSFQHGX2MiEjdmyiMSsAh6MyI_w17C8Q0206";
6
+ const channel_id="UCfRRVg_MwvlFWqsvBUXrXcw";
7
+ const video_id="J-Rn69UHm78";
8
+
9
+ /*
10
+ "main": "dist/index.js",
11
+ for nextjs:
12
+ in package json: "type": "module", "types": "dist/index.d.ts",
13
+ in tsconfig.json: "module": "ESNext",
14
+ for testing here:
15
+ in package json: remove "type": "module", and "type": "module"
16
+ in tsconfig.json: "module": "commonjs",
17
+ */
18
+
19
+ /*
20
+
21
+ npm run build
22
+ npm pack
23
+
24
+ in nextjs go to this package/"my-facebook-api-1.0.0.tgz"
25
+
26
+ */
27
+
28
+
29
+ const youtubeChannelHandler = new YoutubeChannelHandler(access_token,channel_id);
30
+
31
+ /* youtubeChannelHandler.getChannelStats().then((res)=>{
32
+ console.log('================================================');
33
+ console.log(res,'wwwwwwwwwwwwwww');
34
+
35
+ })
36
+ */
37
+ const youtubeAnalyticsHandlers = new YoutubeAnalyticsHandler(access_token,channel_id);
38
+
39
+ /* youtubeAnalyticsHandlers.fetchVideosList().then((res)=>{
40
+ console.log(res,'wwwwwwwwwwwwwwwwwwwwwwwwww');
41
+
42
+ }) */
43
+
44
+
45
+ const youvid = new YoutubeVideoHandler(access_token,video_id);
46
+
47
+ /* youvid.getYouTubeComments().then((res)=>{
48
+ console.log(res,res[0].replies,'eeeeeeeeeeeeeeeeeeeeeeeeeeeee');
49
+
50
+ }) */
package/src/utils.ts ADDED
@@ -0,0 +1,85 @@
1
+ import axios from "axios";
2
+ interface AccessTokenType {
3
+ access_token: string;
4
+ refresh_token: string;
5
+ expires_in: number;
6
+ refresh_token_expires_in: number;
7
+ expires_at: number;
8
+ }
9
+ /**
10
+ * Checks whether a Google access token is still valid.
11
+ * @param accessToken - The access token to verify.
12
+ * @returns true if valid, false otherwise.
13
+ */
14
+ export async function isValidAccessToken(accessToken: string | null): Promise<boolean> {
15
+ if (!accessToken) return false;
16
+
17
+ try {
18
+ const response = await axios.get("https://www.googleapis.com/oauth2/v1/tokeninfo", {
19
+ params: { access_token: accessToken },
20
+ });
21
+
22
+ // The tokeninfo endpoint returns an error if the token is invalid,
23
+ // so if we get here, it's valid.
24
+ return response.status === 200;
25
+ } catch (error) {
26
+ return false; // expired or invalid token
27
+ }
28
+ }
29
+
30
+
31
+
32
+ /**
33
+ * Refreshes an expired access token using a valid refresh token
34
+ * @param valid_refresh_token - The refresh token obtained during initial OAuth2 authentication
35
+ * @returns Promise resolving to AccessTokenType object containing new tokens and expiration information
36
+ * @throws {AxiosError} If the token refresh request fails (invalid refresh token, client credentials, etc.)
37
+ *
38
+ * @remarks
39
+ * All time-related values are returned in seconds:
40
+ * - expires_in: Lifetime of the access token in seconds (typically 3600)
41
+ * - expires_at: Unix timestamp in seconds when the access token will expire
42
+ * - refresh_token_expires_in: Lifetime of the refresh token in seconds (if provided by Google)
43
+ */
44
+ export async function fetchNewAccessToken(
45
+ valid_refresh_token: string,
46
+ google_client_id: string,
47
+ google_client_secret: string
48
+ ): Promise<AccessTokenType> {
49
+ try {
50
+ const params = new URLSearchParams({
51
+ client_id: google_client_id,
52
+ client_secret: google_client_secret,
53
+ grant_type: "refresh_token",
54
+ refresh_token: valid_refresh_token,
55
+ });
56
+
57
+ const response = await axios.post(
58
+ "https://oauth2.googleapis.com/token",
59
+ params.toString(),
60
+ { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
61
+ );
62
+
63
+ const data = response.data;
64
+ const access_token = data.access_token;
65
+ const expires_in = data.expires_in;
66
+ const refresh_token = data.refresh_token ?? valid_refresh_token;
67
+ const refresh_token_expires_in = data.refresh_token_expires_in;
68
+ const expires_at = Math.floor(Date.now() / 1000) + expires_in;
69
+
70
+ return {
71
+ access_token,
72
+ expires_at,
73
+ refresh_token_expires_in,
74
+ expires_in,
75
+ refresh_token,
76
+ };
77
+ } catch (error: any) {
78
+ console.error(
79
+ "❌ Failed to refresh access token:",
80
+ error.response?.data || error.message
81
+ );
82
+ throw error;
83
+ }
84
+ }
85
+
@@ -0,0 +1,51 @@
1
+ import axios from "axios";
2
+ import { YOUTUBE_BASE_URL } from "../error-utils";
3
+
4
+ interface Channel {
5
+ channelId: string;
6
+ title: string;
7
+ description: string;
8
+ thumbnail: string;
9
+ }
10
+
11
+
12
+
13
+ export class YoutubeAccountHandler {
14
+ private account_access_token: string; //user_access_token
15
+
16
+ /**
17
+ * Creates a new YoutubeAccountHandler instance
18
+ * @param account_access_token - The current access token for YouTube API authentication
19
+ */
20
+ constructor(account_access_token: string) {
21
+ this.account_access_token = account_access_token;
22
+ }
23
+
24
+ /**
25
+ * Fetches the list of YouTube channels associated with the authenticated account
26
+ * @returns Promise resolving to an array of Channel objects containing channel details
27
+ * @throws {AxiosError} If the API request fails due to authentication or other errors
28
+ */
29
+ async fetchChannels(): Promise<Channel[]> {
30
+ const url = `${YOUTUBE_BASE_URL}/youtube/v3/channels`;
31
+ const params: Record<string, any> = {
32
+ part: "snippet,contentDetails,statistics",
33
+ mine: true,
34
+ };
35
+ const access_token = this.account_access_token;
36
+ const headers = { Authorization: `Bearer ${access_token}` }
37
+
38
+ const res = await axios.get(url, { params, headers });
39
+
40
+ const channels = res.data.items.map((ch: any) => ({
41
+ channelId: ch.id,
42
+ title: ch.snippet.title,
43
+ description: ch.snippet.description,
44
+ thumbnail: ch.snippet.thumbnails?.default?.url,
45
+ }))
46
+
47
+ return channels;
48
+ }
49
+
50
+
51
+ }