feat: 完善视频任务与 KOC 资源库

This commit is contained in:
巫凤萍
2026-08-15 03:53:09 +08:00
parent ad3dbdcc86
commit f37d05dd88
66 changed files with 6633 additions and 558 deletions

View File

@@ -1,7 +1,9 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
collectMetricsFromMcp,
collectXhsMetricsFromMcp,
resolveAccountProfileFromMcp,
resolveCollectionMcpConfig,
resolveXhsPublicAccountDetails,
resolveXhsPublicAccountId,
@@ -40,10 +42,17 @@ function toolEnvelope(payload, isError = false) {
};
}
function createFakeMcp(toolResults) {
function createFakeMcp(toolResults, redirects = {}) {
let toolIndex = 0;
const calls = [];
const fetchImpl = async (url, init) => {
if (!init?.body) {
const requestUrl = String(url);
calls.push({ url: requestUrl, body: null, headers: new Headers(init?.headers) });
const location = redirects[requestUrl];
if (!location) throw new Error(`Unexpected public request: ${requestUrl}`);
return new Response("", { status: 302, headers: { location } });
}
const body = JSON.parse(init.body);
calls.push({ url: String(url), body, headers: new Headers(init.headers) });
if (body.method === "initialize") {
@@ -103,6 +112,7 @@ test("collects likes, comments and favorites from the verified MCP shape", async
likes: 483,
comments: 41,
collects: 519,
shares: 0,
});
assert.equal(calls.length, 3);
assert.equal(calls[2].body.params.name, "fetch_content_detail");
@@ -147,7 +157,7 @@ test("collects through a stateless MCP server without a session header", async (
fetchImpl,
);
assert.deepEqual(result, { likes: 12, comments: 3, collects: 6 });
assert.deepEqual(result, { likes: 12, comments: 3, collects: 6, shares: 0 });
assert.deepEqual(
calls.map((call) => call.body.method),
["initialize", "tools/call"],
@@ -217,19 +227,24 @@ test("resolves the real XHS account profile from a submitted note link", async (
redId: "94329495984",
ipLocation: "重庆",
followers: 734,
gender: "",
bio: "",
recentNoteTitles: [],
providerTags: [],
});
assert.equal(calls.length, 4);
const mcpCalls = calls.filter((call) => call.body?.method);
assert.equal(mcpCalls.length, 4);
assert.equal(
calls[2].body.params.name,
"collect_xhs_wen_note_detail",
mcpCalls[2].body.params.name,
"fetch_content_detail",
);
assert.equal(
calls[2].body.params.arguments.request.note_id,
"6a671108000000000f004bef",
mcpCalls[2].body.params.arguments.request.link,
"https://www.xiaohongshu.com/discovery/item/6a671108000000000f004bef",
);
assert.equal(calls[3].body.params.name, "parse_xhs_user_summary");
assert.equal(mcpCalls[3].body.params.name, "parse_xhs_user_summary");
assert.equal(
calls[3].body.params.arguments.request.url,
mcpCalls[3].body.params.arguments.request.url,
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
);
assert.equal(
@@ -253,7 +268,17 @@ test("resolves followers directly from the supported XHS user summary tool", asy
ipLocation: "福建",
nickname: "555 五",
userId: "1020668113",
gender: "女",
desc: "分享城市周末与美食",
tags: ["本地生活"],
},
notes: [
{
note_id: "note-1",
title: "长沙湘菜探店",
url: "https://www.xiaohongshu.com/explore/note-1",
},
],
},
},
}),
@@ -273,6 +298,10 @@ test("resolves followers directly from the supported XHS user summary tool", asy
followers: 6,
redId: "1020668113",
ipLocation: "福建",
gender: "女",
bio: "分享城市周末与美食",
recentNoteTitles: ["长沙湘菜探店"],
providerTags: ["本地生活"],
});
assert.equal(calls[2].body.params.name, "parse_xhs_user_summary");
});
@@ -338,8 +367,8 @@ test("resolves an xhslink short URL before requesting the author profile", async
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
);
assert.equal(
fakeMcp.calls[2].body.params.arguments.request.note_id,
"6a572da40000000021018bd2",
fakeMcp.calls[2].body.params.arguments.request.link,
"http://xhslink.cn/o/AJFyP5dnj7O",
);
});
@@ -400,10 +429,14 @@ test("uses the public note author when MCP profile lookup fails", async () => {
redId: "1020668113",
ipLocation: "待识别",
followers: 6,
gender: "",
bio: "",
recentNoteTitles: [],
providerTags: [],
});
assert.equal(
fakeMcp.calls[2].body.params.name,
"collect_xhs_wen_note_detail",
"fetch_content_detail",
);
});
@@ -431,27 +464,16 @@ test("reads the user-visible Xiaohongshu number from a public profile", async ()
});
});
test("falls back to parse_xhs_note when the primary tool fails", async () => {
test("treats empty interaction counters from the current detail tool as zero", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope(
{
response: {
code: 400,
success: false,
msg: "获取内容详情失败",
data: null,
},
},
true,
),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
likes: "1.2万",
comments: 32,
collects: "2,345",
likes: "2",
comments: "",
collects: "",
},
},
}),
@@ -466,11 +488,42 @@ test("falls back to parse_xhs_note when the primary tool fails", async () => {
);
assert.deepEqual(result, {
likes: 12_000,
comments: 32,
collects: 2_345,
likes: 2,
comments: 0,
collects: 0,
shares: 0,
});
assert.equal(calls[3].body.params.name, "parse_xhs_note");
assert.equal(calls.length, 3);
assert.equal(calls[2].body.params.name, "fetch_content_detail");
});
test("surfaces current detail tool failures without calling removed tools", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope(
{
response: {
code: 400,
success: false,
msg: "获取内容详情失败",
data: null,
},
},
true,
),
]);
await assert.rejects(
collectXhsMetricsFromMcp(
"https://www.xiaohongshu.com/explore/test",
{
endpoint: "https://collector.example/mcp?key=test-key",
},
fetchImpl,
),
/获取内容详情失败/,
);
assert.equal(calls.length, 3);
assert.equal(calls[2].body.params.name, "fetch_content_detail");
});
test("requires the MCP key without sending a network request", async () => {
@@ -551,6 +604,237 @@ test("rebuilds the MCP session after a gateway session miss", async () => {
fetchImpl,
);
assert.deepEqual(result, { likes: 8, comments: 2, collects: 5 });
assert.deepEqual(result, { likes: 8, comments: 2, collects: 5, shares: 0 });
assert.equal(initializeCount, 2);
});
test("collects Douyin likes, favorites, shares and comments", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
data: {
digg_count: "120",
collect_count: "30",
share_count: "8",
comment_count: "12",
},
},
}),
]);
const result = await collectMetricsFromMcp(
"https://www.douyin.com/video/7520000000000000000",
"抖音",
{ endpoint: "https://collector.example/mcp", key: "test-key" },
fetchImpl,
);
assert.deepEqual(result, {
likes: 120,
collects: 30,
shares: 8,
comments: 12,
});
assert.equal(calls[2].body.params.name, "fetch_content_detail");
assert.equal(calls[2].body.params.arguments.request.plant, "dy");
});
test("maps the current Douyin MCP metric field names", async () => {
const { fetchImpl } = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
data: {
liked_count: "5682",
collected_count: "565",
share_count: "6878",
comment_count: "332",
},
},
}),
]);
const result = await collectMetricsFromMcp(
"https://v.douyin.com/5O5VpgomO2U/",
"抖音",
{ endpoint: "https://collector.example/mcp", key: "test-key" },
fetchImpl,
);
assert.deepEqual(result, {
likes: 5682,
collects: 565,
shares: 6878,
comments: 332,
});
});
test("resolves a Douyin account from a submitted work link", async () => {
const secUid = "MS4wLjABAAAA-test-profile-123456";
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
data: {
author: {
nickname: "抖音作者",
sec_uid: secUid,
unique_id: "douyin-123",
},
},
},
}),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
user: {
nickname: "抖音作者",
unique_id: "douyin-123",
sec_uid: secUid,
follower_count: "1.5万",
ip_location: "上海",
},
},
},
}),
]);
const profile = await resolveAccountProfileFromMcp(
"https://www.douyin.com/video/7520000000000000000",
"抖音",
"回填昵称",
{ endpoint: "https://collector.example/mcp", key: "test-key" },
fetchImpl,
);
assert.deepEqual(profile, {
platformUid: secUid,
nickname: "抖音作者",
profileUrl: `https://www.douyin.com/user/${secUid}`,
redId: "douyin-123",
ipLocation: "上海",
followers: 15_000,
gender: "",
bio: "",
recentNoteTitles: [],
providerTags: [],
});
assert.equal(calls[2].body.params.arguments.request.plant, "dy");
assert.equal(calls[3].body.params.name, "parse_dy_user_summary");
});
test("does not treat a Douyin short-link device id as the author sec_uid", async () => {
const shortLink = "https://v.douyin.com/5O5VpgomO2U/";
const { calls, fetchImpl } = createFakeMcp(
[
toolEnvelope({
response: {
code: 200,
success: true,
data: {
nickname: "搞怪噜噜😜",
user_id: "4065311277723529",
},
},
}),
],
{
[shortLink]: "https://www.iesdouyin.com/share/video/7671270545631842038/?did=MS4wLjABAAAA-device-token&with_sec_did=1",
},
);
const profile = await resolveAccountProfileFromMcp(
shortLink,
"抖音",
"回填昵称",
{ endpoint: "https://collector.example/mcp", key: "test-key" },
fetchImpl,
);
assert.deepEqual(profile, {
platformUid: "4065311277723529",
nickname: "搞怪噜噜😜",
profileUrl: "",
redId: "",
ipLocation: "待识别",
followers: null,
gender: "",
bio: "",
recentNoteTitles: [],
providerTags: [],
});
assert.equal(calls.find((call) => call.body === null)?.url, shortLink);
assert.equal(
calls.find((call) => call.body?.params?.name === "parse_dy_user_summary"),
undefined,
);
});
test("resolves a Douyin profile only when the public redirect exposes sec_uid", async () => {
const shortLink = "https://v.douyin.com/author-sec-uid/";
const secUid = "MS4wLjABAAAAOqL4Jdu8htr7EWCDAyIr5z_7uvCAxhj-GOzCWg5zn8bDiKOp3WPw7lWkTyHvZpMY";
const { calls, fetchImpl } = createFakeMcp(
[
toolEnvelope({
response: {
code: 200,
success: true,
data: {
nickname: "抖音作者",
user_id: "4065311277723529",
},
},
}),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
user: {
nickname: "抖音作者",
unique_id: "douyin-987",
sec_uid: secUid,
follower_count: "2.3万",
ip_location: "广东",
},
},
},
}),
],
{
[shortLink]: `https://www.iesdouyin.com/share/video/7671270545631842038/?sec_uid=${secUid}`,
},
);
const profile = await resolveAccountProfileFromMcp(
shortLink,
"抖音",
"回填昵称",
{ endpoint: "https://collector.example/mcp", key: "test-key" },
fetchImpl,
);
assert.deepEqual(profile, {
platformUid: secUid,
nickname: "抖音作者",
profileUrl: `https://www.douyin.com/user/${secUid}`,
redId: "douyin-987",
ipLocation: "广东",
followers: 23_000,
gender: "",
bio: "",
recentNoteTitles: [],
providerTags: [],
});
assert.equal(
calls.find((call) => call.body?.params?.name === "parse_dy_user_summary")
?.body.params.arguments.request.url,
`https://www.douyin.com/user/${secUid}`,
);
});