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 <viettranx@gmail.com>
This commit is contained in:
Plateau Nguyenandviettranx authored and GitHub committed 2026-04-24 09:11:59 +07:00
1 parent 32e5b84340
commit 680e54b673
7 files changed
+23 -4

No files matched your search

+5 -4
View File
@@ -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"`
}
@@ -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
}
)
@@ -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
+1
View File
@@ -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."
+1
View File
@@ -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."
+1
View File
@@ -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 天内发送。"
@@ -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<string, FieldDef[]> = {
@@ -198,6 +204,7 @@ export const configSchema: Record<string, FieldDef[]> = {
{ 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"] } },