mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
80eef46b4e
* refactor: move adapters from src/clis/ to root clis/ for monorepo separation Separates CLI adapters from the core runtime to prepare for independent adapter distribution via postinstall fetch. Key changes: - Move src/clis/ → clis/ (adapters at repo root) - Change tsconfig rootDir from "src" to "." so tsc compiles both - Create root-level shim files (registry.ts, errors.ts, etc.) so adapter relative imports (../../registry.js) resolve correctly - Update build-manifest.ts, main.ts paths for new dist/src/ structure - Expand ensureUserCliCompatShims() to cover all adapter import targets (types, utils, logger, launcher, browser/*, download/*, pipeline/*) - Add scripts/fetch-adapters.js postinstall for ~/.opencli/clis/ sync - Update vitest.config.ts adapter test paths - Add package.json files field to exclude adapters from npm package Official adapter files are unconditionally overwritten on update; user-created files not in the manifest are preserved. * fix: add dist/clis/ and cli-manifest.json to npm files, harden fetch-adapters - Add dist/clis/ and dist/cli-manifest.json to package.json files field so built-in adapters and manifest ship with the npm package - Replace execSync with execFileSync to prevent command injection - Add version check to skip redundant adapter fetches - Track tmpRoot explicitly for reliable cleanup * fix: address review blockers — manifest-based updates, global-only fetch, first-run fallback 1. Manifest-based update strategy: - Read old manifest to identify previously-official files - Clean up files removed upstream (in old manifest but not new) - User-created files (never in any manifest) remain untouched 2. Only run fetch-adapters on global install (npm_config_global=true) or explicit OPENCLI_FETCH=1, preventing heavy side effects for local/dev installs 3. First-run fallback in discovery.ts: - ensureUserAdapters() checks for adapter-manifest.json - If missing and ~/.opencli/clis/ is empty, spawns fetch-adapters.js - Guarantees adapters are available even with --ignore-scripts * fix: remove OPENCLI_FETCH env var, use internal _OPENCLI_FIRST_RUN instead * feat: also support OPENCLI_FETCH=1 for explicit adapter fetch trigger * simplify: replace git clone with local copy from dist/clis/ Adapters already ship in the npm package (dist/clis/), so there's no need to clone from GitHub. Copy directly from the installed package: - Eliminates git, curl, tar dependencies - No network calls in postinstall - No timeout/offline issues - Version always matches the installed CLI - ~65 lines of clone/download code replaced by one cpSync loop
108 lines
3.9 KiB
YAML
108 lines
3.9 KiB
YAML
site: douban
|
||
name: subject
|
||
description: 获取电影详情
|
||
domain: movie.douban.com
|
||
strategy: cookie
|
||
browser: true
|
||
|
||
args:
|
||
id:
|
||
positional: true
|
||
required: true
|
||
type: str
|
||
description: 电影 ID
|
||
|
||
pipeline:
|
||
- navigate: https://movie.douban.com/subject/${{ args.id }}
|
||
|
||
- evaluate: |
|
||
(async () => {
|
||
const id = '${{ args.id }}';
|
||
|
||
// Wait for page to load
|
||
await new Promise(r => setTimeout(r, 2000));
|
||
|
||
// Extract title - v:itemreviewed contains "中文名 OriginalName"
|
||
const titleEl = document.querySelector('span[property="v:itemreviewed"]');
|
||
const fullTitle = titleEl?.textContent?.trim() || '';
|
||
|
||
// Split title and originalTitle
|
||
// Douban format: "中文名 OriginalName" - split by first space that separates CJK from non-CJK
|
||
let title = fullTitle;
|
||
let originalTitle = '';
|
||
const titleMatch = fullTitle.match(/^([\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+(?:\s*[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef·::!?]+)*)\s+(.+)$/);
|
||
if (titleMatch) {
|
||
title = titleMatch[1].trim();
|
||
originalTitle = titleMatch[2].trim();
|
||
}
|
||
|
||
// Extract year
|
||
const yearEl = document.querySelector('.year');
|
||
const year = yearEl?.textContent?.trim().replace(/[()()]/g, '') || '';
|
||
|
||
// Extract rating
|
||
const ratingEl = document.querySelector('strong[property="v:average"]');
|
||
const rating = parseFloat(ratingEl?.textContent || '0');
|
||
|
||
// Extract rating count
|
||
const ratingCountEl = document.querySelector('span[property="v:votes"]');
|
||
const ratingCount = parseInt(ratingCountEl?.textContent || '0', 10);
|
||
|
||
// Extract genres
|
||
const genreEls = document.querySelectorAll('span[property="v:genre"]');
|
||
const genres = Array.from(genreEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
|
||
|
||
// Extract directors
|
||
const directorEls = document.querySelectorAll('a[rel="v:directedBy"]');
|
||
const directors = Array.from(directorEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
|
||
|
||
// Extract casts
|
||
const castEls = document.querySelectorAll('a[rel="v:starring"]');
|
||
const casts = Array.from(castEls).slice(0, 5).map(el => el.textContent?.trim()).filter(Boolean);
|
||
|
||
// Extract info section for country and duration
|
||
const infoEl = document.querySelector('#info');
|
||
const infoText = infoEl?.textContent || '';
|
||
|
||
// Extract country/region from #info as list
|
||
let country = [];
|
||
const countryMatch = infoText.match(/制片国家\/地区:\s*([^\n]+)/);
|
||
if (countryMatch) {
|
||
country = countryMatch[1].trim().split(/\s*\/\s*/).filter(Boolean);
|
||
}
|
||
|
||
// Extract duration from #info as pure number in min
|
||
const durationEl = document.querySelector('span[property="v:runtime"]');
|
||
let durationRaw = durationEl?.textContent?.trim() || '';
|
||
if (!durationRaw) {
|
||
const durationMatch = infoText.match(/片长:\s*([^\n]+)/);
|
||
if (durationMatch) {
|
||
durationRaw = durationMatch[1].trim();
|
||
}
|
||
}
|
||
const durationNumMatch = durationRaw.match(/(\d+)/);
|
||
const duration = durationNumMatch ? parseInt(durationNumMatch[1], 10) : null;
|
||
|
||
// Extract summary
|
||
const summaryEl = document.querySelector('span[property="v:summary"]');
|
||
const summary = summaryEl?.textContent?.trim() || '';
|
||
|
||
return [{
|
||
id,
|
||
title,
|
||
originalTitle,
|
||
year,
|
||
rating,
|
||
ratingCount,
|
||
genres,
|
||
directors,
|
||
casts,
|
||
country,
|
||
duration,
|
||
summary: summary.substring(0, 200),
|
||
url: `https://movie.douban.com/subject/${id}`
|
||
}];
|
||
})()
|
||
|
||
columns: [id, title, originalTitle, year, rating, ratingCount, genres, directors, casts, country, duration, summary, url]
|