Files

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(&regionCode, "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)
},
}