Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions internal/command/root_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,16 @@ func (r *Root) newTiktokCommand(runCtx *runContext) *cobra.Command {
return newStaticSourceCommand(
"tiktok",
"TikTok video, comment, and shop data",
"TikTok video, comment, and shop data.\n\nUse this command to search TikTok videos, inspect video details, list comments, and query TikTok Shop products and product details.",
" apimux tiktok search_videos --keyword laptop\n apimux tiktok get_video_detail --share-url https://www.tiktok.com/t/ZTFNEj8Hk/\n apimux tiktok shop_products --seller-id 123456",
"search_videos, get_video_detail, list_comments, shop_products, shop_product_info",
"TikTok video, comment, and shop data.\n\nUse this command to search TikTok videos, inspect video details, list comments, search TikTok Shop products, query seller products and product details, and browse product reviews.",
" apimux tiktok search_videos --keyword laptop\n apimux tiktok get_video_detail --share-url https://www.tiktok.com/t/ZTFNEj8Hk/\n apimux tiktok shop_products --seller-id 123456\n apimux tiktok search_products --keyword labubu --region US\n apimux tiktok product_reviews --product-id 1729556436942358002 --region US",
"search_videos, get_video_detail, list_comments, shop_products, shop_product_info, search_products, product_reviews",
newSchemaBoundCapabilityCommand(runCtx, "tiktok.search_videos", "search_videos", "Search TikTok videos", "tiktok search_videos"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.get_video_detail", "get_video_detail", "Fetch one TikTok video detail", "tiktok get_video_detail"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.list_comments", "list_comments", "List TikTok video comments", "tiktok list_comments"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.shop_products", "shop_products", "List TikTok Shop seller products", "tiktok shop_products"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.shop_product_info", "shop_product_info", "Fetch one TikTok Shop product detail", "tiktok shop_product_info"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.search_products", "search_products", "Search TikTok Shop products", "tiktok search_products"),
newSchemaBoundCapabilityCommand(runCtx, "tiktok.product_reviews", "product_reviews", "List TikTok Shop product reviews", "tiktok product_reviews"),
)
}

Expand Down
64 changes: 64 additions & 0 deletions internal/command/root_sources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,70 @@ func TestTikTokGetVideoDetailCallsService(t *testing.T) {
}
}

func TestTikTokSearchProductsCallsService(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if maybeServeSchema(w, r) {
return
}
if r.URL.Path != "/v1/capabilities/tiktok.search_products" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
_, _ = w.Write([]byte(`{"ok":true,"data":[{"product_id":"p-1","product_name":"Labubu Plush","product_sold_count":10,"format_available_price":"$9.99","format_origin_price":"$12.99","discount":"20% off","rating":4.6,"review_count":42}],"meta":{"capability":"tiktok.search_products","cursor":"next-page","has_more":true}}`))
}))
defer server.Close()

var stdout bytes.Buffer
var stderr bytes.Buffer

root := NewRoot(&stdout, &stderr)
exitCode, err := root.Execute(context.Background(), []string{
"--base-url", server.URL,
"tiktok", "search_products",
"--keyword", "labubu",
"--region", "US",
})
if err != nil {
t.Fatalf("execute root: %v", err)
}
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d, stderr=%s", exitCode, stderr.String())
}
assertCompactTableOutputContains(t, stdout.String(), `"columns":["product_id","product_name","product_sold_count","format_available_price","format_origin_price","discount","rating","review_count"]`, `"p-1"`)
}

func TestTikTokProductReviewsCallsService(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if maybeServeSchema(w, r) {
return
}
if r.URL.Path != "/v1/capabilities/tiktok.product_reviews" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
_, _ = w.Write([]byte(`{"ok":true,"data":[{"review_id":"r-1","rating":5,"verified_purchase":true,"like_count":3,"create_time":"2023-11-14T22:13:20Z","content":"Great!"}],"meta":{"capability":"tiktok.product_reviews","has_more":true,"total":120}}`))
}))
defer server.Close()

var stdout bytes.Buffer
var stderr bytes.Buffer

root := NewRoot(&stdout, &stderr)
exitCode, err := root.Execute(context.Background(), []string{
"--base-url", server.URL,
"tiktok", "product_reviews",
"--product-id", "prod-1",
"--sort", "latest",
"--media-filter", "media",
"--star", "5",
})
if err != nil {
t.Fatalf("execute root: %v", err)
}
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d, stderr=%s", exitCode, stderr.String())
}
assertCompactTableOutputContains(t, stdout.String(), `"columns":["review_id","rating","verified_purchase","like_count","create_time","content"]`, `"r-1"`)
}

