mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(tools): added additional youtube search params, get channel playlists videos and related videos tools (#1814)
This commit is contained in:
@@ -40,7 +40,7 @@ In Sim, the YouTube integration enables your agents to programmatically search a
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate YouTube into the workflow. Can search for videos, get video details, get channel information, get playlist items, and get video comments.
|
||||
Integrate YouTube into the workflow. Can search for videos, get video details, get channel information, get all videos from a channel, get channel playlists, get playlist items, find related videos, and get video comments.
|
||||
|
||||
|
||||
|
||||
@@ -48,15 +48,26 @@ Integrate YouTube into the workflow. Can search for videos, get video details, g
|
||||
|
||||
### `youtube_search`
|
||||
|
||||
Search for videos on YouTube using the YouTube Data API.
|
||||
Search for videos on YouTube using the YouTube Data API. Supports advanced filtering by channel, date range, duration, category, quality, captions, and more.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Yes | Search query for YouTube videos |
|
||||
| `maxResults` | number | No | Maximum number of videos to return |
|
||||
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
||||
| `apiKey` | string | Yes | YouTube API Key |
|
||||
| `channelId` | string | No | Filter results to a specific YouTube channel ID |
|
||||
| `publishedAfter` | string | No | Only return videos published after this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
||||
| `publishedBefore` | string | No | Only return videos published before this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
||||
| `videoDuration` | string | No | Filter by video length: "short" \(<4 min\), "medium" \(4-20 min\), "long" \(>20 min\), "any" |
|
||||
| `order` | string | No | Sort results by: "date", "rating", "relevance" \(default\), "title", "videoCount", "viewCount" |
|
||||
| `videoCategoryId` | string | No | Filter by YouTube category ID \(e.g., "10" for Music, "20" for Gaming\) |
|
||||
| `videoDefinition` | string | No | Filter by video quality: "high" \(HD\), "standard", "any" |
|
||||
| `videoCaption` | string | No | Filter by caption availability: "closedCaption" \(has captions\), "none" \(no captions\), "any" |
|
||||
| `regionCode` | string | No | Return results relevant to a specific region \(ISO 3166-1 alpha-2 country code, e.g., "US", "GB"\) |
|
||||
| `relevanceLanguage` | string | No | Return results most relevant to a language \(ISO 639-1 code, e.g., "en", "es"\) |
|
||||
| `safeSearch` | string | No | Content filtering level: "moderate" \(default\), "none", "strict" |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -118,6 +129,45 @@ Get detailed information about a YouTube channel.
|
||||
| `thumbnail` | string | Channel thumbnail URL |
|
||||
| `customUrl` | string | Channel custom URL |
|
||||
|
||||
### `youtube_channel_videos`
|
||||
|
||||
Get all videos from a specific YouTube channel, with sorting options.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `channelId` | string | Yes | YouTube channel ID to get videos from |
|
||||
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
||||
| `order` | string | No | Sort order: "date" \(newest first\), "rating", "relevance", "title", "viewCount" |
|
||||
| `pageToken` | string | No | Page token for pagination |
|
||||
| `apiKey` | string | Yes | YouTube API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | array | Array of videos from the channel |
|
||||
|
||||
### `youtube_channel_playlists`
|
||||
|
||||
Get all playlists from a specific YouTube channel.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `channelId` | string | Yes | YouTube channel ID to get playlists from |
|
||||
| `maxResults` | number | No | Maximum number of playlists to return \(1-50\) |
|
||||
| `pageToken` | string | No | Page token for pagination |
|
||||
| `apiKey` | string | Yes | YouTube API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | array | Array of playlists from the channel |
|
||||
|
||||
### `youtube_playlist_items`
|
||||
|
||||
Get videos from a YouTube playlist.
|
||||
@@ -137,6 +187,25 @@ Get videos from a YouTube playlist.
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | array | Array of videos in the playlist |
|
||||
|
||||
### `youtube_related_videos`
|
||||
|
||||
Find videos related to a specific YouTube video.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `videoId` | string | Yes | YouTube video ID to find related videos for |
|
||||
| `maxResults` | number | No | Maximum number of related videos to return \(1-50\) |
|
||||
| `pageToken` | string | No | Page token for pagination |
|
||||
| `apiKey` | string | Yes | YouTube API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | array | Array of related videos |
|
||||
|
||||
### `youtube_comments`
|
||||
|
||||
Get comments from a YouTube video.
|
||||
|
||||
@@ -9,7 +9,7 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
description: 'Interact with YouTube videos, channels, and playlists',
|
||||
authMode: AuthMode.ApiKey,
|
||||
longDescription:
|
||||
'Integrate YouTube into the workflow. Can search for videos, get video details, get channel information, get playlist items, and get video comments.',
|
||||
'Integrate YouTube into the workflow. Can search for videos, get video details, get channel information, get all videos from a channel, get channel playlists, get playlist items, find related videos, and get video comments.',
|
||||
docsLink: 'https://docs.sim.ai/tools/youtube',
|
||||
category: 'tools',
|
||||
bgColor: '#FF0000',
|
||||
@@ -24,7 +24,10 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
{ label: 'Search Videos', id: 'youtube_search' },
|
||||
{ label: 'Get Video Details', id: 'youtube_video_details' },
|
||||
{ label: 'Get Channel Info', id: 'youtube_channel_info' },
|
||||
{ label: 'Get Channel Videos', id: 'youtube_channel_videos' },
|
||||
{ label: 'Get Channel Playlists', id: 'youtube_channel_playlists' },
|
||||
{ label: 'Get Playlist Items', id: 'youtube_playlist_items' },
|
||||
{ label: 'Get Related Videos', id: 'youtube_related_videos' },
|
||||
{ label: 'Get Video Comments', id: 'youtube_comments' },
|
||||
],
|
||||
value: () => 'youtube_search',
|
||||
@@ -50,13 +53,129 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
integer: true,
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'channelId',
|
||||
title: 'Filter by Channel ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Filter results to a specific channel',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'publishedAfter',
|
||||
title: 'Published After',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: '2024-01-01T00:00:00Z',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'publishedBefore',
|
||||
title: 'Published Before',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: '2024-12-31T23:59:59Z',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'videoDuration',
|
||||
title: 'Video Duration',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ label: 'Any', id: 'any' },
|
||||
{ label: 'Short (<4 min)', id: 'short' },
|
||||
{ label: 'Medium (4-20 min)', id: 'medium' },
|
||||
{ label: 'Long (>20 min)', id: 'long' },
|
||||
],
|
||||
value: () => 'any',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ label: 'Relevance', id: 'relevance' },
|
||||
{ label: 'Date (Newest First)', id: 'date' },
|
||||
{ label: 'View Count', id: 'viewCount' },
|
||||
{ label: 'Rating', id: 'rating' },
|
||||
{ label: 'Title', id: 'title' },
|
||||
],
|
||||
value: () => 'relevance',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'videoCategoryId',
|
||||
title: 'Category ID',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: '10 for Music, 20 for Gaming',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'videoDefinition',
|
||||
title: 'Video Quality',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ label: 'Any', id: 'any' },
|
||||
{ label: 'HD', id: 'high' },
|
||||
{ label: 'Standard', id: 'standard' },
|
||||
],
|
||||
value: () => 'any',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'videoCaption',
|
||||
title: 'Captions',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ label: 'Any', id: 'any' },
|
||||
{ label: 'Has Captions', id: 'closedCaption' },
|
||||
{ label: 'No Captions', id: 'none' },
|
||||
],
|
||||
value: () => 'any',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'regionCode',
|
||||
title: 'Region Code',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'US, GB, JP',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'relevanceLanguage',
|
||||
title: 'Language Code',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'en, es, fr',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
{
|
||||
id: 'safeSearch',
|
||||
title: 'Safe Search',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ label: 'Moderate', id: 'moderate' },
|
||||
{ label: 'None', id: 'none' },
|
||||
{ label: 'Strict', id: 'strict' },
|
||||
],
|
||||
value: () => 'moderate',
|
||||
condition: { field: 'operation', value: 'youtube_search' },
|
||||
},
|
||||
// Get Video Details operation inputs
|
||||
{
|
||||
id: 'videoId',
|
||||
title: 'Video ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter YouTube video ID (e.g., dQw4w9WgXcQ)',
|
||||
placeholder: 'Enter YouTube video ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: 'youtube_video_details' },
|
||||
},
|
||||
@@ -77,6 +196,63 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
placeholder: 'Enter channel username (if not using channel ID)',
|
||||
condition: { field: 'operation', value: 'youtube_channel_info' },
|
||||
},
|
||||
// Get Channel Videos operation inputs
|
||||
{
|
||||
id: 'channelId',
|
||||
title: 'Channel ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter YouTube channel ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: 'youtube_channel_videos' },
|
||||
},
|
||||
{
|
||||
id: 'maxResults',
|
||||
title: 'Max Results',
|
||||
type: 'slider',
|
||||
layout: 'half',
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
integer: true,
|
||||
condition: { field: 'operation', value: 'youtube_channel_videos' },
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Date (Newest First)', id: 'date' },
|
||||
{ label: 'Relevance', id: 'relevance' },
|
||||
{ label: 'View Count', id: 'viewCount' },
|
||||
{ label: 'Rating', id: 'rating' },
|
||||
{ label: 'Title', id: 'title' },
|
||||
],
|
||||
value: () => 'date',
|
||||
condition: { field: 'operation', value: 'youtube_channel_videos' },
|
||||
},
|
||||
// Get Channel Playlists operation inputs
|
||||
{
|
||||
id: 'channelId',
|
||||
title: 'Channel ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter YouTube channel ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: 'youtube_channel_playlists' },
|
||||
},
|
||||
{
|
||||
id: 'maxResults',
|
||||
title: 'Max Results',
|
||||
type: 'slider',
|
||||
layout: 'half',
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
integer: true,
|
||||
condition: { field: 'operation', value: 'youtube_channel_playlists' },
|
||||
},
|
||||
// Get Playlist Items operation inputs
|
||||
{
|
||||
id: 'playlistId',
|
||||
@@ -98,6 +274,27 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
integer: true,
|
||||
condition: { field: 'operation', value: 'youtube_playlist_items' },
|
||||
},
|
||||
// Get Related Videos operation inputs
|
||||
{
|
||||
id: 'videoId',
|
||||
title: 'Video ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter YouTube video ID to find related videos',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: 'youtube_related_videos' },
|
||||
},
|
||||
{
|
||||
id: 'maxResults',
|
||||
title: 'Max Results',
|
||||
type: 'slider',
|
||||
layout: 'half',
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
integer: true,
|
||||
condition: { field: 'operation', value: 'youtube_related_videos' },
|
||||
},
|
||||
// Get Video Comments operation inputs
|
||||
{
|
||||
id: 'videoId',
|
||||
@@ -147,7 +344,10 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
'youtube_search',
|
||||
'youtube_video_details',
|
||||
'youtube_channel_info',
|
||||
'youtube_channel_videos',
|
||||
'youtube_channel_playlists',
|
||||
'youtube_playlist_items',
|
||||
'youtube_related_videos',
|
||||
'youtube_comments',
|
||||
],
|
||||
config: {
|
||||
@@ -164,8 +364,14 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
return 'youtube_video_details'
|
||||
case 'youtube_channel_info':
|
||||
return 'youtube_channel_info'
|
||||
case 'youtube_channel_videos':
|
||||
return 'youtube_channel_videos'
|
||||
case 'youtube_channel_playlists':
|
||||
return 'youtube_channel_playlists'
|
||||
case 'youtube_playlist_items':
|
||||
return 'youtube_playlist_items'
|
||||
case 'youtube_related_videos':
|
||||
return 'youtube_related_videos'
|
||||
case 'youtube_comments':
|
||||
return 'youtube_comments'
|
||||
default:
|
||||
@@ -180,6 +386,16 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
// Search Videos
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
maxResults: { type: 'number', description: 'Maximum number of results' },
|
||||
// Search Filters
|
||||
publishedAfter: { type: 'string', description: 'Published after date (RFC 3339)' },
|
||||
publishedBefore: { type: 'string', description: 'Published before date (RFC 3339)' },
|
||||
videoDuration: { type: 'string', description: 'Video duration filter' },
|
||||
videoCategoryId: { type: 'string', description: 'YouTube category ID' },
|
||||
videoDefinition: { type: 'string', description: 'Video quality filter' },
|
||||
videoCaption: { type: 'string', description: 'Caption availability filter' },
|
||||
regionCode: { type: 'string', description: 'Region code (ISO 3166-1)' },
|
||||
relevanceLanguage: { type: 'string', description: 'Language code (ISO 639-1)' },
|
||||
safeSearch: { type: 'string', description: 'Safe search level' },
|
||||
// Video Details & Comments
|
||||
videoId: { type: 'string', description: 'YouTube video ID' },
|
||||
// Channel Info
|
||||
@@ -187,8 +403,8 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
|
||||
username: { type: 'string', description: 'YouTube channel username' },
|
||||
// Playlist Items
|
||||
playlistId: { type: 'string', description: 'YouTube playlist ID' },
|
||||
// Comments
|
||||
order: { type: 'string', description: 'Sort order for comments' },
|
||||
// Sort Order (used by multiple operations)
|
||||
order: { type: 'string', description: 'Sort order' },
|
||||
},
|
||||
outputs: {
|
||||
// Search Videos & Playlist Items
|
||||
|
||||
@@ -224,8 +224,11 @@ import { workflowExecutorTool } from '@/tools/workflow'
|
||||
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from '@/tools/x'
|
||||
import {
|
||||
youtubeChannelInfoTool,
|
||||
youtubeChannelPlaylistsTool,
|
||||
youtubeChannelVideosTool,
|
||||
youtubeCommentsTool,
|
||||
youtubePlaylistItemsTool,
|
||||
youtubeRelatedVideosTool,
|
||||
youtubeSearchTool,
|
||||
youtubeVideoDetailsTool,
|
||||
} from '@/tools/youtube'
|
||||
@@ -289,6 +292,9 @@ export const tools: Record<string, ToolConfig> = {
|
||||
youtube_channel_info: youtubeChannelInfoTool,
|
||||
youtube_playlist_items: youtubePlaylistItemsTool,
|
||||
youtube_comments: youtubeCommentsTool,
|
||||
youtube_channel_videos: youtubeChannelVideosTool,
|
||||
youtube_channel_playlists: youtubeChannelPlaylistsTool,
|
||||
youtube_related_videos: youtubeRelatedVideosTool,
|
||||
notion_read: notionReadTool,
|
||||
notion_read_database: notionReadDatabaseTool,
|
||||
notion_write: notionWriteTool,
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
YouTubeChannelPlaylistsParams,
|
||||
YouTubeChannelPlaylistsResponse,
|
||||
} from '@/tools/youtube/types'
|
||||
|
||||
export const youtubeChannelPlaylistsTool: ToolConfig<
|
||||
YouTubeChannelPlaylistsParams,
|
||||
YouTubeChannelPlaylistsResponse
|
||||
> = {
|
||||
id: 'youtube_channel_playlists',
|
||||
name: 'YouTube Channel Playlists',
|
||||
description: 'Get all playlists from a specific YouTube channel.',
|
||||
version: '1.0.0',
|
||||
params: {
|
||||
channelId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'YouTube channel ID to get playlists from',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
default: 10,
|
||||
description: 'Maximum number of playlists to return (1-50)',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page token for pagination',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'YouTube API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: YouTubeChannelPlaylistsParams) => {
|
||||
let url = `https://www.googleapis.com/youtube/v3/playlists?part=snippet,contentDetails&channelId=${encodeURIComponent(
|
||||
params.channelId
|
||||
)}&key=${params.apiKey}`
|
||||
url += `&maxResults=${params.maxResults || 10}`
|
||||
if (params.pageToken) {
|
||||
url += `&pageToken=${params.pageToken}`
|
||||
}
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<YouTubeChannelPlaylistsResponse> => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.items) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
items: [],
|
||||
totalResults: 0,
|
||||
},
|
||||
error: 'No playlists found',
|
||||
}
|
||||
}
|
||||
|
||||
const items = (data.items || []).map((item: any) => ({
|
||||
playlistId: item.id,
|
||||
title: item.snippet?.title || '',
|
||||
description: item.snippet?.description || '',
|
||||
thumbnail:
|
||||
item.snippet?.thumbnails?.medium?.url ||
|
||||
item.snippet?.thumbnails?.default?.url ||
|
||||
item.snippet?.thumbnails?.high?.url ||
|
||||
'',
|
||||
itemCount: item.contentDetails?.itemCount || 0,
|
||||
publishedAt: item.snippet?.publishedAt || '',
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items,
|
||||
totalResults: data.pageInfo?.totalResults || 0,
|
||||
nextPageToken: data.nextPageToken,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of playlists from the channel',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
playlistId: { type: 'string', description: 'YouTube playlist ID' },
|
||||
title: { type: 'string', description: 'Playlist title' },
|
||||
description: { type: 'string', description: 'Playlist description' },
|
||||
thumbnail: { type: 'string', description: 'Playlist thumbnail URL' },
|
||||
itemCount: { type: 'number', description: 'Number of videos in playlist' },
|
||||
publishedAt: { type: 'string', description: 'Playlist creation date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalResults: {
|
||||
type: 'number',
|
||||
description: 'Total number of playlists in the channel',
|
||||
},
|
||||
nextPageToken: {
|
||||
type: 'string',
|
||||
description: 'Token for accessing the next page of results',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
YouTubeChannelVideosParams,
|
||||
YouTubeChannelVideosResponse,
|
||||
} from '@/tools/youtube/types'
|
||||
|
||||
export const youtubeChannelVideosTool: ToolConfig<
|
||||
YouTubeChannelVideosParams,
|
||||
YouTubeChannelVideosResponse
|
||||
> = {
|
||||
id: 'youtube_channel_videos',
|
||||
name: 'YouTube Channel Videos',
|
||||
description: 'Get all videos from a specific YouTube channel, with sorting options.',
|
||||
version: '1.0.0',
|
||||
params: {
|
||||
channelId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'YouTube channel ID to get videos from',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
default: 10,
|
||||
description: 'Maximum number of videos to return (1-50)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: "date" (newest first), "rating", "relevance", "title", "viewCount"',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page token for pagination',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'YouTube API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: YouTubeChannelVideosParams) => {
|
||||
let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&channelId=${encodeURIComponent(
|
||||
params.channelId
|
||||
)}&key=${params.apiKey}`
|
||||
url += `&maxResults=${params.maxResults || 10}`
|
||||
if (params.order) {
|
||||
url += `&order=${params.order}`
|
||||
}
|
||||
if (params.pageToken) {
|
||||
url += `&pageToken=${params.pageToken}`
|
||||
}
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<YouTubeChannelVideosResponse> => {
|
||||
const data = await response.json()
|
||||
const items = (data.items || []).map((item: any) => ({
|
||||
videoId: item.id?.videoId,
|
||||
title: item.snippet?.title,
|
||||
description: item.snippet?.description,
|
||||
thumbnail:
|
||||
item.snippet?.thumbnails?.medium?.url ||
|
||||
item.snippet?.thumbnails?.default?.url ||
|
||||
item.snippet?.thumbnails?.high?.url ||
|
||||
'',
|
||||
publishedAt: item.snippet?.publishedAt || '',
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items,
|
||||
totalResults: data.pageInfo?.totalResults || 0,
|
||||
nextPageToken: data.nextPageToken,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of videos from the channel',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
videoId: { type: 'string', description: 'YouTube video ID' },
|
||||
title: { type: 'string', description: 'Video title' },
|
||||
description: { type: 'string', description: 'Video description' },
|
||||
thumbnail: { type: 'string', description: 'Video thumbnail URL' },
|
||||
publishedAt: { type: 'string', description: 'Video publish date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalResults: {
|
||||
type: 'number',
|
||||
description: 'Total number of videos in the channel',
|
||||
},
|
||||
nextPageToken: {
|
||||
type: 'string',
|
||||
description: 'Token for accessing the next page of results',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { youtubeChannelInfoTool } from '@/tools/youtube/channel_info'
|
||||
import { youtubeChannelPlaylistsTool } from '@/tools/youtube/channel_playlists'
|
||||
import { youtubeChannelVideosTool } from '@/tools/youtube/channel_videos'
|
||||
import { youtubeCommentsTool } from '@/tools/youtube/comments'
|
||||
import { youtubePlaylistItemsTool } from '@/tools/youtube/playlist_items'
|
||||
import { youtubeRelatedVideosTool } from '@/tools/youtube/related_videos'
|
||||
import { youtubeSearchTool } from '@/tools/youtube/search'
|
||||
import { youtubeVideoDetailsTool } from '@/tools/youtube/video_details'
|
||||
|
||||
@@ -9,3 +12,6 @@ export { youtubeVideoDetailsTool }
|
||||
export { youtubeChannelInfoTool }
|
||||
export { youtubePlaylistItemsTool }
|
||||
export { youtubeCommentsTool }
|
||||
export { youtubeChannelVideosTool }
|
||||
export { youtubeChannelPlaylistsTool }
|
||||
export { youtubeRelatedVideosTool }
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
YouTubeRelatedVideosParams,
|
||||
YouTubeRelatedVideosResponse,
|
||||
} from '@/tools/youtube/types'
|
||||
|
||||
export const youtubeRelatedVideosTool: ToolConfig<
|
||||
YouTubeRelatedVideosParams,
|
||||
YouTubeRelatedVideosResponse
|
||||
> = {
|
||||
id: 'youtube_related_videos',
|
||||
name: 'YouTube Related Videos',
|
||||
description: 'Find videos related to a specific YouTube video.',
|
||||
version: '1.0.0',
|
||||
params: {
|
||||
videoId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'YouTube video ID to find related videos for',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
default: 10,
|
||||
description: 'Maximum number of related videos to return (1-50)',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page token for pagination',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'YouTube API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: YouTubeRelatedVideosParams) => {
|
||||
let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&relatedToVideoId=${encodeURIComponent(
|
||||
params.videoId
|
||||
)}&key=${params.apiKey}`
|
||||
url += `&maxResults=${params.maxResults || 10}`
|
||||
if (params.pageToken) {
|
||||
url += `&pageToken=${params.pageToken}`
|
||||
}
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<YouTubeRelatedVideosResponse> => {
|
||||
const data = await response.json()
|
||||
const items = (data.items || []).map((item: any) => ({
|
||||
videoId: item.id?.videoId,
|
||||
title: item.snippet?.title,
|
||||
description: item.snippet?.description,
|
||||
thumbnail:
|
||||
item.snippet?.thumbnails?.medium?.url ||
|
||||
item.snippet?.thumbnails?.default?.url ||
|
||||
item.snippet?.thumbnails?.high?.url ||
|
||||
'',
|
||||
channelTitle: item.snippet?.channelTitle || '',
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items,
|
||||
totalResults: data.pageInfo?.totalResults || 0,
|
||||
nextPageToken: data.nextPageToken,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of related videos',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
videoId: { type: 'string', description: 'YouTube video ID' },
|
||||
title: { type: 'string', description: 'Video title' },
|
||||
description: { type: 'string', description: 'Video description' },
|
||||
thumbnail: { type: 'string', description: 'Video thumbnail URL' },
|
||||
channelTitle: { type: 'string', description: 'Channel name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalResults: {
|
||||
type: 'number',
|
||||
description: 'Total number of related videos available',
|
||||
},
|
||||
nextPageToken: {
|
||||
type: 'string',
|
||||
description: 'Token for accessing the next page of results',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import type { YouTubeSearchParams, YouTubeSearchResponse } from '@/tools/youtube
|
||||
export const youtubeSearchTool: ToolConfig<YouTubeSearchParams, YouTubeSearchResponse> = {
|
||||
id: 'youtube_search',
|
||||
name: 'YouTube Search',
|
||||
description: 'Search for videos on YouTube using the YouTube Data API.',
|
||||
description:
|
||||
'Search for videos on YouTube using the YouTube Data API. Supports advanced filtering by channel, date range, duration, category, quality, captions, and more.',
|
||||
version: '1.0.0',
|
||||
params: {
|
||||
query: {
|
||||
@@ -18,7 +19,7 @@ export const youtubeSearchTool: ToolConfig<YouTubeSearchParams, YouTubeSearchRes
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
default: 5,
|
||||
description: 'Maximum number of videos to return',
|
||||
description: 'Maximum number of videos to return (1-50)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
@@ -26,6 +27,80 @@ export const youtubeSearchTool: ToolConfig<YouTubeSearchParams, YouTubeSearchRes
|
||||
visibility: 'user-only',
|
||||
description: 'YouTube API Key',
|
||||
},
|
||||
// Priority 1: Essential filters
|
||||
channelId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter results to a specific YouTube channel ID',
|
||||
},
|
||||
publishedAfter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Only return videos published after this date (RFC 3339 format: "2024-01-01T00:00:00Z")',
|
||||
},
|
||||
publishedBefore: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Only return videos published before this date (RFC 3339 format: "2024-01-01T00:00:00Z")',
|
||||
},
|
||||
videoDuration: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by video length: "short" (<4 min), "medium" (4-20 min), "long" (>20 min), "any"',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort results by: "date", "rating", "relevance" (default), "title", "videoCount", "viewCount"',
|
||||
},
|
||||
videoCategoryId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by YouTube category ID (e.g., "10" for Music, "20" for Gaming)',
|
||||
},
|
||||
// Priority 2: Very useful filters
|
||||
videoDefinition: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by video quality: "high" (HD), "standard", "any"',
|
||||
},
|
||||
videoCaption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by caption availability: "closedCaption" (has captions), "none" (no captions), "any"',
|
||||
},
|
||||
regionCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Return results relevant to a specific region (ISO 3166-1 alpha-2 country code, e.g., "US", "GB")',
|
||||
},
|
||||
relevanceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Return results most relevant to a language (ISO 639-1 code, e.g., "en", "es")',
|
||||
},
|
||||
safeSearch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Content filtering level: "moderate" (default), "none", "strict"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
@@ -34,6 +109,44 @@ export const youtubeSearchTool: ToolConfig<YouTubeSearchParams, YouTubeSearchRes
|
||||
params.query
|
||||
)}`
|
||||
url += `&maxResults=${params.maxResults || 5}`
|
||||
|
||||
// Add Priority 1 filters if provided
|
||||
if (params.channelId) {
|
||||
url += `&channelId=${encodeURIComponent(params.channelId)}`
|
||||
}
|
||||
if (params.publishedAfter) {
|
||||
url += `&publishedAfter=${encodeURIComponent(params.publishedAfter)}`
|
||||
}
|
||||
if (params.publishedBefore) {
|
||||
url += `&publishedBefore=${encodeURIComponent(params.publishedBefore)}`
|
||||
}
|
||||
if (params.videoDuration) {
|
||||
url += `&videoDuration=${params.videoDuration}`
|
||||
}
|
||||
if (params.order) {
|
||||
url += `&order=${params.order}`
|
||||
}
|
||||
if (params.videoCategoryId) {
|
||||
url += `&videoCategoryId=${params.videoCategoryId}`
|
||||
}
|
||||
|
||||
// Add Priority 2 filters if provided
|
||||
if (params.videoDefinition) {
|
||||
url += `&videoDefinition=${params.videoDefinition}`
|
||||
}
|
||||
if (params.videoCaption) {
|
||||
url += `&videoCaption=${params.videoCaption}`
|
||||
}
|
||||
if (params.regionCode) {
|
||||
url += `®ionCode=${params.regionCode}`
|
||||
}
|
||||
if (params.relevanceLanguage) {
|
||||
url += `&relevanceLanguage=${params.relevanceLanguage}`
|
||||
}
|
||||
if (params.safeSearch) {
|
||||
url += `&safeSearch=${params.safeSearch}`
|
||||
}
|
||||
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
|
||||
@@ -5,6 +5,17 @@ export interface YouTubeSearchParams {
|
||||
query: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
channelId?: string
|
||||
publishedAfter?: string
|
||||
publishedBefore?: string
|
||||
videoDuration?: 'any' | 'short' | 'medium' | 'long'
|
||||
order?: 'date' | 'rating' | 'relevance' | 'title' | 'videoCount' | 'viewCount'
|
||||
videoCategoryId?: string
|
||||
videoDefinition?: 'any' | 'high' | 'standard'
|
||||
videoCaption?: 'any' | 'closedCaption' | 'none'
|
||||
regionCode?: string
|
||||
relevanceLanguage?: string
|
||||
safeSearch?: 'moderate' | 'none' | 'strict'
|
||||
}
|
||||
|
||||
export interface YouTubeSearchResponse extends ToolResponse {
|
||||
@@ -111,9 +122,77 @@ export interface YouTubeCommentsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface YouTubeChannelVideosParams {
|
||||
apiKey: string
|
||||
channelId: string
|
||||
maxResults?: number
|
||||
order?: 'date' | 'rating' | 'relevance' | 'title' | 'viewCount'
|
||||
pageToken?: string
|
||||
}
|
||||
|
||||
export interface YouTubeChannelVideosResponse extends ToolResponse {
|
||||
output: {
|
||||
items: Array<{
|
||||
videoId: string
|
||||
title: string
|
||||
description: string
|
||||
thumbnail: string
|
||||
publishedAt: string
|
||||
}>
|
||||
totalResults: number
|
||||
nextPageToken?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface YouTubeChannelPlaylistsParams {
|
||||
apiKey: string
|
||||
channelId: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
}
|
||||
|
||||
export interface YouTubeChannelPlaylistsResponse extends ToolResponse {
|
||||
output: {
|
||||
items: Array<{
|
||||
playlistId: string
|
||||
title: string
|
||||
description: string
|
||||
thumbnail: string
|
||||
itemCount: number
|
||||
publishedAt: string
|
||||
}>
|
||||
totalResults: number
|
||||
nextPageToken?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface YouTubeRelatedVideosParams {
|
||||
apiKey: string
|
||||
videoId: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
}
|
||||
|
||||
export interface YouTubeRelatedVideosResponse extends ToolResponse {
|
||||
output: {
|
||||
items: Array<{
|
||||
videoId: string
|
||||
title: string
|
||||
description: string
|
||||
thumbnail: string
|
||||
channelTitle: string
|
||||
}>
|
||||
totalResults: number
|
||||
nextPageToken?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type YouTubeResponse =
|
||||
| YouTubeSearchResponse
|
||||
| YouTubeVideoDetailsResponse
|
||||
| YouTubeChannelInfoResponse
|
||||
| YouTubePlaylistItemsResponse
|
||||
| YouTubeCommentsResponse
|
||||
| YouTubeChannelVideosResponse
|
||||
| YouTubeChannelPlaylistsResponse
|
||||
| YouTubeRelatedVideosResponse
|
||||
|
||||
Reference in New Issue
Block a user