239 lines
8.6 KiB
Go
239 lines
8.6 KiB
Go
// Copyright 2025 eat-pray-ai & OpenWaygate
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package search
|
|
|
|
import (
|
|
"encoding/json/jsontext"
|
|
"io"
|
|
|
|
cobramcp "github.com/eat-pray-ai/cobra-mcp"
|
|
"github.com/eat-pray-ai/yutu/cmd"
|
|
"github.com/eat-pray-ai/yutu/pkg"
|
|
"github.com/eat-pray-ai/yutu/pkg/search"
|
|
"github.com/eat-pray-ai/yutu/pkg/utils"
|
|
"github.com/google/jsonschema-go/jsonschema"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
listTool = "search-list"
|
|
listShort = "Search resources"
|
|
listLong = "Search resources. Use this tool to search for videos, channels, playlists, and other resources."
|
|
listExample = `# Search for videos by keyword
|
|
yutu search list --q 'golang tutorial' --maxResults 10
|
|
# Search videos by duration in JSON format
|
|
yutu search list --q 'music' --types video --videoDuration medium --output json
|
|
# List latest videos from a channel
|
|
yutu search list --channelId UC_x5XG1OV2P6uZZ5FSM9Ttw --order date --maxResults 5
|
|
# Search for live streams
|
|
yutu search list --q 'live coding' --eventType live --types video`
|
|
)
|
|
|
|
var listInSchema = &jsonschema.Schema{
|
|
Type: "object",
|
|
Required: []string{},
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"channel_id": {Type: "string", Description: cidUsage},
|
|
"channel_type": {
|
|
Type: "string", Enum: []any{"channelTypeUnspecified", "any", "show"},
|
|
Description: ctUsage, Default: jsontext.Value(`"channelTypeUnspecified"`),
|
|
},
|
|
"event_type": {
|
|
Type: "string", Enum: []any{"none", "upcoming", "live", "completed"},
|
|
Description: etUsage, Default: jsontext.Value(`"none"`),
|
|
},
|
|
"for": {
|
|
Type: "string", Description: forUsage,
|
|
Enum: []any{"mine", "developer", "contentOwner"},
|
|
},
|
|
"location": {Type: "string", Description: locationUsage},
|
|
"location_radius": {Type: "string", Description: lrUsage},
|
|
"max_results": {
|
|
Type: "number", Description: pkg.MRUsage,
|
|
Default: jsontext.Value("5"),
|
|
Minimum: new(float64(0)),
|
|
},
|
|
"on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage},
|
|
"order": {
|
|
Type: "string", Description: orderUsage,
|
|
Default: jsontext.Value(`"relevance"`),
|
|
Enum: []any{
|
|
"searchSortUnspecified", "date", "rating",
|
|
"viewCount", "relevance", "title", "videoCount",
|
|
},
|
|
},
|
|
"published_after": {Type: "string", Description: paUsage},
|
|
"published_before": {Type: "string", Description: pbUsage},
|
|
"q": {Type: "string", Description: qUsage},
|
|
"region_code": {Type: "string", Description: rcUsage},
|
|
"relevance_language": {Type: "string", Description: rlUsage},
|
|
"safe_search": {
|
|
Type: "string", Description: ssUsage,
|
|
Enum: []any{
|
|
"safeSearchSettingUnspecified", "none", "moderate", "strict",
|
|
},
|
|
Default: jsontext.Value(`"moderate"`),
|
|
},
|
|
"topic_id": {Type: "string", Description: tidUsage},
|
|
"types": {
|
|
Type: "array", Description: typesUsage,
|
|
Items: &jsonschema.Schema{Type: "string"},
|
|
},
|
|
"video_caption": {
|
|
Type: "string", Description: vcUsage,
|
|
Enum: []any{
|
|
"videoCaptionUnspecified", "any", "closedCaption", "none",
|
|
},
|
|
Default: jsontext.Value(`"any"`),
|
|
},
|
|
"video_category_id": {Type: "string", Description: vcidUsage},
|
|
"video_definition": {
|
|
Type: "string", Description: vdeUsage,
|
|
Enum: []any{"any", "high", "standard"},
|
|
},
|
|
"video_dimension": {
|
|
Type: "string", Enum: []any{"any", "2d", "3d"},
|
|
Description: vdiUsage, Default: jsontext.Value(`"any"`),
|
|
},
|
|
"video_duration": {
|
|
Type: "string", Description: vduUsage,
|
|
Enum: []any{
|
|
"videoDurationUnspecified", "any", "short", "medium", "long",
|
|
},
|
|
Default: jsontext.Value(`"any"`),
|
|
},
|
|
"video_embeddable": {
|
|
Type: "string", Description: veUsage,
|
|
Enum: []any{"videoEmbeddableUnspecified", "any", "true"},
|
|
},
|
|
"video_license": {
|
|
Type: "string", Description: vlUsage,
|
|
Enum: []any{"any", "youtube", "creativeCommon"},
|
|
},
|
|
"video_paid_product_placement": {
|
|
Type: "string", Description: vpppUsage,
|
|
Enum: []any{
|
|
"videoPaidProductPlacementUnspecified", "any", "true",
|
|
},
|
|
},
|
|
"video_syndicated": {
|
|
Type: "string", Description: vsUsage,
|
|
Enum: []any{"videoSyndicatedUnspecified", "any", "true"},
|
|
},
|
|
"video_type": {
|
|
Type: "string", Description: vtUsage,
|
|
Enum: []any{"videoTypeUnspecified", "any", "movie", "episode"},
|
|
},
|
|
"parts": {
|
|
Type: "array", Description: pkg.PartsUsage,
|
|
Items: &jsonschema.Schema{Type: "string"},
|
|
Default: jsontext.Value(`["id","snippet"]`),
|
|
},
|
|
"output": {
|
|
Type: "string", Enum: []any{"json", "yaml", "table"},
|
|
Description: pkg.TableUsage, Default: jsontext.Value(`"json"`),
|
|
},
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
mcp.AddTool(
|
|
cmd.Server, &mcp.Tool{
|
|
Name: listTool, Title: listShort, Description: listLong,
|
|
InputSchema: listInSchema, Annotations: &mcp.ToolAnnotations{
|
|
DestructiveHint: new(false),
|
|
IdempotentHint: true,
|
|
OpenWorldHint: new(true),
|
|
ReadOnlyHint: true,
|
|
},
|
|
}, cobramcp.GenToolHandler(
|
|
listTool, func(input search.Search, writer io.Writer) error {
|
|
return input.List(writer)
|
|
},
|
|
),
|
|
)
|
|
searchCmd.AddCommand(listCmd)
|
|
|
|
listCmd.Flags().StringVar(&channelId, "channelId", "", cidUsage)
|
|
listCmd.Flags().StringVar(
|
|
&channelType, "channelType", "channelTypeUnspecified", ctUsage,
|
|
)
|
|
listCmd.Flags().StringVar(&eventType, "eventType", "none", etUsage)
|
|
listCmd.Flags().StringVar(&searchFor, "for", "", forUsage)
|
|
listCmd.Flags().StringVar(&location, "location", "", locationUsage)
|
|
listCmd.Flags().StringVar(&locationRadius, "locationRadius", "", lrUsage)
|
|
listCmd.Flags().Int64Var(&maxResults, "maxResults", 5, pkg.MRUsage)
|
|
listCmd.Flags().StringVar(
|
|
&onBehalfOfContentOwner, "onBehalfOfContentOwner", "", pkg.OBOCOUsage,
|
|
)
|
|
listCmd.Flags().StringVar(&order, "order", "relevance", orderUsage)
|
|
listCmd.Flags().StringVar(&publishedAfter, "publishedAfter", "", paUsage)
|
|
listCmd.Flags().StringVar(&publishedBefore, "publishedBefore", "", pbUsage)
|
|
listCmd.Flags().StringVar(&q, "q", "", qUsage)
|
|
listCmd.Flags().StringVar(®ionCode, "regionCode", "", rcUsage)
|
|
listCmd.Flags().StringVar(&relevanceLanguage, "relevanceLanguage", "", rlUsage)
|
|
listCmd.Flags().StringVar(&safeSearch, "safeSearch", "moderate", ssUsage)
|
|
listCmd.Flags().StringVar(&topicId, "topicId", "", tidUsage)
|
|
listCmd.Flags().StringSliceVar(&types, "types", []string{}, typesUsage)
|
|
listCmd.Flags().StringVar(&videoCaption, "videoCaption", "any", vcUsage)
|
|
listCmd.Flags().StringVar(&videoCategoryId, "videoCategoryId", "", vcidUsage)
|
|
listCmd.Flags().StringVar(&videoDefinition, "videoDefinition", "", vdeUsage)
|
|
listCmd.Flags().StringVar(&videoDimension, "videoDimension", "any", vdiUsage)
|
|
listCmd.Flags().StringVar(&videoDuration, "videoDuration", "any", vduUsage)
|
|
listCmd.Flags().StringVar(&videoEmbeddable, "videoEmbeddable", "", veUsage)
|
|
listCmd.Flags().StringVar(&videoLicense, "videoLicense", "", vlUsage)
|
|
listCmd.Flags().StringVar(
|
|
&videoPaidProductPlacement, "videoPaidProductPlacement", "", vpppUsage,
|
|
)
|
|
listCmd.Flags().StringVar(&videoSyndicated, "videoSyndicated", "", vsUsage)
|
|
listCmd.Flags().StringVar(&videoType, "videoType", "", vtUsage)
|
|
listCmd.Flags().StringSliceVar(
|
|
&parts, "parts", []string{"id", "snippet"}, pkg.PartsUsage,
|
|
)
|
|
listCmd.Flags().String("output", "table", pkg.TableUsage)
|
|
}
|
|
|
|
var listCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: listShort,
|
|
Long: listLong,
|
|
Example: listExample,
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
output, _ := cmd.Flags().GetString("output")
|
|
input := search.NewSearch(
|
|
search.WithChannelId(channelId),
|
|
search.WithChannelType(channelType),
|
|
search.WithEventType(eventType),
|
|
search.WithFor(searchFor),
|
|
search.WithLocation(location),
|
|
search.WithLocationRadius(locationRadius),
|
|
search.WithMaxResults(maxResults),
|
|
search.WithOnBehalfOfContentOwner(onBehalfOfContentOwner),
|
|
search.WithOrder(order),
|
|
search.WithPublishedAfter(publishedAfter),
|
|
search.WithPublishedBefore(publishedBefore),
|
|
search.WithQ(q),
|
|
search.WithRegionCode(regionCode),
|
|
search.WithRelevanceLanguage(relevanceLanguage),
|
|
search.WithSafeSearch(safeSearch),
|
|
search.WithTopicId(topicId),
|
|
search.WithTypes(types),
|
|
search.WithVideoCaption(videoCaption),
|
|
search.WithVideoCategoryId(videoCategoryId),
|
|
search.WithVideoDefinition(videoDefinition),
|
|
search.WithVideoDimension(videoDimension),
|
|
search.WithVideoDuration(videoDuration),
|
|
search.WithVideoEmbeddable(videoEmbeddable),
|
|
search.WithVideoLicense(videoLicense),
|
|
search.WithVideoPaidProductPlacement(videoPaidProductPlacement),
|
|
search.WithVideoSyndicated(videoSyndicated),
|
|
search.WithVideoType(videoType),
|
|
search.WithParts(parts),
|
|
search.WithOutput(output),
|
|
)
|
|
utils.HandleCmdError(input.List(cmd.OutOrStdout()), cmd)
|
|
},
|
|
}
|