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.
- package/dist/error-utils.d.ts +16 -0
- package/dist/error-utils.js +29 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +12 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +40 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +61 -0
- package/dist/youtube-handlers/youtube-account-handler.d.ts +21 -0
- package/dist/youtube-handlers/youtube-account-handler.js +33 -0
- package/dist/youtube-handlers/youtube-channel-handler.d.ts +65 -0
- package/dist/youtube-handlers/youtube-channel-handler.js +193 -0
- package/dist/youtube-handlers/youtube-channel-handler2.d.ts +65 -0
- package/dist/youtube-handlers/youtube-channel-handler2.js +177 -0
- package/dist/youtube-handlers/youtube-video-handler.d.ts +30 -0
- package/dist/youtube-handlers/youtube-video-handler.js +55 -0
- package/dist/youtube-handlers/youtube-video-poster.d.ts +23 -0
- package/dist/youtube-handlers/youtube-video-poster.js +47 -0
- package/my-youtube-api-1.0.0.tgz +0 -0
- package/package.json +26 -0
- package/src/error-utils.ts +38 -0
- package/src/index.ts +22 -0
- package/src/test.ts +50 -0
- package/src/utils.ts +85 -0
- package/src/youtube-handlers/youtube-account-handler.ts +51 -0
- package/src/youtube-handlers/youtube-channel-handler.ts +275 -0
- package/src/youtube-handlers/youtube-channel-handler2.ts +249 -0
- package/src/youtube-handlers/youtube-video-handler.ts +87 -0
- package/src/youtube-handlers/youtube-video-poster.ts +65 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const YOUTUBE_BASE_URL = "https://www.googleapis.com";
|
|
2
|
+
export declare const RUPLOAD_URL = "https://rupload.facebook.com";
|
|
3
|
+
export declare enum FacebookErrorCode {
|
|
4
|
+
NO_ACCOUNT = "NO_ACCOUNT",
|
|
5
|
+
ACCESS_TOKEN_EXPIRED = "ACCESS_TOKEN_EXPIRED",
|
|
6
|
+
FAILED_GET_PAGES = "FAILED_GET_PAGES",
|
|
7
|
+
ACCESS_TOKEN_INVALID = "ACCESS_TOKEN_INVALID",
|
|
8
|
+
PAGE_NOT_FOUND = "PAGE_NOT_FOUND",
|
|
9
|
+
PAGE_ACCESS_TOKEN_EXPIRE = "PAGE_ACCESS_TOKEN_EXPIRE",
|
|
10
|
+
FAILED_FETCH_PAGES_FROM_API = "FAILED_FETCH_PAGES_FROM_API"
|
|
11
|
+
}
|
|
12
|
+
export declare class FacebookError extends Error {
|
|
13
|
+
code: FacebookErrorCode;
|
|
14
|
+
originalError?: any;
|
|
15
|
+
constructor(code: FacebookErrorCode, originalError?: any);
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const YOUTUBE_BASE_URL = "https://www.googleapis.com";
|
|
2
|
+
export const RUPLOAD_URL = 'https://rupload.facebook.com';
|
|
3
|
+
export var FacebookErrorCode;
|
|
4
|
+
(function (FacebookErrorCode) {
|
|
5
|
+
FacebookErrorCode["NO_ACCOUNT"] = "NO_ACCOUNT";
|
|
6
|
+
FacebookErrorCode["ACCESS_TOKEN_EXPIRED"] = "ACCESS_TOKEN_EXPIRED";
|
|
7
|
+
FacebookErrorCode["FAILED_GET_PAGES"] = "FAILED_GET_PAGES";
|
|
8
|
+
FacebookErrorCode["ACCESS_TOKEN_INVALID"] = "ACCESS_TOKEN_INVALID";
|
|
9
|
+
FacebookErrorCode["PAGE_NOT_FOUND"] = "PAGE_NOT_FOUND";
|
|
10
|
+
FacebookErrorCode["PAGE_ACCESS_TOKEN_EXPIRE"] = "PAGE_ACCESS_TOKEN_EXPIRE";
|
|
11
|
+
FacebookErrorCode["FAILED_FETCH_PAGES_FROM_API"] = "FAILED_FETCH_PAGES_FROM_API";
|
|
12
|
+
})(FacebookErrorCode || (FacebookErrorCode = {}));
|
|
13
|
+
const errorMessages = {
|
|
14
|
+
[FacebookErrorCode.NO_ACCOUNT]: 'Facebook account not connected',
|
|
15
|
+
[FacebookErrorCode.ACCESS_TOKEN_EXPIRED]: 'Facebook access token has expired',
|
|
16
|
+
[FacebookErrorCode.FAILED_GET_PAGES]: 'Failed to fetch Facebook pages',
|
|
17
|
+
[FacebookErrorCode.ACCESS_TOKEN_INVALID]: 'Facebook access token is invalid',
|
|
18
|
+
[FacebookErrorCode.PAGE_NOT_FOUND]: 'Facebook page not found',
|
|
19
|
+
[FacebookErrorCode.PAGE_ACCESS_TOKEN_EXPIRE]: 'Facebook page access token has expired',
|
|
20
|
+
[FacebookErrorCode.FAILED_FETCH_PAGES_FROM_API]: 'Failed to fetch pages from Facebook API'
|
|
21
|
+
};
|
|
22
|
+
export class FacebookError extends Error {
|
|
23
|
+
constructor(code, originalError) {
|
|
24
|
+
super(errorMessages[code]);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.originalError = originalError;
|
|
27
|
+
this.name = 'FacebookError';
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
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
|
+
import { isValidAccessToken, fetchNewAccessToken } from './utils';
|
|
6
|
+
export { YoutubeAccountHandler, YoutubeChannelHandler, YoutubeVideoHandler, YoutubeVideoPoster, isValidAccessToken, fetchNewAccessToken };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
npm run build
|
|
7
|
+
npm pack
|
|
8
|
+
|
|
9
|
+
in nextjs go to this package/"my-youtube-api-1.0.0.tgz"
|
|
10
|
+
*/
|
|
11
|
+
import { isValidAccessToken, fetchNewAccessToken } from './utils';
|
|
12
|
+
export { YoutubeAccountHandler, YoutubeChannelHandler, YoutubeVideoHandler, YoutubeVideoPoster, isValidAccessToken, fetchNewAccessToken };
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
const access_token = "ya29.a0AQQ_BDSZ2rCrjvxUbkkVtbVtWdstheMCiFoBLDwCLcOkcPVdej3wmFbraxVoVuhTNd875VIOA_hjWj7bPom8AJbRzfDL3roL3OIaLVP2egffiMo0lPScLLK4RuwA16JmdVeJadeNiMofUtfhqdwjz9JRHQ2DBhUYRwzwCAZzPTX9kUU4d4v43TePdZ9Pm1AoCNnGLEQaCgYKAQYSARUSFQHGX2MiEjdmyiMSsAh6MyI_w17C8Q0206";
|
|
5
|
+
const channel_id = "UCfRRVg_MwvlFWqsvBUXrXcw";
|
|
6
|
+
const video_id = "J-Rn69UHm78";
|
|
7
|
+
/*
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
for nextjs:
|
|
10
|
+
in package json: "type": "module", "types": "dist/index.d.ts",
|
|
11
|
+
in tsconfig.json: "module": "ESNext",
|
|
12
|
+
for testing here:
|
|
13
|
+
in package json: remove "type": "module", and "type": "module"
|
|
14
|
+
in tsconfig.json: "module": "commonjs",
|
|
15
|
+
*/
|
|
16
|
+
/*
|
|
17
|
+
|
|
18
|
+
npm run build
|
|
19
|
+
npm pack
|
|
20
|
+
|
|
21
|
+
in nextjs go to this package/"my-facebook-api-1.0.0.tgz"
|
|
22
|
+
|
|
23
|
+
*/
|
|
24
|
+
const youtubeChannelHandler = new YoutubeChannelHandler(access_token, channel_id);
|
|
25
|
+
/* youtubeChannelHandler.getChannelStats().then((res)=>{
|
|
26
|
+
console.log('================================================');
|
|
27
|
+
console.log(res,'wwwwwwwwwwwwwww');
|
|
28
|
+
|
|
29
|
+
})
|
|
30
|
+
*/
|
|
31
|
+
const youtubeAnalyticsHandlers = new YoutubeAnalyticsHandler(access_token, channel_id);
|
|
32
|
+
/* youtubeAnalyticsHandlers.fetchVideosList().then((res)=>{
|
|
33
|
+
console.log(res,'wwwwwwwwwwwwwwwwwwwwwwwwww');
|
|
34
|
+
|
|
35
|
+
}) */
|
|
36
|
+
const youvid = new YoutubeVideoHandler(access_token, video_id);
|
|
37
|
+
/* youvid.getYouTubeComments().then((res)=>{
|
|
38
|
+
console.log(res,res[0].replies,'eeeeeeeeeeeeeeeeeeeeeeeeeeeee');
|
|
39
|
+
|
|
40
|
+
}) */
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface AccessTokenType {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
expires_in: number;
|
|
5
|
+
refresh_token_expires_in: number;
|
|
6
|
+
expires_at: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether a Google access token is still valid.
|
|
10
|
+
* @param accessToken - The access token to verify.
|
|
11
|
+
* @returns true if valid, false otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isValidAccessToken(accessToken: string | null): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Refreshes an expired access token using a valid refresh token
|
|
16
|
+
* @param valid_refresh_token - The refresh token obtained during initial OAuth2 authentication
|
|
17
|
+
* @returns Promise resolving to AccessTokenType object containing new tokens and expiration information
|
|
18
|
+
* @throws {AxiosError} If the token refresh request fails (invalid refresh token, client credentials, etc.)
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* All time-related values are returned in seconds:
|
|
22
|
+
* - expires_in: Lifetime of the access token in seconds (typically 3600)
|
|
23
|
+
* - expires_at: Unix timestamp in seconds when the access token will expire
|
|
24
|
+
* - refresh_token_expires_in: Lifetime of the refresh token in seconds (if provided by Google)
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchNewAccessToken(valid_refresh_token: string, google_client_id: string, google_client_secret: string): Promise<AccessTokenType>;
|
|
27
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether a Google access token is still valid.
|
|
4
|
+
* @param accessToken - The access token to verify.
|
|
5
|
+
* @returns true if valid, false otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export async function isValidAccessToken(accessToken) {
|
|
8
|
+
if (!accessToken)
|
|
9
|
+
return false;
|
|
10
|
+
try {
|
|
11
|
+
const response = await axios.get("https://www.googleapis.com/oauth2/v1/tokeninfo", {
|
|
12
|
+
params: { access_token: accessToken },
|
|
13
|
+
});
|
|
14
|
+
// The tokeninfo endpoint returns an error if the token is invalid,
|
|
15
|
+
// so if we get here, it's valid.
|
|
16
|
+
return response.status === 200;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return false; // expired or invalid token
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Refreshes an expired access token using a valid refresh token
|
|
24
|
+
* @param valid_refresh_token - The refresh token obtained during initial OAuth2 authentication
|
|
25
|
+
* @returns Promise resolving to AccessTokenType object containing new tokens and expiration information
|
|
26
|
+
* @throws {AxiosError} If the token refresh request fails (invalid refresh token, client credentials, etc.)
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* All time-related values are returned in seconds:
|
|
30
|
+
* - expires_in: Lifetime of the access token in seconds (typically 3600)
|
|
31
|
+
* - expires_at: Unix timestamp in seconds when the access token will expire
|
|
32
|
+
* - refresh_token_expires_in: Lifetime of the refresh token in seconds (if provided by Google)
|
|
33
|
+
*/
|
|
34
|
+
export async function fetchNewAccessToken(valid_refresh_token, google_client_id, google_client_secret) {
|
|
35
|
+
try {
|
|
36
|
+
const params = new URLSearchParams({
|
|
37
|
+
client_id: google_client_id,
|
|
38
|
+
client_secret: google_client_secret,
|
|
39
|
+
grant_type: "refresh_token",
|
|
40
|
+
refresh_token: valid_refresh_token,
|
|
41
|
+
});
|
|
42
|
+
const response = await axios.post("https://oauth2.googleapis.com/token", params.toString(), { headers: { "Content-Type": "application/x-www-form-urlencoded" } });
|
|
43
|
+
const data = response.data;
|
|
44
|
+
const access_token = data.access_token;
|
|
45
|
+
const expires_in = data.expires_in;
|
|
46
|
+
const refresh_token = data.refresh_token ?? valid_refresh_token;
|
|
47
|
+
const refresh_token_expires_in = data.refresh_token_expires_in;
|
|
48
|
+
const expires_at = Math.floor(Date.now() / 1000) + expires_in;
|
|
49
|
+
return {
|
|
50
|
+
access_token,
|
|
51
|
+
expires_at,
|
|
52
|
+
refresh_token_expires_in,
|
|
53
|
+
expires_in,
|
|
54
|
+
refresh_token,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error("❌ Failed to refresh access token:", error.response?.data || error.message);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface Channel {
|
|
2
|
+
channelId: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
thumbnail: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class YoutubeAccountHandler {
|
|
8
|
+
private account_access_token;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new YoutubeAccountHandler instance
|
|
11
|
+
* @param account_access_token - The current access token for YouTube API authentication
|
|
12
|
+
*/
|
|
13
|
+
constructor(account_access_token: string);
|
|
14
|
+
/**
|
|
15
|
+
* Fetches the list of YouTube channels associated with the authenticated account
|
|
16
|
+
* @returns Promise resolving to an array of Channel objects containing channel details
|
|
17
|
+
* @throws {AxiosError} If the API request fails due to authentication or other errors
|
|
18
|
+
*/
|
|
19
|
+
fetchChannels(): Promise<Channel[]>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { YOUTUBE_BASE_URL } from "../error-utils";
|
|
3
|
+
export class YoutubeAccountHandler {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new YoutubeAccountHandler instance
|
|
6
|
+
* @param account_access_token - The current access token for YouTube API authentication
|
|
7
|
+
*/
|
|
8
|
+
constructor(account_access_token) {
|
|
9
|
+
this.account_access_token = account_access_token;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Fetches the list of YouTube channels associated with the authenticated account
|
|
13
|
+
* @returns Promise resolving to an array of Channel objects containing channel details
|
|
14
|
+
* @throws {AxiosError} If the API request fails due to authentication or other errors
|
|
15
|
+
*/
|
|
16
|
+
async fetchChannels() {
|
|
17
|
+
const url = `${YOUTUBE_BASE_URL}/youtube/v3/channels`;
|
|
18
|
+
const params = {
|
|
19
|
+
part: "snippet,contentDetails,statistics",
|
|
20
|
+
mine: true,
|
|
21
|
+
};
|
|
22
|
+
const access_token = this.account_access_token;
|
|
23
|
+
const headers = { Authorization: `Bearer ${access_token}` };
|
|
24
|
+
const res = await axios.get(url, { params, headers });
|
|
25
|
+
const channels = res.data.items.map((ch) => ({
|
|
26
|
+
channelId: ch.id,
|
|
27
|
+
title: ch.snippet.title,
|
|
28
|
+
description: ch.snippet.description,
|
|
29
|
+
thumbnail: ch.snippet.thumbnails?.default?.url,
|
|
30
|
+
}));
|
|
31
|
+
return channels;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
interface VideoAnalytics {
|
|
2
|
+
videoId: string;
|
|
3
|
+
title: string;
|
|
4
|
+
views: number;
|
|
5
|
+
likes: number;
|
|
6
|
+
comments: number;
|
|
7
|
+
publishedAt: string;
|
|
8
|
+
thumbnail: string;
|
|
9
|
+
}
|
|
10
|
+
interface ChannelAnalytics {
|
|
11
|
+
channelId: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
thumbnail: string;
|
|
15
|
+
subscriberCount: number;
|
|
16
|
+
viewCount: number;
|
|
17
|
+
videoCount: number;
|
|
18
|
+
hiddenSubscriberCount: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface AudienceAnalytics {
|
|
21
|
+
views: number;
|
|
22
|
+
estimatedMinutesWatched: number;
|
|
23
|
+
averageViewDuration: number;
|
|
24
|
+
subscribersGained: number;
|
|
25
|
+
subscribersLost: number;
|
|
26
|
+
}
|
|
27
|
+
interface VideoAnalytics {
|
|
28
|
+
videoId: string;
|
|
29
|
+
title: string;
|
|
30
|
+
views: number;
|
|
31
|
+
likes: number;
|
|
32
|
+
comments: number;
|
|
33
|
+
publishedAt: string;
|
|
34
|
+
thumbneil?: string;
|
|
35
|
+
videoType: "short" | "regular";
|
|
36
|
+
}
|
|
37
|
+
interface ChannelRevenue {
|
|
38
|
+
estimatedRevenue: number;
|
|
39
|
+
currency: string;
|
|
40
|
+
startDate: string;
|
|
41
|
+
endDate: string;
|
|
42
|
+
}
|
|
43
|
+
export declare class YoutubeChannelHandler {
|
|
44
|
+
private access_token;
|
|
45
|
+
private channel_id;
|
|
46
|
+
constructor(access_token: string, channel_id: string);
|
|
47
|
+
/**
|
|
48
|
+
* Fetches basic channel statistics and information
|
|
49
|
+
* @returns Channel analytics including subscriber count, view count, and video count
|
|
50
|
+
*/
|
|
51
|
+
fetchChannelAnalytics(): Promise<ChannelAnalytics>;
|
|
52
|
+
/**
|
|
53
|
+
* Fetches analytics for all videos in the channel
|
|
54
|
+
* @param maxResults Maximum number of videos to fetch (default: 50, max: 50)
|
|
55
|
+
* @returns Array of video analytics including views, likes, and comments
|
|
56
|
+
*/
|
|
57
|
+
fetchVideos(maxResults?: number): Promise<VideoAnalytics[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Fetches audience analytics and engagement metrics for the channel
|
|
60
|
+
* @returns Audience analytics including view duration and subscriber changes
|
|
61
|
+
*/
|
|
62
|
+
fetchAudienceAnalytics(): Promise<AudienceAnalytics>;
|
|
63
|
+
fetchChannelRevenue(startDate?: string, endDate?: string): Promise<ChannelRevenue>;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -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 {};
|