agent-foundation/scripts/local_media_matrix_worker.js
2026-06-18 14:46:49 +08:00

218 lines
6.2 KiB
JavaScript

const douyin = require('./local_douyin_worker');
const xiaohongshu = require('./local_xiaohongshu_worker');
const DEFAULT_BASE_URL = process.env.MEDIA_BASE_URL || 'http://localhost:8880';
const DEFAULT_WORKER_ID = process.env.MEDIA_WORKER_ID || 'local-media-matrix-worker';
const DEFAULT_WORKER_TOKEN = process.env.MEDIA_WORKER_TOKEN || 'local-worker-dev-token';
const DEFAULT_DOUYIN_ACCOUNT_ID = process.env.DOUYIN_ACCOUNT_ID || 'douyin_company';
const DEFAULT_XHS_ACCOUNT_ID = process.env.XHS_ACCOUNT_ID || 'xiaohongshu_company';
const PLATFORM_HANDLERS = {
douyin: {
accountId: DEFAULT_DOUYIN_ACCOUNT_ID,
collectMetrics: douyin.collectMetrics,
checkLogin: douyin.checkLogin
},
xiaohongshu: {
accountId: DEFAULT_XHS_ACCOUNT_ID,
collectMetrics: xiaohongshu.collectMetrics,
checkLogin: xiaohongshu.checkLogin
}
};
function normalizePlatform(platform) {
const value = String(platform || '').trim().toLowerCase();
if (value === 'xhs' || value === 'rednote') {
return 'xiaohongshu';
}
return value;
}
function requestedPlatforms(task) {
const payload = task.payload || {};
const raw = Array.isArray(payload.platforms) && payload.platforms.length > 0
? payload.platforms
: ['douyin', 'xiaohongshu'];
return raw.map(normalizePlatform).filter(platform => PLATFORM_HANDLERS[platform]);
}
function accountIdFor(task, platform) {
const payload = task.payload || {};
const accountIds = payload.accountIds || {};
if (accountIds && typeof accountIds === 'object' && accountIds[platform]) {
return accountIds[platform];
}
return PLATFORM_HANDLERS[platform].accountId;
}
function payloadForPlatform(task, platform) {
const payload = task.payload || {};
const platformPayloads = payload.platformPayloads || {};
return {
...payload,
...(platformPayloads[platform] || {}),
platforms: undefined,
accountIds: undefined,
platformPayloads: undefined
};
}
async function pollTasks() {
const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/poll`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Worker-Token': DEFAULT_WORKER_TOKEN
},
body: JSON.stringify({
workerId: DEFAULT_WORKER_ID,
platforms: ['multi', 'matrix', 'all'],
maxTasks: 3
})
});
if (!response.ok) {
throw new Error(`Poll failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
async function submitResult(taskId, body) {
const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/tasks/${taskId}/result`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Worker-Token': DEFAULT_WORKER_TOKEN
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`Submit result failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
async function handleTask(task) {
const platforms = requestedPlatforms(task);
if (platforms.length === 0) {
return submitResult(task.id, {
status: 'FAILED',
errorMessage: 'No supported platforms requested.',
result: { action: 'sync_metrics', platform: 'multi', platformResults: {} }
});
}
const platformResults = {};
const videos = [];
const metrics = emptyMetrics();
const errors = [];
for (const platform of platforms) {
const handler = PLATFORM_HANDLERS[platform];
const platformTask = {
...task,
platform,
accountId: accountIdFor(task, platform),
payload: payloadForPlatform(task, platform)
};
try {
const response = await handler.collectMetrics(platformTask);
const result = response.result || {};
platformResults[platform] = {
...result,
platform,
accountId: platformTask.accountId,
status: response.status
};
addMetrics(metrics, result.metrics || {});
for (const video of result.videos || []) {
videos.push({ ...video, platform, accountId: platformTask.accountId });
}
if (response.status !== 'SUCCESS') {
errors.push(`${platform}: ${response.errorMessage || 'failed'}`);
}
} catch (error) {
platformResults[platform] = {
platform,
accountId: platformTask.accountId,
status: 'FAILED',
errorMessage: error.message
};
errors.push(`${platform}: ${error.message}`);
}
}
return submitResult(task.id, {
status: errors.length === platforms.length ? 'FAILED' : 'SUCCESS',
errorMessage: errors.length > 0 ? errors.join('; ') : null,
result: {
action: 'sync_metrics',
platform: 'multi',
platforms,
metrics,
videos,
videoCount: videos.length,
platformResults,
extractionMode: 'multi_platform'
}
});
}
function emptyMetrics() {
return { views: 0, likes: 0, comments: 0, collects: 0, shares: 0 };
}
function addMetrics(target, source) {
target.views += Number(source.views || 0);
target.likes += Number(source.likes || 0);
target.comments += Number(source.comments || 0);
target.collects += Number(source.collects || 0);
target.shares += Number(source.shares || 0);
}
async function workerOnce() {
const tasks = await pollTasks();
if (!Array.isArray(tasks) || tasks.length === 0) {
console.log('No pending media matrix tasks.');
return;
}
for (const task of tasks) {
const result = await handleTask(task);
console.log(`Completed ${task.id}: ${result.status}`);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function workerLoop() {
const intervalMs = Number(process.env.MEDIA_WORKER_INTERVAL_MS || 30000);
console.log(`Media matrix worker loop started. baseUrl=${DEFAULT_BASE_URL}, intervalMs=${intervalMs}`);
while (true) {
try {
await workerOnce();
} catch (error) {
console.error(error.stack || error.message);
}
await sleep(intervalMs);
}
}
async function main() {
const mode = process.argv[2];
if (mode === 'worker') {
await workerOnce();
return;
}
if (mode === 'worker-loop') {
await workerLoop();
return;
}
throw new Error('Usage: node scripts/local_media_matrix_worker.js <worker|worker-loop>');
}
main().catch(error => {
console.error(error.stack || error.message);
process.exit(1);
});