From 680e54b673290d51dcee658b6a07cdc0231eb794 Mon Sep 17 00:00:00 2001 From: Plateau Nguyen Date: Fri, 24 Apr 2026 09:11:59 +0700 Subject: [PATCH] feat(pancake): add TikTok sub-platform support (#990) Add TikTok account type dropdown (Livestream AIO, Business Messaging, TikTok Shop) to Pancake channel config with prefix-aware webhook routing. - Add tt, ttm, tts prefixes to platformPrefixes map for convID parsing - Add TikTokType field to pancakeInstanceConfig (optional, backward compat) - Add tiktok_type field to UI with showWhen platform=tiktok - Add i18n labels for en/vi/zh locales Co-authored-by: viettranx --- internal/channels/pancake/types.go | 9 +++++---- internal/channels/pancake/webhook_handler.go | 3 +++ internal/channels/pancake/webhook_handler_test.go | 5 +++++ ui/web/src/i18n/locales/en/channels.json | 1 + ui/web/src/i18n/locales/vi/channels.json | 1 + ui/web/src/i18n/locales/zh/channels.json | 1 + ui/web/src/pages/channels/channel-schemas.ts | 7 +++++++ 7 files changed, 23 insertions(+), 4 deletions(-) diff --git a/internal/channels/pancake/types.go b/internal/channels/pancake/types.go index 610d43d4..7aa7ebaf 100644 --- a/internal/channels/pancake/types.go +++ b/internal/channels/pancake/types.go @@ -16,10 +16,11 @@ type pancakeCreds struct { type pancakeInstanceConfig struct { PageID string `json:"page_id"` WebhookPageID string `json:"webhook_page_id,omitempty"` // native platform page ID sent in webhooks (e.g. Facebook page ID vs Pancake internal ID) - Platform string `json:"platform,omitempty"` // set explicitly via UI; auto-detected at Start() as fallback for existing channels + Platform string `json:"platform,omitempty"` // set explicitly via UI; auto-detected at Start() as fallback for existing channels // Known values: facebook/instagram/threads/tiktok/youtube/shopee/line/google/chat_plugin/lazada/tokopedia // Excluded (have native channel implementations): telegram/zalo/whatsapp - Features struct { + TikTokType string `json:"tiktok_type,omitempty"` // livestream|messaging|shop — only meaningful when Platform=tiktok + Features struct { InboxReply bool `json:"inbox_reply"` CommentReply bool `json:"comment_reply"` PrivateReply bool `json:"private_reply"` // send one-time DM to commenter (after comment reply or standalone) @@ -30,7 +31,7 @@ type pancakeInstanceConfig struct { Filter string `json:"filter"` // "all" | "keyword" (default: all) Keywords []string `json:"keywords"` // required when filter = "keyword" } `json:"comment_reply_options"` - PrivateReplyMessage string `json:"private_reply_message,omitempty"` // custom DM text; defaults to built-in message. Supports {{commenter_name}} / {{post_title}} vars. + PrivateReplyMessage string `json:"private_reply_message,omitempty"` // custom DM text; defaults to built-in message. Supports {{commenter_name}} / {{post_title}} vars. AutoReactOptions *AutoReactOptions `json:"auto_react_options,omitempty"` PostContextCacheTTL string `json:"post_context_cache_ttl,omitempty"` // e.g. "30m"; defaults to 15m AllowFrom []string `json:"allow_from,omitempty"` @@ -134,7 +135,7 @@ type PageInfo struct { type SendMessageRequest struct { Action string `json:"action"` Message string `json:"message,omitempty"` - MessageID string `json:"message_id,omitempty"` // required for reply_comment: ID of the comment being replied to + MessageID string `json:"message_id,omitempty"` // required for reply_comment: ID of the comment being replied to ContentIDs []string `json:"content_ids,omitempty"` } diff --git a/internal/channels/pancake/webhook_handler.go b/internal/channels/pancake/webhook_handler.go index fd5c59e2..ca7f03f1 100644 --- a/internal/channels/pancake/webhook_handler.go +++ b/internal/channels/pancake/webhook_handler.go @@ -261,6 +261,9 @@ var ( platformPrefixesMu sync.RWMutex platformPrefixes = map[string]struct{}{ "spo": {}, // Shopee — verified via curl 2026-04-20 + "tt": {}, // TikTok Livestream AIO + "ttm": {}, // TikTok Business Messaging + "tts": {}, // TikTok Shop } ) diff --git a/internal/channels/pancake/webhook_handler_test.go b/internal/channels/pancake/webhook_handler_test.go index 9f4dc478..d6c056ef 100644 --- a/internal/channels/pancake/webhook_handler_test.go +++ b/internal/channels/pancake/webhook_handler_test.go @@ -13,6 +13,11 @@ func TestResolvePageIDFromConvID(t *testing.T) { {"facebook_numeric", "123456_789012", "123456"}, {"shopee_prefixed", "spo_25409726_109139680425439630", "spo_25409726"}, {"shopee_system_2_segments", "spo_25409726", "spo_25409726"}, // M2: system event w/o sender — return as-is + // TikTok variants (tt=Livestream AIO, ttm=Business Messaging, tts=TikTok Shop) + {"tiktok_livestream", "tt_12345678_987654321", "tt_12345678"}, + {"tiktok_messaging", "ttm_12345678_987654321", "ttm_12345678"}, + {"tiktok_shop", "tts_12345678_987654321", "tts_12345678"}, + {"tiktok_system_2_segments", "tt_12345678", "tt_12345678"}, // system event w/o sender {"empty_input", "", ""}, {"no_underscore", "abcdef", ""}, {"prefix_only_no_underscore", "spo", ""}, // regression: prefix-only without underscore diff --git a/ui/web/src/i18n/locales/en/channels.json b/ui/web/src/i18n/locales/en/channels.json index f470c290..c9208541 100644 --- a/ui/web/src/i18n/locales/en/channels.json +++ b/ui/web/src/i18n/locales/en/channels.json @@ -318,6 +318,7 @@ }, "system_prompt": { "label": "System Prompt" }, "platform": { "label": "Platform", "help": "Select the platform this Pancake page serves." }, + "tiktok_type": { "label": "TikTok Type", "help": "Select the TikTok account type for this page" }, "features.private_reply": { "label": "Private Reply (Comment → DM)", "help": "Send a one-time DM to commenters after the public reply. Facebook/Instagram only. Meta allows DM within 7 days of the comment." diff --git a/ui/web/src/i18n/locales/vi/channels.json b/ui/web/src/i18n/locales/vi/channels.json index 4895e5da..77fbbaf4 100644 --- a/ui/web/src/i18n/locales/vi/channels.json +++ b/ui/web/src/i18n/locales/vi/channels.json @@ -245,6 +245,7 @@ "tools": { "label": "Danh sách công cụ được phép", "help": "Giới hạn công cụ agent có thể dùng trong nhóm này" }, "system_prompt": { "label": "Prompt hệ thống" }, "platform": { "label": "Nền tảng", "help": "Chọn nền tảng mà trang Pancake này phục vụ." }, + "tiktok_type": { "label": "Loại tài khoản TikTok", "help": "Chọn loại tài khoản TikTok cho trang này" }, "features.private_reply": { "label": "Private Reply (Comment → DM)", "help": "Gửi tin nhắn riêng cho người bình luận sau khi đã trả lời công khai. Chỉ Facebook/Instagram. Meta cho phép DM trong vòng 7 ngày kể từ khi có bình luận." diff --git a/ui/web/src/i18n/locales/zh/channels.json b/ui/web/src/i18n/locales/zh/channels.json index abab58a4..ea5026de 100644 --- a/ui/web/src/i18n/locales/zh/channels.json +++ b/ui/web/src/i18n/locales/zh/channels.json @@ -245,6 +245,7 @@ "tools": { "label": "工具白名单", "help": "限制Agent在此群组中可使用的工具" }, "system_prompt": { "label": "系统提示词" }, "platform": { "label": "平台", "help": "选择此 Pancake 页面所服务的平台。" }, + "tiktok_type": { "label": "TikTok 类型", "help": "选择此页面的 TikTok 账户类型" }, "features.private_reply": { "label": "私信回复(评论 → 私信)", "help": "在公开回复后向评论者发送一次性私信。仅支持 Facebook/Instagram,Meta 允许在评论后 7 天内发送。" diff --git a/ui/web/src/pages/channels/channel-schemas.ts b/ui/web/src/pages/channels/channel-schemas.ts index 3a70eb34..65b685b6 100644 --- a/ui/web/src/pages/channels/channel-schemas.ts +++ b/ui/web/src/pages/channels/channel-schemas.ts @@ -99,6 +99,12 @@ const pancakePlatformOptions = [ { value: "tokopedia", label: "Tokopedia" }, ]; +const tiktokTypeOptions = [ + { value: "livestream", label: "Livestream AIO" }, + { value: "messaging", label: "Business Messaging" }, + { value: "shop", label: "TikTok Shop" }, +]; + // --- Config schemas --- export const configSchema: Record = { @@ -198,6 +204,7 @@ export const configSchema: Record = { { key: "page_id", label: "Page ID", type: "text", required: true, help: "Pancake internal page ID (numeric, from Pancake dashboard)" }, { key: "webhook_page_id", label: "Webhook Page ID", type: "text", help: "Only needed when the native platform page ID in webhooks differs from the Pancake page ID above (rare). Leave empty if both are the same.", advanced: true }, { key: "platform", label: "Platform", type: "select", required: true, defaultValue: "", options: pancakePlatformOptions, help: "Select the platform this Pancake page serves." }, + { key: "tiktok_type", label: "TikTok Type", type: "select", options: tiktokTypeOptions, showWhen: { key: "platform", value: "tiktok" }, help: "Select the TikTok account type for this page" }, { key: "features.inbox_reply", label: "Inbox Auto-Reply", type: "boolean", defaultValue: true }, { key: "features.comment_reply", label: "Comment Reply", type: "boolean", defaultValue: false, showWhen: { key: "platform", value: ["facebook", "instagram", "threads", "tiktok", "youtube"] } },