func TestMetaAdsSearchCallsService(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if maybeServeSchema(w, r) {
Expand Down
5 changes: 5 additions & 0 deletions internal/command/schema_test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func testSchemas() map[string]schema.CapabilitySchema {
"amazon.get_category_trend": amazonSchema("amazon.get_category_trend", []schema.CapabilityParam{{Name: "node_id", Type: "string", Required: true, FlagName: "node-id"}, {Name: "market", Type: "string"}, {Name: "trend_types", Type: "array", Required: true, ItemsType: "string", Encoding: "csv", FlagName: "trend-types"}}),
"tiktok.search_videos": {Name: "tiktok.search_videos", Parameters: []schema.CapabilityParam{{Name: "keyword", Type: "string", Required: true}, {Name: "region", Type: "string"}, {Name: "sort_by", Type: "string", Enum: []string{"relevance", "likes", "date"}, FlagName: "sort-by"}, {Name: "publish_time", Type: "string", Enum: []string{"all", "1d", "1w", "1m", "3m", "6m"}, FlagName: "publish-time"}, {Name: "cursor", Type: "integer"}, {Name: "count", Type: "integer"}}},
"tiktok.get_video_detail": {Name: "tiktok.get_video_detail", Parameters: []schema.CapabilityParam{{Name: "share_url", Type: "string", FlagName: "share-url"}, {Name: "aweme_id", Type: "string", FlagName: "aweme-id"}, {Name: "region", Type: "string"}}},
"tiktok.list_comments": {Name: "tiktok.list_comments", Parameters: []schema.CapabilityParam{{Name: "video_id", Type: "string", Required: true, FlagName: "video-id"}, {Name: "cursor", Type: "integer"}, {Name: "count", Type: "integer"}}},
"tiktok.shop_products": {Name: "tiktok.shop_products", Parameters: []schema.CapabilityParam{{Name: "seller_id", Type: "string", Required: true, FlagName: "seller-id"}, {Name: "region", Type: "string"}, {Name: "sort", Type: "string", Enum: []string{"sale", "rec"}}, {Name: "top_n", Type: "integer", FlagName: "top-n"}}},
"tiktok.shop_product_info": {Name: "tiktok.shop_product_info", Parameters: []schema.CapabilityParam{{Name: "product_id", Type: "string", Required: true, FlagName: "product-id"}, {Name: "region", Type: "string"}}},
"tiktok.search_products": {Name: "tiktok.search_products", Parameters: []schema.CapabilityParam{{Name: "keyword", Type: "string", Required: true}, {Name: "region", Type: "string", Enum: []string{"US", "GB", "SG", "MY", "PH", "TH", "VN", "ID"}}, {Name: "cursor", Type: "string"}, {Name: "offset", Type: "integer"}, {Name: "count", Type: "integer"}}},
"tiktok.product_reviews": {Name: "tiktok.product_reviews", Parameters: []schema.CapabilityParam{{Name: "product_id", Type: "string", Required: true, FlagName: "product-id"}, {Name: "region", Type: "string", Enum: []string{"US", "GB", "SG", "MY", "PH", "TH", "VN", "ID"}}, {Name: "page", Type: "integer"}, {Name: "sort", Type: "string", Enum: []string{"default", "latest"}}, {Name: "media_filter", Type: "string", Enum: []string{"all", "media", "verified"}, FlagName: "media-filter"}, {Name: "star", Type: "string", Enum: []string{"all", "1", "2", "3", "4", "5"}}, {Name: "count", Type: "integer"}}},
"meta_ads.search_ads": {Name: "meta_ads.search_ads", Parameters: []schema.CapabilityParam{{Name: "q", Type: "string", Required: true}, {Name: "country", Type: "string"}, {Name: "ad_type", Type: "string", Enum: []string{"all", "political_and_issue_ads", "housing_ads", "employment_ads", "credit_ads"}, FlagName: "ad-type"}, {Name: "active_status", Type: "string", Enum: []string{"active", "inactive", "all"}, FlagName: "active-status"}, {Name: "media_type", Type: "string", Enum: []string{"all", "video", "image", "meme", "image_and_meme", "none"}, FlagName: "media-type"}, {Name: "platforms", Type: "string"}, {Name: "start_date", Type: "string", FlagName: "start-date"}, {Name: "end_date", Type: "string", FlagName: "end-date"}, {Name: "next_page_token", Type: "string", FlagName: "next-page-token"}}},
"meta_ads.get_ad_detail": {Name: "meta_ads.get_ad_detail", Parameters: []schema.CapabilityParam{{Name: "ad_id", Type: "string", Required: true, FlagName: "ad-id"}}},
"douyin.search_videos": {Name: "douyin.search_videos", Parameters: []schema.CapabilityParam{{Name: "keyword", Type: "string", Required: true}, {Name: "sort_type", Type: "string", Enum: []string{"comprehensive", "likes", "latest"}, FlagName: "sort-type"}, {Name: "publish_time", Type: "string", Enum: []string{"all", "1d", "1w", "6m"}, FlagName: "publish-time"}, {Name: "filter_duration", Type: "string", Enum: []string{"all", "under_1m", "1m_5m", "over_5m"}, FlagName: "filter-duration"}, {Name: "content_type", Type: "string", Enum: []string{"all", "video", "image", "article"}, FlagName: "content-type"}, {Name: "cursor", Type: "integer"}}},
Expand Down
40 changes: 40 additions & 0 deletions internal/output/projection_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,46 @@ var projectionRules = map[string]projectionRule{
},
},
},
"tiktok.search_products": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 10,
Columns: []fieldRule{
{From: "product_id", To: "product_id"},
{From: "product_name", To: "product_name"},
{From: "product_sold_count", To: "product_sold_count"},
{From: "format_available_price", To: "format_available_price"},
{From: "format_origin_price", To: "format_origin_price"},
{From: "discount", To: "discount"},
{From: "rating", To: "rating"},
{From: "review_count", To: "review_count"},
},
},
},
},
},
"tiktok.product_reviews": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 10,
Columns: []fieldRule{
{From: "review_id", To: "review_id"},
{From: "rating", To: "rating"},
{From: "verified_purchase", To: "verified_purchase"},
{From: "like_count", To: "like_count"},
{From: "create_time", To: "create_time"},
{From: "content", To: "content"},
},
},
},
},
},
"xiaohongshu.get_note_comments": {
Compact: projectionSpec{
Tables: []tableRule{
Expand Down
2 changes: 2 additions & 0 deletions internal/output/projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,8 @@ func TestProjectionRulesCoverAllAgentTestCapabilities(t *testing.T) {
"reddit.get_post_comments",
"tiktok.shop_products",
"tiktok.shop_product_info",
"tiktok.search_products",
"tiktok.product_reviews",
"xiaohongshu.get_note_comments",
"douyin.get_comment_replies",
}
Expand Down
Loading
Loading