| const { ytToolkit } = require('@librechat/api'); | |
| const { tool } = require('@langchain/core/tools'); | |
| const { youtube } = require('@googleapis/youtube'); | |
| const { logger } = require('@librechat/data-schemas'); | |
| const { YoutubeTranscript } = require('youtube-transcript'); | |
| const { getApiKey } = require('./credentials'); | |
| function extractVideoId(url) { | |
| const rawIdRegex = /^[a-zA-Z0-9_-]{11}$/; | |
| if (rawIdRegex.test(url)) { | |
| return url; | |
| } | |
| const regex = new RegExp( | |
| '(?:youtu\\.be/|youtube(?:\\.com)?/(?:' + | |
| '(?:watch\\?v=)|(?:embed/)|(?:shorts/)|(?:live/)|(?:v/)|(?:/))?)' + | |
| '([a-zA-Z0-9_-]{11})(?:\\S+)?$', | |
| ); | |
| const match = url.match(regex); | |
| return match ? match[1] : null; | |
| } | |
| function parseTranscript(transcriptResponse) { | |
| if (!Array.isArray(transcriptResponse)) { | |
| return ''; | |
| } | |
| return transcriptResponse | |
| .map((entry) => entry.text.trim()) | |
| .filter((text) => text) | |
| .join(' ') | |
| .replaceAll(''', "'"); | |
| } | |
| function createYouTubeTools(fields = {}) { | |
| const envVar = 'YOUTUBE_API_KEY'; | |
| const override = fields.override ?? false; | |
| const apiKey = fields.apiKey ?? fields[envVar] ?? getApiKey(envVar, override); | |
| const youtubeClient = youtube({ | |
| version: 'v3', | |
| auth: apiKey, | |
| }); | |
| const searchTool = tool(async ({ query, maxResults = 5 }) => { | |
| const response = await youtubeClient.search.list({ | |
| part: 'snippet', | |
| q: query, | |
| type: 'video', | |
| maxResults: maxResults || 5, | |
| }); | |
| const result = response.data.items.map((item) => ({ | |
| title: item.snippet.title, | |
| description: item.snippet.description, | |
| url: `https://www.youtube.com/watch?v=${item.id.videoId}`, | |
| })); | |
| return JSON.stringify(result, null, 2); | |
| }, ytToolkit.youtube_search); | |
| const infoTool = tool(async ({ url }) => { | |
| const videoId = extractVideoId(url); | |
| if (!videoId) { | |
| throw new Error('Invalid YouTube URL or video ID'); | |
| } | |
| const response = await youtubeClient.videos.list({ | |
| part: 'snippet,statistics', | |
| id: videoId, | |
| }); | |
| if (!response.data.items?.length) { | |
| throw new Error('Video not found'); | |
| } | |
| const video = response.data.items[0]; | |
| const result = { | |
| title: video.snippet.title, | |
| description: video.snippet.description, | |
| views: video.statistics.viewCount, | |
| likes: video.statistics.likeCount, | |
| comments: video.statistics.commentCount, | |
| }; | |
| return JSON.stringify(result, null, 2); | |
| }, ytToolkit.youtube_info); | |
| const commentsTool = tool(async ({ url, maxResults = 10 }) => { | |
| const videoId = extractVideoId(url); | |
| if (!videoId) { | |
| throw new Error('Invalid YouTube URL or video ID'); | |
| } | |
| const response = await youtubeClient.commentThreads.list({ | |
| part: 'snippet', | |
| videoId, | |
| maxResults: maxResults || 10, | |
| }); | |
| const result = response.data.items.map((item) => ({ | |
| author: item.snippet.topLevelComment.snippet.authorDisplayName, | |
| text: item.snippet.topLevelComment.snippet.textDisplay, | |
| likes: item.snippet.topLevelComment.snippet.likeCount, | |
| })); | |
| return JSON.stringify(result, null, 2); | |
| }, ytToolkit.youtube_comments); | |
| const transcriptTool = tool(async ({ url }) => { | |
| const videoId = extractVideoId(url); | |
| if (!videoId) { | |
| throw new Error('Invalid YouTube URL or video ID'); | |
| } | |
| try { | |
| try { | |
| const transcript = await YoutubeTranscript.fetchTranscript(videoId, { lang: 'en' }); | |
| return parseTranscript(transcript); | |
| } catch (e) { | |
| logger.error(e); | |
| } | |
| try { | |
| const transcript = await YoutubeTranscript.fetchTranscript(videoId, { lang: 'de' }); | |
| return parseTranscript(transcript); | |
| } catch (e) { | |
| logger.error(e); | |
| } | |
| const transcript = await YoutubeTranscript.fetchTranscript(videoId); | |
| return parseTranscript(transcript); | |
| } catch (error) { | |
| throw new Error(`Failed to fetch transcript: ${error.message}`); | |
| } | |
| }, ytToolkit.youtube_transcript); | |
| return [searchTool, infoTool, commentsTool, transcriptTool]; | |
| } | |
| module.exports = createYouTubeTools; | |