Files
wehub-resource-sync 9b395f5cc3
Build Chrome Extension / build (push) Waiting to run
Trigger Website Rebuild (Docs Updated) / dispatch (push) Waiting to run
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

117 lines
4.1 KiB
JavaScript

import { cli, Strategy } from '@jackwener/opencli/registry';
import { getSelfUid } from './utils.js';
cli({
site: 'douban',
name: 'marks',
access: 'read',
description: '导出个人观影标记',
domain: 'movie.douban.com',
strategy: Strategy.COOKIE,
args: [
{
name: 'status',
default: 'collect',
choices: ['collect', 'wish', 'do', 'all'],
help: '标记类型: collect(看过), wish(想看), do(在看), all(全部)'
},
{ name: 'limit', type: 'int', default: 50, help: '导出数量, 0 表示全部' },
{ name: 'uid', help: '用户ID,不填则使用当前登录账号' },
],
columns: ['title', 'year', 'myRating', 'myStatus', 'myDate', 'myComment', 'url'],
func: async (page, kwargs) => {
const { status = 'collect', limit = 50, uid: providedUid } = kwargs;
const uid = providedUid || await getSelfUid(page);
const statuses = status === 'all'
? ['collect', 'wish', 'do']
: [status];
const allMarks = [];
for (const s of statuses) {
const remaining = limit > 0 ? limit - allMarks.length : 0;
if (limit > 0 && remaining <= 0)
break;
const marks = await fetchMarks(page, uid, s, remaining);
allMarks.push(...marks);
}
return allMarks.slice(0, limit > 0 ? limit : undefined);
},
});
async function fetchMarks(page, uid, status, limit) {
const marks = [];
let offset = 0;
const pageSize = 15;
while (true) {
const url = `https://movie.douban.com/people/${uid}/${status}?start=${offset}&sort=time&rating=all&filter=all&mode=grid`;
await page.goto(url);
await page.wait({ time: 2 });
const pageMarks = await page.evaluate(`
() => {
const results = [];
const items = document.querySelectorAll('.item');
items.forEach(item => {
const titleLink = item.querySelector('.info a[href*="/subject/"]');
if (!titleLink) return;
const titleEl = titleLink.querySelector('em');
const titleText = titleEl?.textContent?.trim() || titleLink.textContent?.trim() || '';
const title = titleText.split('/')[0].trim();
const href = titleLink.href || '';
const idMatch = href.match(/subject\\/(\\d+)/);
const movieId = idMatch ? idMatch[1] : '';
if (!movieId || !title) return;
const ratingSpan = item.querySelector('span[class*="rating"]');
let myRating = null;
if (ratingSpan) {
const cls = ratingSpan.className || '';
const ratingMatch = cls.match(/rating(\\d)-t/);
if (ratingMatch) {
myRating = parseInt(ratingMatch[1], 10) * 2;
}
}
const dateSpan = item.querySelector('.date');
const myDate = dateSpan?.textContent?.trim() || '';
const commentSpan = item.querySelector('.comment');
const myComment = commentSpan?.textContent?.trim() || '';
const introSpan = item.querySelector('.intro');
let year = '';
if (introSpan) {
const introText = introSpan.textContent || '';
const yearMatch = introText.match(/(\\d{4})/);
year = yearMatch ? yearMatch[1] : '';
}
results.push({
movieId,
title,
year,
myRating,
myStatus: '${status}',
myComment,
myDate,
url: href || 'https://movie.douban.com/subject/' + movieId
});
});
return results;
}
`);
if (!pageMarks || pageMarks.length === 0)
break;
marks.push(...pageMarks);
if (pageMarks.length < pageSize)
break;
if (limit > 0 && marks.length >= limit)
break;
offset += pageSize;
await new Promise(resolve => setTimeout(resolve, 1000));
}
return marks;
}