@vitkuz/youtube-adapter 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/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @vitkuz/youtube-adapter
2
+
3
+ Functional YouTube Data API adapter for AWS/Node.js environments.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vitkuz/youtube-adapter
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createAdapter } from '@vitkuz/youtube-adapter';
15
+
16
+ const adapter = createAdapter({
17
+ apiKey: process.env.YOUTUBE_API_KEY,
18
+ logger: console, // Optional logger
19
+ });
20
+
21
+ // Search
22
+ const results = await adapter.search({
23
+ q: 'Node.js tutorial',
24
+ maxResults: 5
25
+ });
26
+
27
+ // Get Video Details
28
+ if (results.items?.length) {
29
+ const videoId = results.items[0].id.videoId;
30
+ const details = await adapter.videoDetails({
31
+ id: [videoId]
32
+ });
33
+ }
34
+ ```
35
+
36
+ ## Operations
37
+
38
+ - `search(input: SearchInput)`: Search for videos, channels, playlists.
39
+ - `videoDetails(input: VideoDetailsInput)`: Get detailed information about videos.
40
+
41
+ ## Configuration
42
+
43
+ Required environment variables:
44
+ - `YOUTUBE_API_KEY` (if using env vars, otherwise pass to constructor)
@@ -0,0 +1,61 @@
1
+ import { youtube_v3 } from 'googleapis';
2
+ import { Adapter as Adapter$1 } from '@vitkuz/firecrawl-adapter';
3
+
4
+ type YoutubeClient = youtube_v3.Youtube;
5
+ declare const createClient: (apiKey: string) => YoutubeClient;
6
+
7
+ interface Logger {
8
+ debug: (message: string, context?: {
9
+ error?: any;
10
+ data?: any;
11
+ }) => void;
12
+ [key: string]: any;
13
+ }
14
+ interface Context {
15
+ client: YoutubeClient;
16
+ firecrawlAdapter?: Adapter$1;
17
+ logger?: Logger;
18
+ }
19
+
20
+ type SearchInput = youtube_v3.Params$Resource$Search$List;
21
+ type SearchOutput = youtube_v3.Schema$SearchListResponse;
22
+ declare const search: (context: Context) => (input: SearchInput) => Promise<SearchOutput>;
23
+
24
+ type VideoDetailsInput = youtube_v3.Params$Resource$Videos$List;
25
+ type VideoDetailsOutput = youtube_v3.Schema$VideoListResponse;
26
+ declare const videoDetails: (context: Context) => (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;
27
+
28
+ interface GetAllChannelVideosInput {
29
+ channelId: string;
30
+ }
31
+ interface GetAllChannelVideosOutput {
32
+ items: youtube_v3.Schema$Video[];
33
+ totalCount: number;
34
+ }
35
+
36
+ interface GetTranscriptInput {
37
+ videoId: string;
38
+ lang?: string;
39
+ }
40
+ interface TranscriptItem {
41
+ timestamp: string;
42
+ text: string;
43
+ }
44
+ type GetTranscriptOutput = TranscriptItem[];
45
+ declare const getTranscript: (context: Context) => (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;
46
+
47
+ interface AdapterConfig {
48
+ apiKey: string;
49
+ firecrawlApiKey?: string;
50
+ logger?: Logger;
51
+ }
52
+ interface Adapter {
53
+ client: YoutubeClient;
54
+ search: (input: SearchInput) => Promise<SearchOutput>;
55
+ videoDetails: (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;
56
+ getAllChannelVideos: (input: GetAllChannelVideosInput) => Promise<GetAllChannelVideosOutput>;
57
+ getTranscript: (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;
58
+ }
59
+ declare const createAdapter: (config: AdapterConfig) => Adapter;
60
+
61
+ export { type Adapter, type AdapterConfig, type Context, type GetTranscriptInput, type GetTranscriptOutput, type Logger, type SearchInput, type SearchOutput, type TranscriptItem, type VideoDetailsInput, type VideoDetailsOutput, type YoutubeClient, createAdapter, createClient, getTranscript, search, videoDetails };
@@ -0,0 +1,61 @@
1
+ import { youtube_v3 } from 'googleapis';
2
+ import { Adapter as Adapter$1 } from '@vitkuz/firecrawl-adapter';
3
+
4
+ type YoutubeClient = youtube_v3.Youtube;
5
+ declare const createClient: (apiKey: string) => YoutubeClient;
6
+
7
+ interface Logger {
8
+ debug: (message: string, context?: {
9
+ error?: any;
10
+ data?: any;
11
+ }) => void;
12
+ [key: string]: any;
13
+ }
14
+ interface Context {
15
+ client: YoutubeClient;
16
+ firecrawlAdapter?: Adapter$1;
17
+ logger?: Logger;
18
+ }
19
+
20
+ type SearchInput = youtube_v3.Params$Resource$Search$List;
21
+ type SearchOutput = youtube_v3.Schema$SearchListResponse;
22
+ declare const search: (context: Context) => (input: SearchInput) => Promise<SearchOutput>;
23
+
24
+ type VideoDetailsInput = youtube_v3.Params$Resource$Videos$List;
25
+ type VideoDetailsOutput = youtube_v3.Schema$VideoListResponse;
26
+ declare const videoDetails: (context: Context) => (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;
27
+
28
+ interface GetAllChannelVideosInput {
29
+ channelId: string;
30
+ }
31
+ interface GetAllChannelVideosOutput {
32
+ items: youtube_v3.Schema$Video[];
33
+ totalCount: number;
34
+ }
35
+
36
+ interface GetTranscriptInput {
37
+ videoId: string;
38
+ lang?: string;
39
+ }
40
+ interface TranscriptItem {
41
+ timestamp: string;
42
+ text: string;
43
+ }
44
+ type GetTranscriptOutput = TranscriptItem[];
45
+ declare const getTranscript: (context: Context) => (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;
46
+
47
+ interface AdapterConfig {
48
+ apiKey: string;
49
+ firecrawlApiKey?: string;
50
+ logger?: Logger;
51
+ }
52
+ interface Adapter {
53
+ client: YoutubeClient;
54
+ search: (input: SearchInput) => Promise<SearchOutput>;
55
+ videoDetails: (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;
56
+ getAllChannelVideos: (input: GetAllChannelVideosInput) => Promise<GetAllChannelVideosOutput>;
57
+ getTranscript: (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;
58
+ }
59
+ declare const createAdapter: (config: AdapterConfig) => Adapter;
60
+
61
+ export { type Adapter, type AdapterConfig, type Context, type GetTranscriptInput, type GetTranscriptOutput, type Logger, type SearchInput, type SearchOutput, type TranscriptItem, type VideoDetailsInput, type VideoDetailsOutput, type YoutubeClient, createAdapter, createClient, getTranscript, search, videoDetails };
package/dist/index.js ADDED
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ var googleapis = require('googleapis');
4
+ var firecrawlAdapter = require('@vitkuz/firecrawl-adapter');
5
+
6
+ // src/client.ts
7
+ var createClient = (apiKey) => {
8
+ return googleapis.google.youtube({
9
+ version: "v3",
10
+ auth: apiKey
11
+ });
12
+ };
13
+
14
+ // src/operations/search.ts
15
+ var search = (context) => async (input) => {
16
+ const { client, logger } = context;
17
+ logger?.debug("search:start", { data: input });
18
+ try {
19
+ const response = await client.search.list({
20
+ part: ["snippet"],
21
+ ...input
22
+ });
23
+ logger?.debug("search:success");
24
+ return response.data;
25
+ } catch (error) {
26
+ logger?.debug("search:error", { error });
27
+ throw error;
28
+ }
29
+ };
30
+
31
+ // src/operations/video-details.ts
32
+ var videoDetails = (context) => async (input) => {
33
+ const { client, logger } = context;
34
+ logger?.debug("videoDetails:start", { data: input });
35
+ try {
36
+ const response = await client.videos.list({
37
+ part: ["snippet", "contentDetails", "statistics"],
38
+ ...input
39
+ });
40
+ logger?.debug("videoDetails:success");
41
+ return response.data;
42
+ } catch (error) {
43
+ logger?.debug("videoDetails:error", { error });
44
+ throw error;
45
+ }
46
+ };
47
+
48
+ // src/operations/get-all-channel-videos.ts
49
+ var getAllChannelVideos = (context) => async (input) => {
50
+ const { client, logger } = context;
51
+ logger?.debug("getAllChannelVideos:start", { data: input });
52
+ try {
53
+ const channelResponse = await client.channels.list({
54
+ id: [input.channelId],
55
+ part: ["contentDetails"]
56
+ });
57
+ const uploadsPlaylistId = channelResponse.data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads;
58
+ if (!uploadsPlaylistId) {
59
+ throw new Error(
60
+ `Could not find uploads playlist for channel ID: ${input.channelId}`
61
+ );
62
+ }
63
+ logger?.debug("getAllChannelVideos:found_playlist", { data: { uploadsPlaylistId } });
64
+ let items = [];
65
+ let nextPageToken = void 0;
66
+ do {
67
+ const playlistResponse = await client.playlistItems.list({
68
+ playlistId: uploadsPlaylistId,
69
+ part: ["contentDetails"],
70
+ maxResults: 50,
71
+ pageToken: nextPageToken
72
+ });
73
+ const playlistItems = playlistResponse.data.items || [];
74
+ if (playlistItems.length > 0) {
75
+ const videoIds = playlistItems.map((item) => item.contentDetails?.videoId).filter((id) => !!id);
76
+ if (videoIds.length > 0) {
77
+ const videosResponse = await client.videos.list({
78
+ id: videoIds,
79
+ part: ["snippet", "contentDetails", "statistics"]
80
+ });
81
+ const videoItems = videosResponse.data.items || [];
82
+ items = items.concat(videoItems);
83
+ }
84
+ }
85
+ nextPageToken = playlistResponse.data.nextPageToken || void 0;
86
+ logger?.debug("getAllChannelVideos:fetched_page", {
87
+ data: {
88
+ fetched: playlistItems.length,
89
+ totalVideosSoFar: items.length,
90
+ hasNextPage: !!nextPageToken
91
+ }
92
+ });
93
+ } while (nextPageToken);
94
+ logger?.debug("getAllChannelVideos:success", { data: { totalCount: items.length } });
95
+ return {
96
+ items,
97
+ totalCount: items.length
98
+ };
99
+ } catch (error) {
100
+ logger?.debug("getAllChannelVideos:error", { error });
101
+ throw error;
102
+ }
103
+ };
104
+
105
+ // src/operations/get-transcript.ts
106
+ var getTranscript = (context) => async (input) => {
107
+ const { logger, firecrawlAdapter } = context;
108
+ logger?.debug("getTranscript:start", { data: input });
109
+ if (!firecrawlAdapter) {
110
+ throw new Error(
111
+ "Firecrawl adapter is not initialized. Provide firecrawlApiKey in config."
112
+ );
113
+ }
114
+ const url = `https://www.youtube.com/watch?v=${input.videoId}`;
115
+ let markdown;
116
+ try {
117
+ const response = await firecrawlAdapter.scrape({
118
+ url,
119
+ params: {
120
+ formats: ["markdown"]
121
+ }
122
+ });
123
+ if (!response.success || !response.data || !response.data.markdown) {
124
+ throw new Error(
125
+ `Failed to scrape YouTube page: ${response.error || "No data returned"}`
126
+ );
127
+ }
128
+ markdown = response.data.markdown;
129
+ } catch (error) {
130
+ logger?.debug("getTranscript:error", { error });
131
+ throw new Error(`Failed to scrape YouTube page: ${error.message || String(error)}`);
132
+ }
133
+ const segments = [];
134
+ const transcriptStartValues = markdown.split("## Transcript");
135
+ if (transcriptStartValues.length < 2) {
136
+ if (markdown.length < 500) {
137
+ logger?.debug("getTranscript:markdown-too-short", { data: { markdown } });
138
+ } else {
139
+ logger?.debug("getTranscript:no-transcript-found", {
140
+ data: { preview: markdown.slice(0, 1e3) }
141
+ });
142
+ }
143
+ throw new Error("Transcript section not found in scraped content");
144
+ }
145
+ const transcriptContent = transcriptStartValues[1];
146
+ const lines = transcriptContent.split("\n");
147
+ let currentTimestamp = "";
148
+ let currentText = [];
149
+ const timestampRegex = /^(\d{1,2}:)?\d{1,2}:\d{2}$/;
150
+ for (const line of lines) {
151
+ const trimmed = line.trim();
152
+ if (!trimmed) continue;
153
+ if (timestampRegex.test(trimmed)) {
154
+ if (currentTimestamp) {
155
+ segments.push({
156
+ timestamp: currentTimestamp,
157
+ text: currentText.join(" ").trim()
158
+ });
159
+ currentText = [];
160
+ }
161
+ currentTimestamp = trimmed;
162
+ } else {
163
+ if (trimmed.startsWith("##")) continue;
164
+ if (trimmed.startsWith("[![](")) {
165
+ break;
166
+ }
167
+ if (trimmed === "English" || trimmed === "German") {
168
+ continue;
169
+ }
170
+ if (currentTimestamp) {
171
+ currentText.push(trimmed);
172
+ }
173
+ }
174
+ }
175
+ if (currentTimestamp && currentText.length > 0) {
176
+ segments.push({
177
+ timestamp: currentTimestamp,
178
+ text: currentText.join(" ").trim()
179
+ });
180
+ }
181
+ logger?.debug("getTranscript:success", { data: { count: segments.length } });
182
+ return segments;
183
+ };
184
+ var createAdapter = (config) => {
185
+ const client = createClient(config.apiKey);
186
+ let firecrawlAdapter$1;
187
+ if (config.firecrawlApiKey) {
188
+ firecrawlAdapter$1 = firecrawlAdapter.createAdapter({
189
+ apiKey: config.firecrawlApiKey,
190
+ plan: firecrawlAdapter.FirecrawlPlan.FREE,
191
+ // Default to free, could be configurable
192
+ logger: config.logger
193
+ });
194
+ }
195
+ const context = {
196
+ client,
197
+ firecrawlAdapter: firecrawlAdapter$1,
198
+ logger: config.logger
199
+ };
200
+ return {
201
+ client,
202
+ search: search(context),
203
+ videoDetails: videoDetails(context),
204
+ getAllChannelVideos: getAllChannelVideos(context),
205
+ getTranscript: getTranscript(context)
206
+ };
207
+ };
208
+
209
+ exports.createAdapter = createAdapter;
210
+ exports.createClient = createClient;
211
+ exports.getTranscript = getTranscript;
212
+ exports.search = search;
213
+ exports.videoDetails = videoDetails;
214
+ //# sourceMappingURL=index.js.map
215
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/operations/search.ts","../src/operations/video-details.ts","../src/operations/get-all-channel-videos.ts","../src/operations/get-transcript.ts","../src/adapter.ts"],"names":["google","firecrawlAdapter","createFirecrawlAdapter","FirecrawlPlan"],"mappings":"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","file":"index.js","sourcesContent":["import { google, youtube_v3 } from 'googleapis';\n\nexport type YoutubeClient = youtube_v3.Youtube;\n\nexport const createClient = (apiKey: string): YoutubeClient => {\n return google.youtube({\n version: 'v3',\n auth: apiKey,\n });\n};\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport type SearchInput = youtube_v3.Params$Resource$Search$List;\nexport type SearchOutput = youtube_v3.Schema$SearchListResponse;\n\nexport const search =\n (context: Context) =>\n async (input: SearchInput): Promise<SearchOutput> => {\n const { client, logger } = context;\n\n logger?.debug('search:start', { data: input });\n\n try {\n const response = await client.search.list({\n part: ['snippet'],\n ...input,\n });\n\n logger?.debug('search:success');\n return response.data;\n } catch (error) {\n logger?.debug('search:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport type VideoDetailsInput = youtube_v3.Params$Resource$Videos$List;\nexport type VideoDetailsOutput = youtube_v3.Schema$VideoListResponse;\n\nexport const videoDetails =\n (context: Context) =>\n async (input: VideoDetailsInput): Promise<VideoDetailsOutput> => {\n const { client, logger } = context;\n\n logger?.debug('videoDetails:start', { data: input });\n\n try {\n const response = await client.videos.list({\n part: ['snippet', 'contentDetails', 'statistics'],\n ...input,\n });\n\n logger?.debug('videoDetails:success');\n return response.data;\n } catch (error) {\n logger?.debug('videoDetails:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport interface GetAllChannelVideosInput {\n channelId: string;\n}\n\nexport interface GetAllChannelVideosOutput {\n items: youtube_v3.Schema$Video[];\n totalCount: number;\n}\n\nexport const getAllChannelVideos =\n (context: Context) =>\n async (input: GetAllChannelVideosInput): Promise<GetAllChannelVideosOutput> => {\n const { client, logger } = context;\n\n logger?.debug('getAllChannelVideos:start', { data: input });\n\n try {\n // Step 1: Get the Uploads playlist ID\n const channelResponse = await client.channels.list({\n id: [input.channelId],\n part: ['contentDetails'],\n });\n\n const uploadsPlaylistId =\n channelResponse.data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads;\n\n if (!uploadsPlaylistId) {\n throw new Error(\n `Could not find uploads playlist for channel ID: ${input.channelId}`,\n );\n }\n\n logger?.debug('getAllChannelVideos:found_playlist', { data: { uploadsPlaylistId } });\n\n // Step 2: Recursively fetch all playlist items and their video details\n let items: youtube_v3.Schema$Video[] = [];\n let nextPageToken: string | undefined = undefined;\n\n do {\n // Get playlist items (Video IDs)\n const playlistResponse: { data: youtube_v3.Schema$PlaylistItemListResponse } =\n (await client.playlistItems.list({\n playlistId: uploadsPlaylistId,\n part: ['contentDetails'],\n maxResults: 50,\n pageToken: nextPageToken,\n })) as any;\n\n const playlistItems = playlistResponse.data.items || [];\n\n if (playlistItems.length > 0) {\n const videoIds = playlistItems\n .map((item) => item.contentDetails?.videoId)\n .filter((id): id is string => !!id);\n\n if (videoIds.length > 0) {\n // Fetch full video details\n const videosResponse = await client.videos.list({\n id: videoIds,\n part: ['snippet', 'contentDetails', 'statistics'],\n });\n\n const videoItems = videosResponse.data.items || [];\n items = items.concat(videoItems);\n }\n }\n\n nextPageToken = playlistResponse.data.nextPageToken || undefined;\n\n logger?.debug('getAllChannelVideos:fetched_page', {\n data: {\n fetched: playlistItems.length,\n totalVideosSoFar: items.length,\n hasNextPage: !!nextPageToken,\n },\n });\n } while (nextPageToken);\n\n logger?.debug('getAllChannelVideos:success', { data: { totalCount: items.length } });\n\n return {\n items,\n totalCount: items.length,\n };\n } catch (error) {\n logger?.debug('getAllChannelVideos:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\n\nexport interface GetTranscriptInput {\n videoId: string;\n lang?: string;\n}\n\nexport interface TranscriptItem {\n timestamp: string;\n text: string;\n}\n\nexport type GetTranscriptOutput = TranscriptItem[];\n\nexport const getTranscript =\n (context: Context) =>\n async (input: GetTranscriptInput): Promise<GetTranscriptOutput> => {\n const { logger, firecrawlAdapter } = context;\n logger?.debug('getTranscript:start', { data: input });\n\n if (!firecrawlAdapter) {\n throw new Error(\n 'Firecrawl adapter is not initialized. Provide firecrawlApiKey in config.',\n );\n }\n\n const url = `https://www.youtube.com/watch?v=${input.videoId}`;\n let markdown: string;\n\n try {\n // Use firecrawlAdapter.scrape directly\n const response = await firecrawlAdapter.scrape({\n url,\n params: {\n formats: ['markdown'],\n },\n });\n\n if (!response.success || !response.data || !response.data.markdown) {\n throw new Error(\n `Failed to scrape YouTube page: ${response.error || 'No data returned'}`,\n );\n }\n markdown = response.data.markdown;\n } catch (error: any) {\n logger?.debug('getTranscript:error', { error });\n throw new Error(`Failed to scrape YouTube page: ${error.message || String(error)}`);\n }\n\n // Parsing logic reused from firecrawl adapter implementation\n const segments: TranscriptItem[] = [];\n\n const transcriptStartValues = markdown.split('## Transcript');\n if (transcriptStartValues.length < 2) {\n if (markdown.length < 500) {\n logger?.debug('getTranscript:markdown-too-short', { data: { markdown } });\n } else {\n logger?.debug('getTranscript:no-transcript-found', {\n data: { preview: markdown.slice(0, 1000) },\n });\n }\n throw new Error('Transcript section not found in scraped content');\n }\n\n const transcriptContent = transcriptStartValues[1];\n\n const lines = transcriptContent.split('\\n');\n let currentTimestamp = '';\n let currentText: string[] = [];\n\n const timestampRegex = /^(\\d{1,2}:)?\\d{1,2}:\\d{2}$/; // Matches 0:01, 10:05, 1:00:00\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check if it's a timestamp\n if (timestampRegex.test(trimmed)) {\n // If we have a previous segment accumulating, push it\n if (currentTimestamp) {\n segments.push({\n timestamp: currentTimestamp,\n text: currentText.join(' ').trim(),\n });\n currentText = [];\n }\n currentTimestamp = trimmed;\n } else {\n // It's text or a header?\n if (trimmed.startsWith('##')) continue;\n\n // Stop if we hit video thumbnails (footer)\n if (trimmed.startsWith('[![](')) {\n break;\n }\n\n // Stop if we hit language options (English/German) which usually signify end of transcript\n if (trimmed === 'English' || trimmed === 'German') {\n continue;\n }\n\n if (currentTimestamp) {\n currentText.push(trimmed);\n }\n }\n }\n\n // Push last segment\n if (currentTimestamp && currentText.length > 0) {\n segments.push({\n timestamp: currentTimestamp,\n text: currentText.join(' ').trim(),\n });\n }\n\n logger?.debug('getTranscript:success', { data: { count: segments.length } });\n return segments;\n };\n","import { createClient, YoutubeClient } from './client';\nimport { Context, Logger } from './types';\nimport { search, SearchInput, SearchOutput } from './operations/search';\nimport { videoDetails, VideoDetailsInput, VideoDetailsOutput } from './operations/video-details';\nimport {\n getAllChannelVideos,\n GetAllChannelVideosInput,\n GetAllChannelVideosOutput,\n} from './operations/get-all-channel-videos';\nimport {\n getTranscript,\n GetTranscriptInput,\n GetTranscriptOutput,\n} from './operations/get-transcript';\n\nexport interface AdapterConfig {\n apiKey: string;\n firecrawlApiKey?: string;\n logger?: Logger;\n}\n\nexport interface Adapter {\n client: YoutubeClient;\n search: (input: SearchInput) => Promise<SearchOutput>;\n videoDetails: (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;\n getAllChannelVideos: (input: GetAllChannelVideosInput) => Promise<GetAllChannelVideosOutput>;\n getTranscript: (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;\n}\n\nimport { createAdapter as createFirecrawlAdapter, FirecrawlPlan } from '@vitkuz/firecrawl-adapter';\n\nexport const createAdapter = (config: AdapterConfig): Adapter => {\n const client = createClient(config.apiKey);\n\n let firecrawlAdapter;\n if (config.firecrawlApiKey) {\n firecrawlAdapter = createFirecrawlAdapter({\n apiKey: config.firecrawlApiKey,\n plan: FirecrawlPlan.FREE, // Default to free, could be configurable\n logger: config.logger,\n });\n }\n\n const context: Context = {\n client,\n firecrawlAdapter,\n logger: config.logger,\n };\n\n return {\n client,\n search: search(context),\n videoDetails: videoDetails(context),\n getAllChannelVideos: getAllChannelVideos(context),\n getTranscript: getTranscript(context),\n };\n};\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,209 @@
1
+ import { google } from 'googleapis';
2
+ import { createAdapter as createAdapter$1, FirecrawlPlan } from '@vitkuz/firecrawl-adapter';
3
+
4
+ // src/client.ts
5
+ var createClient = (apiKey) => {
6
+ return google.youtube({
7
+ version: "v3",
8
+ auth: apiKey
9
+ });
10
+ };
11
+
12
+ // src/operations/search.ts
13
+ var search = (context) => async (input) => {
14
+ const { client, logger } = context;
15
+ logger?.debug("search:start", { data: input });
16
+ try {
17
+ const response = await client.search.list({
18
+ part: ["snippet"],
19
+ ...input
20
+ });
21
+ logger?.debug("search:success");
22
+ return response.data;
23
+ } catch (error) {
24
+ logger?.debug("search:error", { error });
25
+ throw error;
26
+ }
27
+ };
28
+
29
+ // src/operations/video-details.ts
30
+ var videoDetails = (context) => async (input) => {
31
+ const { client, logger } = context;
32
+ logger?.debug("videoDetails:start", { data: input });
33
+ try {
34
+ const response = await client.videos.list({
35
+ part: ["snippet", "contentDetails", "statistics"],
36
+ ...input
37
+ });
38
+ logger?.debug("videoDetails:success");
39
+ return response.data;
40
+ } catch (error) {
41
+ logger?.debug("videoDetails:error", { error });
42
+ throw error;
43
+ }
44
+ };
45
+
46
+ // src/operations/get-all-channel-videos.ts
47
+ var getAllChannelVideos = (context) => async (input) => {
48
+ const { client, logger } = context;
49
+ logger?.debug("getAllChannelVideos:start", { data: input });
50
+ try {
51
+ const channelResponse = await client.channels.list({
52
+ id: [input.channelId],
53
+ part: ["contentDetails"]
54
+ });
55
+ const uploadsPlaylistId = channelResponse.data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads;
56
+ if (!uploadsPlaylistId) {
57
+ throw new Error(
58
+ `Could not find uploads playlist for channel ID: ${input.channelId}`
59
+ );
60
+ }
61
+ logger?.debug("getAllChannelVideos:found_playlist", { data: { uploadsPlaylistId } });
62
+ let items = [];
63
+ let nextPageToken = void 0;
64
+ do {
65
+ const playlistResponse = await client.playlistItems.list({
66
+ playlistId: uploadsPlaylistId,
67
+ part: ["contentDetails"],
68
+ maxResults: 50,
69
+ pageToken: nextPageToken
70
+ });
71
+ const playlistItems = playlistResponse.data.items || [];
72
+ if (playlistItems.length > 0) {
73
+ const videoIds = playlistItems.map((item) => item.contentDetails?.videoId).filter((id) => !!id);
74
+ if (videoIds.length > 0) {
75
+ const videosResponse = await client.videos.list({
76
+ id: videoIds,
77
+ part: ["snippet", "contentDetails", "statistics"]
78
+ });
79
+ const videoItems = videosResponse.data.items || [];
80
+ items = items.concat(videoItems);
81
+ }
82
+ }
83
+ nextPageToken = playlistResponse.data.nextPageToken || void 0;
84
+ logger?.debug("getAllChannelVideos:fetched_page", {
85
+ data: {
86
+ fetched: playlistItems.length,
87
+ totalVideosSoFar: items.length,
88
+ hasNextPage: !!nextPageToken
89
+ }
90
+ });
91
+ } while (nextPageToken);
92
+ logger?.debug("getAllChannelVideos:success", { data: { totalCount: items.length } });
93
+ return {
94
+ items,
95
+ totalCount: items.length
96
+ };
97
+ } catch (error) {
98
+ logger?.debug("getAllChannelVideos:error", { error });
99
+ throw error;
100
+ }
101
+ };
102
+
103
+ // src/operations/get-transcript.ts
104
+ var getTranscript = (context) => async (input) => {
105
+ const { logger, firecrawlAdapter } = context;
106
+ logger?.debug("getTranscript:start", { data: input });
107
+ if (!firecrawlAdapter) {
108
+ throw new Error(
109
+ "Firecrawl adapter is not initialized. Provide firecrawlApiKey in config."
110
+ );
111
+ }
112
+ const url = `https://www.youtube.com/watch?v=${input.videoId}`;
113
+ let markdown;
114
+ try {
115
+ const response = await firecrawlAdapter.scrape({
116
+ url,
117
+ params: {
118
+ formats: ["markdown"]
119
+ }
120
+ });
121
+ if (!response.success || !response.data || !response.data.markdown) {
122
+ throw new Error(
123
+ `Failed to scrape YouTube page: ${response.error || "No data returned"}`
124
+ );
125
+ }
126
+ markdown = response.data.markdown;
127
+ } catch (error) {
128
+ logger?.debug("getTranscript:error", { error });
129
+ throw new Error(`Failed to scrape YouTube page: ${error.message || String(error)}`);
130
+ }
131
+ const segments = [];
132
+ const transcriptStartValues = markdown.split("## Transcript");
133
+ if (transcriptStartValues.length < 2) {
134
+ if (markdown.length < 500) {
135
+ logger?.debug("getTranscript:markdown-too-short", { data: { markdown } });
136
+ } else {
137
+ logger?.debug("getTranscript:no-transcript-found", {
138
+ data: { preview: markdown.slice(0, 1e3) }
139
+ });
140
+ }
141
+ throw new Error("Transcript section not found in scraped content");
142
+ }
143
+ const transcriptContent = transcriptStartValues[1];
144
+ const lines = transcriptContent.split("\n");
145
+ let currentTimestamp = "";
146
+ let currentText = [];
147
+ const timestampRegex = /^(\d{1,2}:)?\d{1,2}:\d{2}$/;
148
+ for (const line of lines) {
149
+ const trimmed = line.trim();
150
+ if (!trimmed) continue;
151
+ if (timestampRegex.test(trimmed)) {
152
+ if (currentTimestamp) {
153
+ segments.push({
154
+ timestamp: currentTimestamp,
155
+ text: currentText.join(" ").trim()
156
+ });
157
+ currentText = [];
158
+ }
159
+ currentTimestamp = trimmed;
160
+ } else {
161
+ if (trimmed.startsWith("##")) continue;
162
+ if (trimmed.startsWith("[![](")) {
163
+ break;
164
+ }
165
+ if (trimmed === "English" || trimmed === "German") {
166
+ continue;
167
+ }
168
+ if (currentTimestamp) {
169
+ currentText.push(trimmed);
170
+ }
171
+ }
172
+ }
173
+ if (currentTimestamp && currentText.length > 0) {
174
+ segments.push({
175
+ timestamp: currentTimestamp,
176
+ text: currentText.join(" ").trim()
177
+ });
178
+ }
179
+ logger?.debug("getTranscript:success", { data: { count: segments.length } });
180
+ return segments;
181
+ };
182
+ var createAdapter = (config) => {
183
+ const client = createClient(config.apiKey);
184
+ let firecrawlAdapter;
185
+ if (config.firecrawlApiKey) {
186
+ firecrawlAdapter = createAdapter$1({
187
+ apiKey: config.firecrawlApiKey,
188
+ plan: FirecrawlPlan.FREE,
189
+ // Default to free, could be configurable
190
+ logger: config.logger
191
+ });
192
+ }
193
+ const context = {
194
+ client,
195
+ firecrawlAdapter,
196
+ logger: config.logger
197
+ };
198
+ return {
199
+ client,
200
+ search: search(context),
201
+ videoDetails: videoDetails(context),
202
+ getAllChannelVideos: getAllChannelVideos(context),
203
+ getTranscript: getTranscript(context)
204
+ };
205
+ };
206
+
207
+ export { createAdapter, createClient, getTranscript, search, videoDetails };
208
+ //# sourceMappingURL=index.mjs.map
209
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/operations/search.ts","../src/operations/video-details.ts","../src/operations/get-all-channel-videos.ts","../src/operations/get-transcript.ts","../src/adapter.ts"],"names":["createFirecrawlAdapter"],"mappings":"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","file":"index.mjs","sourcesContent":["import { google, youtube_v3 } from 'googleapis';\n\nexport type YoutubeClient = youtube_v3.Youtube;\n\nexport const createClient = (apiKey: string): YoutubeClient => {\n return google.youtube({\n version: 'v3',\n auth: apiKey,\n });\n};\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport type SearchInput = youtube_v3.Params$Resource$Search$List;\nexport type SearchOutput = youtube_v3.Schema$SearchListResponse;\n\nexport const search =\n (context: Context) =>\n async (input: SearchInput): Promise<SearchOutput> => {\n const { client, logger } = context;\n\n logger?.debug('search:start', { data: input });\n\n try {\n const response = await client.search.list({\n part: ['snippet'],\n ...input,\n });\n\n logger?.debug('search:success');\n return response.data;\n } catch (error) {\n logger?.debug('search:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport type VideoDetailsInput = youtube_v3.Params$Resource$Videos$List;\nexport type VideoDetailsOutput = youtube_v3.Schema$VideoListResponse;\n\nexport const videoDetails =\n (context: Context) =>\n async (input: VideoDetailsInput): Promise<VideoDetailsOutput> => {\n const { client, logger } = context;\n\n logger?.debug('videoDetails:start', { data: input });\n\n try {\n const response = await client.videos.list({\n part: ['snippet', 'contentDetails', 'statistics'],\n ...input,\n });\n\n logger?.debug('videoDetails:success');\n return response.data;\n } catch (error) {\n logger?.debug('videoDetails:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\nimport { youtube_v3 } from 'googleapis';\n\nexport interface GetAllChannelVideosInput {\n channelId: string;\n}\n\nexport interface GetAllChannelVideosOutput {\n items: youtube_v3.Schema$Video[];\n totalCount: number;\n}\n\nexport const getAllChannelVideos =\n (context: Context) =>\n async (input: GetAllChannelVideosInput): Promise<GetAllChannelVideosOutput> => {\n const { client, logger } = context;\n\n logger?.debug('getAllChannelVideos:start', { data: input });\n\n try {\n // Step 1: Get the Uploads playlist ID\n const channelResponse = await client.channels.list({\n id: [input.channelId],\n part: ['contentDetails'],\n });\n\n const uploadsPlaylistId =\n channelResponse.data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads;\n\n if (!uploadsPlaylistId) {\n throw new Error(\n `Could not find uploads playlist for channel ID: ${input.channelId}`,\n );\n }\n\n logger?.debug('getAllChannelVideos:found_playlist', { data: { uploadsPlaylistId } });\n\n // Step 2: Recursively fetch all playlist items and their video details\n let items: youtube_v3.Schema$Video[] = [];\n let nextPageToken: string | undefined = undefined;\n\n do {\n // Get playlist items (Video IDs)\n const playlistResponse: { data: youtube_v3.Schema$PlaylistItemListResponse } =\n (await client.playlistItems.list({\n playlistId: uploadsPlaylistId,\n part: ['contentDetails'],\n maxResults: 50,\n pageToken: nextPageToken,\n })) as any;\n\n const playlistItems = playlistResponse.data.items || [];\n\n if (playlistItems.length > 0) {\n const videoIds = playlistItems\n .map((item) => item.contentDetails?.videoId)\n .filter((id): id is string => !!id);\n\n if (videoIds.length > 0) {\n // Fetch full video details\n const videosResponse = await client.videos.list({\n id: videoIds,\n part: ['snippet', 'contentDetails', 'statistics'],\n });\n\n const videoItems = videosResponse.data.items || [];\n items = items.concat(videoItems);\n }\n }\n\n nextPageToken = playlistResponse.data.nextPageToken || undefined;\n\n logger?.debug('getAllChannelVideos:fetched_page', {\n data: {\n fetched: playlistItems.length,\n totalVideosSoFar: items.length,\n hasNextPage: !!nextPageToken,\n },\n });\n } while (nextPageToken);\n\n logger?.debug('getAllChannelVideos:success', { data: { totalCount: items.length } });\n\n return {\n items,\n totalCount: items.length,\n };\n } catch (error) {\n logger?.debug('getAllChannelVideos:error', { error });\n throw error;\n }\n };\n","import { Context } from '../types';\n\nexport interface GetTranscriptInput {\n videoId: string;\n lang?: string;\n}\n\nexport interface TranscriptItem {\n timestamp: string;\n text: string;\n}\n\nexport type GetTranscriptOutput = TranscriptItem[];\n\nexport const getTranscript =\n (context: Context) =>\n async (input: GetTranscriptInput): Promise<GetTranscriptOutput> => {\n const { logger, firecrawlAdapter } = context;\n logger?.debug('getTranscript:start', { data: input });\n\n if (!firecrawlAdapter) {\n throw new Error(\n 'Firecrawl adapter is not initialized. Provide firecrawlApiKey in config.',\n );\n }\n\n const url = `https://www.youtube.com/watch?v=${input.videoId}`;\n let markdown: string;\n\n try {\n // Use firecrawlAdapter.scrape directly\n const response = await firecrawlAdapter.scrape({\n url,\n params: {\n formats: ['markdown'],\n },\n });\n\n if (!response.success || !response.data || !response.data.markdown) {\n throw new Error(\n `Failed to scrape YouTube page: ${response.error || 'No data returned'}`,\n );\n }\n markdown = response.data.markdown;\n } catch (error: any) {\n logger?.debug('getTranscript:error', { error });\n throw new Error(`Failed to scrape YouTube page: ${error.message || String(error)}`);\n }\n\n // Parsing logic reused from firecrawl adapter implementation\n const segments: TranscriptItem[] = [];\n\n const transcriptStartValues = markdown.split('## Transcript');\n if (transcriptStartValues.length < 2) {\n if (markdown.length < 500) {\n logger?.debug('getTranscript:markdown-too-short', { data: { markdown } });\n } else {\n logger?.debug('getTranscript:no-transcript-found', {\n data: { preview: markdown.slice(0, 1000) },\n });\n }\n throw new Error('Transcript section not found in scraped content');\n }\n\n const transcriptContent = transcriptStartValues[1];\n\n const lines = transcriptContent.split('\\n');\n let currentTimestamp = '';\n let currentText: string[] = [];\n\n const timestampRegex = /^(\\d{1,2}:)?\\d{1,2}:\\d{2}$/; // Matches 0:01, 10:05, 1:00:00\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check if it's a timestamp\n if (timestampRegex.test(trimmed)) {\n // If we have a previous segment accumulating, push it\n if (currentTimestamp) {\n segments.push({\n timestamp: currentTimestamp,\n text: currentText.join(' ').trim(),\n });\n currentText = [];\n }\n currentTimestamp = trimmed;\n } else {\n // It's text or a header?\n if (trimmed.startsWith('##')) continue;\n\n // Stop if we hit video thumbnails (footer)\n if (trimmed.startsWith('[![](')) {\n break;\n }\n\n // Stop if we hit language options (English/German) which usually signify end of transcript\n if (trimmed === 'English' || trimmed === 'German') {\n continue;\n }\n\n if (currentTimestamp) {\n currentText.push(trimmed);\n }\n }\n }\n\n // Push last segment\n if (currentTimestamp && currentText.length > 0) {\n segments.push({\n timestamp: currentTimestamp,\n text: currentText.join(' ').trim(),\n });\n }\n\n logger?.debug('getTranscript:success', { data: { count: segments.length } });\n return segments;\n };\n","import { createClient, YoutubeClient } from './client';\nimport { Context, Logger } from './types';\nimport { search, SearchInput, SearchOutput } from './operations/search';\nimport { videoDetails, VideoDetailsInput, VideoDetailsOutput } from './operations/video-details';\nimport {\n getAllChannelVideos,\n GetAllChannelVideosInput,\n GetAllChannelVideosOutput,\n} from './operations/get-all-channel-videos';\nimport {\n getTranscript,\n GetTranscriptInput,\n GetTranscriptOutput,\n} from './operations/get-transcript';\n\nexport interface AdapterConfig {\n apiKey: string;\n firecrawlApiKey?: string;\n logger?: Logger;\n}\n\nexport interface Adapter {\n client: YoutubeClient;\n search: (input: SearchInput) => Promise<SearchOutput>;\n videoDetails: (input: VideoDetailsInput) => Promise<VideoDetailsOutput>;\n getAllChannelVideos: (input: GetAllChannelVideosInput) => Promise<GetAllChannelVideosOutput>;\n getTranscript: (input: GetTranscriptInput) => Promise<GetTranscriptOutput>;\n}\n\nimport { createAdapter as createFirecrawlAdapter, FirecrawlPlan } from '@vitkuz/firecrawl-adapter';\n\nexport const createAdapter = (config: AdapterConfig): Adapter => {\n const client = createClient(config.apiKey);\n\n let firecrawlAdapter;\n if (config.firecrawlApiKey) {\n firecrawlAdapter = createFirecrawlAdapter({\n apiKey: config.firecrawlApiKey,\n plan: FirecrawlPlan.FREE, // Default to free, could be configurable\n logger: config.logger,\n });\n }\n\n const context: Context = {\n client,\n firecrawlAdapter,\n logger: config.logger,\n };\n\n return {\n client,\n search: search(context),\n videoDetails: videoDetails(context),\n getAllChannelVideos: getAllChannelVideos(context),\n getTranscript: getTranscript(context),\n };\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@vitkuz/youtube-adapter",
3
+ "version": "1.0.0",
4
+ "description": "Functional YouTube adapter",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
22
+ "prepublishOnly": "npm run format && npm run build"
23
+ },
24
+ "dependencies": {
25
+ "@vitkuz/firecrawl-adapter": "file:../vitkuz-firecrawl-adapter",
26
+ "dotenv": "^16.4.5",
27
+ "googleapis": "^144.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.0.0",
31
+ "prettier": "^3.7.4",
32
+ "rimraf": "^6.0.0",
33
+ "tsup": "^8.0.0",
34
+ "tsx": "^4.21.0",
35
+ "typescript": "^5.0.0"
36
+ }
37
+ }