feat(xiaohongshu): add 4 creator analytics commands (creator-profile, creator-stats, creator-notes, creator-note-detail) (#49)
* feat(xiaohongshu): add 4 creator analytics commands Add creator backend support for Xiaohongshu (小红书), enabling creators to access their analytics data from the command line. New commands: - creator-profile: account info (followers, likes, creator level) - creator-stats: 7-day/30-day overview (views, likes, collects, comments, shares, new followers) with daily trend data - creator-notes: note list with per-note metrics from note manager - creator-note-detail: single note analytics breakdown (organic vs promoted vs video traffic) API discovery: - /api/galaxy/creator/home/personal_info (cookie auth, 200 OK) - /api/galaxy/creator/data/note_detail_new (cookie auth, 200 OK) - /api/galaxy/creator/data/note_detail?note_id=xxx (cookie auth, 200 OK) - Note manager DOM extraction for note list (bypasses v2 signature) All endpoints verified working with real creator account. Screenshots (redacted) included in docs/screenshots/. Requires: Chrome logged into creator.xiaohongshu.com * chore: remove screenshots from repo (will host externally for PR) * review: fix creator analytics CLI integration Co-authored-by: stone16 <stone2paul@gmail.com> * test: add site-scoped test runner Co-authored-by: stone16 <stone2paul@gmail.com> * review: ignore publish timestamps in creator note metrics --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
This commit is contained in:
1 parent
8a355dfd2d
commit
c370bd0582
8 files changed
+597
-56
No files matched your search
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
const site = process.argv[2]?.trim();
|
||||
|
||||
if (!site) {
|
||||
console.error('Usage: npm run test:site -- <site>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const repoRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
||||
const srcDir = path.join(repoRoot, 'src');
|
||||
|
||||
function runStep(label, command, args) {
|
||||
console.log(`\n==> ${label}`);
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: repoRoot,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
function walk(dir) {
|
||||
const files = [];
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...walk(fullPath));
|
||||
} else {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function toPosix(filePath) {
|
||||
return filePath.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
function findSiteTests() {
|
||||
return walk(srcDir)
|
||||
.filter(filePath => filePath.endsWith('.test.ts'))
|
||||
.filter(filePath => {
|
||||
const normalized = toPosix(path.relative(repoRoot, filePath));
|
||||
return normalized.includes(`/clis/${site}/`) || normalized.includes(`/${site}.test.ts`);
|
||||
})
|
||||
.sort();
|
||||
}
|
||||
|
||||
runStep('Typecheck', 'npm', ['run', 'typecheck']);
|
||||
runStep('Targeted verify', 'npx', ['tsx', 'src/main.ts', 'verify', site]);
|
||||
|
||||
const testFiles = findSiteTests();
|
||||
if (testFiles.length === 0) {
|
||||
console.log(`\nNo site-specific vitest files found for "${site}". Skipping full vitest run.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
runStep(
|
||||
`Site tests (${site})`,
|
||||
'npx',
|
||||
['vitest', 'run', ...testFiles.map(filePath => path.relative(repoRoot, filePath))],
|
||||
);
|
||||
Reference in new issue
Block a user