import { Command } from 'commander'; /** * 区域覆盖提示(feedback#150 起,issue#191 收敛):上游给每个非美区都贴同一句 * "may be limited or inconsistent" 免责。凡带 meta.regionNote 的响应,两种输出 * 格式都必须透出,不能只躺在缓存文件里;但**支持区不该报警**——那句话是上游的 * 统一声明,不是本区的实测结论(马来站实测数据真实丰富)。 * 因此:`region_supported` 机器可读透出,文案图标按支持与否分级。 * shop-search / shop-reviews 共用。 */ export declare function regionJsonFields(response: any): { region_supported?: boolean; region_note?: string; }; /** * 价格带币种显示(issue#191):非美区商品的价格是本币金额,裸数字印出来会被 * 读成美元。上游 product_price_info 一直带 currency_symbol / currency_name, * 优先用符号,退化到币种代码,都没有才印裸数字。 */ export declare function formatMoney(amount: string | number | undefined | null, currencySymbol?: string, currencyName?: string): string; /** * Build the JSON summary for video search / trending results. * Inlines key fields (incl. the web `url`) so an agent can surface clickable * source links without reading the full cache file. Full data stays in cache. * * `comments` / `shares` 必须内联(#232):backend `transformVideo` 早就算好了,这里漏掉 * 后 agent 对用户说「评论数拿不到」,转走逐条 `video-insights` 的贵路。每条多两个整数,量可控。 * `success: true` 让成功信封与 `outputError` 的 `{success:false, error}` 共享一个判别键(#202 §二)。 */ export declare function buildVideoJsonSummary(command: string, response: any, cacheFile: string | null): { success: boolean; command: string; summary: { result_count: any; videos: any; }; cache_file: string | null; }; /** * Build the JSON summary for shop-reviews. * 原来 stdout 只有 `{command, total_reviews, cache_file}`,一条评论正文都没有—— * 下游读 stdout 恒拿到空,只能去 cat 缓存(#192 §2)。这里按 buildVideoJsonSummary * 的惯例内联**有界**摘要:单页约 10 条、正文截断,量可控;全量仍在 cache_file。 * `total_reviews` 是本次抓到的条数,`summary.review_count` 是上游的总评论数。 */ export declare function buildReviewsJsonSummary(response: any, cacheFile: string | null): { region_supported?: boolean; region_note?: string; command: string; total_reviews: any; summary: { rating: any; review_count: any; reviews: any; }; cache_file: string | null; }; /** * Build the JSON summary for video-insights. * 重点是 `video_url`(CDN 直链):它是下游拆分镜的必需品,原来只躺在缓存的 * `data.data.video.videoUrl` 里,逼得下游要么 cat 缓存、要么把**页面 URL**喂给 * video-download 下成 HTML 壳(#192 §2)。字幕/评论正文量大,仍只留在 cache_file。 */ export declare function buildInsightsJsonSummary(response: any, cacheFile: string | null): { command: string; summary: { video_id: any; url: any; video_url: any; creator: any; views: any; likes: any; comments: any; shares: any; description: string; has_transcript: boolean; transcript_status: any; fetched_comments: any; }; cache_file: string | null; }; /** * 从用户输入解析数字 seller_id(供 shop-products 单测)。 * - 纯数字 → 原样返回。 * - 店铺链接(含 `/shop/store/` 或 `/view/store/`)→ 取 `store/` 后紧邻的数字段, * 中间允许至多一个 `@handle/` 段,忽略 query/hash。 * - 其它 → null(命令层报错并引导 fallback 到商品链接)。 */ export declare function extractSellerId(input: string): string | null; /** * TikTok 分享短链的三种形态——展开前拿不到任何 ID,必须发一次网络请求。 * 是短链则返回**归一化后**的可探测 URL,否则 null。两处归一都不是洁癖: * - 裸 `tiktok.com/t/` → `www.tiktok.com/t/`:两个 skill 的 URL 表都按裸域教 agent * 原样传,而 cn-prod 反代的 host 白名单只认 vt./vm./www.tiktok.com 三个 * (config.ts 的 TIKTOK_RESOLVE_PROXY_DEFAULT 注释)——不归一就会在 cn-prod 被拒。 * - `http:` → `https:`:聊天软件回传的链接偶尔掉成明文,但探测一律走 TLS。 */ export declare function normalizeTikTokShortLink(input: string): string | null; /** `normalizeTikTokShortLink` 的布尔壳。 */ export declare function isTikTokShortLink(input: string): boolean; /** expandShortLink 的探针:只看状态码与 Location,不读 body(原生 Response 即可满足)。 */ type RedirectProbe = (url: string) => Promise<{ status: number; headers: { get(name: string): string | null; }; }>; /** * 逐跳展开 TikTok 分享短链,返回落地长链。 * * 手动跟随(而非 `redirect: 'follow'`)有两个理由:一是每跳都要校验没离开 * tiktok.com(SSRF 兜底,对齐 sc-5 Lambda 的白名单思路);二是我们只要 Location, * 不需要把落地页 body 拉回来。 */ export declare function expandShortLink(url: string, probe: RedirectProbe, maxHops?: number, isDestination?: (url: string) => boolean): Promise; /** * 从用户输入解析商品 ID(供 shop-product 单测)。 * - 纯数字 → 原样返回,无区域提示。 * - 商品链接 → 取 `pdp/`、`product/` 后紧邻的数字段,或 query 里的 * `productId=` / `product_id=`(两处 SKILL.md 的 URL 表都点名这三种形态)。 * `shop.tiktok.com/<区>/pdp/` 这一形态的区段一并回传(大写),供命令层在 * 用户未显式 `--region` 时采纳——链接自己声明了区域,不算「静默换区」。 * - 短链(vt./vm./tiktok.com/t/)→ null:展开要发网络请求,属 expandShortLink * 的职责,本函数保持纯同步。 * - 其它(含店铺链接)→ null。 */ export declare function extractProductId(input: string): { productId: string; regionHint?: string; } | null; /** * 把用户给 shop-product 的任意输入(裸 ID / 长链 / 短链)归一成 `{ productId, region }`。 * * region 优先级:显式 `--region` > 链接自带区段 > 缺省(美区,由 route 决定)。 * 链接区段优先于缺省不违反 skill 的「绝不静默换区重试」——那条禁的是查不到之后 * 偷偷换区,这里是链接自己声明了区域,且命令层会把 regionSource 打印出来。 * * 展开短链要发网络请求,由 `expand` 注入(cn-prod 走 sc-5 反代,其余环境直连)。 */ export declare function resolveProductInput(input: string, explicitRegion: string | undefined, expand: (url: string) => Promise): Promise<{ productId: string; region?: string; regionSource: 'flag' | 'link' | 'default'; ignoredLinkRegion?: string; }>; /** * 翻页聚合 TikTok Shop 全店商品。`fetchPage` 注入 api 调用(供单测),循环至多 `pages` * 页、`hasMore=false` 提前停。中途失败按错误形态分两路: * - HTTP 404(已成 ≥1 页)→ 上游翻页断尾(实测 ScrapeCreators 会对自己发出的尾页 * cursor 确定性返回 not_found;店铺显然存在,404 不是区域信号)→ 视为"到底", * 返回已积累数据 + `paginationEndedEarly`,不产出 error 类字段、不给 resume cursor * (该 cursor 重放必 404,给了只会引导重试循环)。 * - 其他错误(已成 ≥1 页)→ 返回 `partial` + `partialError` + 已积累数据 + * 最后成功页的 next cursor(供 `--cursor` 续拉)。 * 首页即失败 → 原样 rethrow(首页 404 才是真正的区域/存在性信号)。唯一例外: * **续拉调用**(带 `--cursor`)的首页 404 —— cursor 的存在证明此前拉到过页,这仍是 * 上游断尾(配额恰好在尾页前用尽、agent 按提示续拉时踩到毒 cursor),同走 * `paginationEndedEarly` 收口而非 rethrow。 * * 响应字段路径钉死 route body(`{ success, data: { shop, products }, meta: { count, * hasMore, cursor, dataSource } }`):shop←page.data.shop(首个成功页)、 * products←page.data.products、hasMore←page.meta.hasMore、cursor←page.meta.cursor。 */ export declare function fetchShopProductPages(fetchPage: (cursor?: string) => Promise, options: { pages: number; cursor?: string; }): Promise<{ shop: any; products: any[]; pagesFetched: number; hasMore: boolean; cursor?: string; partial?: boolean; partialError?: string; paginationEndedEarly?: boolean; }>; /** * shop-products 的 stdout JSON 形态(供单测钉住)。硬约束:顶层**永远不出现 `error` * 键,也不出现 `status: 'error'`**——cn 平台 gateway tool-registry 会对工具 stdout * JSON.parse 后按这两个形态嗅探失败,一旦命中就丢弃全部 data 只留 error 字符串喂给 * 模型(2026-08-24 VGR 冒烟误判「不是美区店」的直接成因)。partial 场景用 * `partial_error`,断尾场景用 `pagination_ended_early` + 中性 note(note 文案不得含 * "not found" 字样)。 */ export declare function buildShopProductsJsonSummary(result: Awaited>, cacheFile: string | null): Record; /** * 翻页聚合某创作者的视频(#234)。结构与 fetchShopProductPages 同构——上游一页约 * 10 条,`romantic.love501` 实测 42 条只看得到 10 条,「一页」和「全部」在响应里 * 长得一模一样。中途失败的两条收口同样照它: * - HTTP 404(已成 ≥1 页)→ 上游翻页断尾,视为「到底」,不产出 error 类字段、不给 cursor * - 其他错误(已成 ≥1 页)→ `partial` + `partialError` + 已积累数据 + 最后成功页的 cursor * 首页即失败 → 原样 rethrow(那才是「账号不存在」的真信号)。 * * 两个**必须重算**的量:`withProductLink` 与 `coverage` 后端都只按单页算过, * 沿用首页的数会和实际取到的条数对不上;`hint`(「一条带货链接都没有」)在聚合后 * 出现带货链接时就成了假话,得丢掉。 * * 响应字段路径钉死 route body(`{ success, data: { creator, videos }, meta: { count, * withProductLink, creatorRegion, hasMore, cursor, accountTotalVideos, hint } }`)。 */ export declare function fetchCreatorVideoPages(fetchPage: (cursor?: string) => Promise, options: { pages: number; cursor?: string; }): Promise<{ creator?: string; videos: any[]; withProductLink: number; creatorRegion?: string; hint?: string; accountTotalVideos?: number; coverage?: string; pagesFetched: number; hasMore: boolean; cursor?: string; partial?: boolean; partialError?: string; paginationEndedEarly?: boolean; }>; export declare const tiktokCommand: Command; export {}; //# sourceMappingURL=tiktok.d.ts.map