@jackwener/opencli 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +2 -6
- package/.github/workflows/ci.yml +21 -1
- package/README.md +35 -6
- package/README.zh-CN.md +12 -5
- package/SKILL.md +2 -0
- package/dist/browser/cdp.d.ts +2 -1
- package/dist/browser/discover.d.ts +4 -1
- package/dist/browser/discover.js +6 -2
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +4 -12
- package/dist/browser/mcp.d.ts +2 -1
- package/dist/build-manifest.d.ts +2 -0
- package/dist/build-manifest.js +39 -14
- package/dist/build-manifest.test.js +21 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +2 -1
- package/dist/cli-manifest.json +1111 -112
- package/dist/cli.js +34 -3
- package/dist/clis/36kr/article.d.ts +1 -0
- package/dist/clis/36kr/article.js +62 -0
- package/dist/clis/36kr/hot.d.ts +3 -0
- package/dist/clis/36kr/hot.js +80 -0
- package/dist/clis/36kr/hot.test.d.ts +1 -0
- package/dist/clis/36kr/hot.test.js +15 -0
- package/dist/clis/36kr/news.d.ts +1 -0
- package/dist/clis/36kr/news.js +51 -0
- package/dist/clis/36kr/news.test.d.ts +1 -0
- package/dist/clis/36kr/news.test.js +85 -0
- package/dist/clis/36kr/search.d.ts +1 -0
- package/dist/clis/36kr/search.js +72 -0
- package/dist/clis/bilibili/comments.d.ts +5 -0
- package/dist/clis/bilibili/comments.js +40 -0
- package/dist/clis/bilibili/comments.test.d.ts +1 -0
- package/dist/clis/bilibili/comments.test.js +82 -0
- package/dist/clis/chatgpt/ask.js +29 -14
- package/dist/clis/chatgpt/ax.d.ts +6 -0
- package/dist/clis/chatgpt/ax.js +172 -1
- package/dist/clis/chatgpt/model.d.ts +1 -0
- package/dist/clis/chatgpt/model.js +24 -0
- package/dist/clis/chatgpt/send.js +12 -3
- package/dist/clis/douban/download.d.ts +1 -0
- package/dist/clis/douban/download.js +67 -0
- package/dist/clis/douban/download.test.d.ts +1 -0
- package/dist/clis/douban/download.test.js +170 -0
- package/dist/clis/douban/photos.d.ts +1 -0
- package/dist/clis/douban/photos.js +34 -0
- package/dist/clis/douban/utils.d.ts +25 -0
- package/dist/clis/douban/utils.js +190 -1
- package/dist/clis/douban/utils.test.d.ts +1 -0
- package/dist/clis/douban/utils.test.js +64 -0
- package/dist/clis/imdb/person.d.ts +1 -0
- package/dist/clis/imdb/person.js +203 -0
- package/dist/clis/imdb/reviews.d.ts +1 -0
- package/dist/clis/imdb/reviews.js +88 -0
- package/dist/clis/imdb/search.d.ts +1 -0
- package/dist/clis/imdb/search.js +161 -0
- package/dist/clis/imdb/title.d.ts +1 -0
- package/dist/clis/imdb/title.js +93 -0
- package/dist/clis/imdb/top.d.ts +1 -0
- package/dist/clis/imdb/top.js +53 -0
- package/dist/clis/imdb/trending.d.ts +1 -0
- package/dist/clis/imdb/trending.js +52 -0
- package/dist/clis/imdb/utils.d.ts +46 -0
- package/dist/clis/imdb/utils.js +285 -0
- package/dist/clis/imdb/utils.test.d.ts +1 -0
- package/dist/clis/imdb/utils.test.js +88 -0
- package/dist/clis/jd/item.d.ts +4 -0
- package/dist/clis/jd/item.js +16 -15
- package/dist/clis/jd/item.test.js +16 -1
- package/dist/clis/linux-do/categories.yaml +38 -9
- package/dist/clis/linux-do/category.d.ts +1 -0
- package/dist/clis/linux-do/category.js +36 -0
- package/dist/clis/linux-do/feed.d.ts +45 -0
- package/dist/clis/linux-do/feed.js +397 -0
- package/dist/clis/linux-do/feed.test.d.ts +1 -0
- package/dist/clis/linux-do/feed.test.js +118 -0
- package/dist/clis/linux-do/hot.d.ts +1 -0
- package/dist/clis/linux-do/hot.js +25 -0
- package/dist/clis/linux-do/latest.d.ts +1 -0
- package/dist/clis/linux-do/latest.js +18 -0
- package/dist/clis/linux-do/tags.yaml +41 -0
- package/dist/clis/linux-do/topic.yaml +41 -3
- package/dist/clis/linux-do/user-posts.yaml +67 -0
- package/dist/clis/linux-do/user-topics.yaml +54 -0
- package/dist/clis/paperreview/commands.test.d.ts +3 -0
- package/dist/clis/paperreview/commands.test.js +243 -0
- package/dist/clis/paperreview/feedback.d.ts +1 -0
- package/dist/clis/paperreview/feedback.js +52 -0
- package/dist/clis/paperreview/review.d.ts +1 -0
- package/dist/clis/paperreview/review.js +37 -0
- package/dist/clis/paperreview/submit.d.ts +1 -0
- package/dist/clis/paperreview/submit.js +85 -0
- package/dist/clis/paperreview/utils.d.ts +46 -0
- package/dist/clis/paperreview/utils.js +197 -0
- package/dist/clis/paperreview/utils.test.d.ts +1 -0
- package/dist/clis/paperreview/utils.test.js +49 -0
- package/dist/clis/producthunt/browse.d.ts +1 -0
- package/dist/clis/producthunt/browse.js +99 -0
- package/dist/clis/producthunt/hot.d.ts +1 -0
- package/dist/clis/producthunt/hot.js +110 -0
- package/dist/clis/producthunt/posts.d.ts +1 -0
- package/dist/clis/producthunt/posts.js +28 -0
- package/dist/clis/producthunt/today.d.ts +1 -0
- package/dist/clis/producthunt/today.js +35 -0
- package/dist/clis/producthunt/utils.d.ts +29 -0
- package/dist/clis/producthunt/utils.js +99 -0
- package/dist/clis/producthunt/utils.test.d.ts +1 -0
- package/dist/clis/producthunt/utils.test.js +64 -0
- package/dist/clis/twitter/article.js +4 -28
- package/dist/clis/twitter/likes.d.ts +24 -0
- package/dist/clis/twitter/likes.js +217 -0
- package/dist/clis/twitter/likes.test.d.ts +1 -0
- package/dist/clis/twitter/likes.test.js +85 -0
- package/dist/clis/twitter/profile.js +4 -28
- package/dist/clis/twitter/search.js +2 -1
- package/dist/clis/twitter/search.test.js +2 -0
- package/dist/clis/twitter/shared.d.ts +6 -0
- package/dist/clis/twitter/shared.js +35 -0
- package/dist/clis/twitter/timeline.js +2 -13
- package/dist/clis/weixin/download.d.ts +17 -0
- package/dist/clis/weixin/download.js +88 -20
- package/dist/clis/weread/book.js +2 -2
- package/dist/clis/weread/commands.test.d.ts +3 -0
- package/dist/clis/weread/commands.test.js +43 -0
- package/dist/clis/weread/highlights.js +2 -2
- package/dist/clis/weread/notebooks.js +2 -2
- package/dist/clis/weread/notes.js +3 -3
- package/dist/clis/weread/shelf.js +2 -2
- package/dist/clis/weread/utils.d.ts +4 -4
- package/dist/clis/weread/utils.js +32 -14
- package/dist/clis/weread/utils.test.js +1 -28
- package/dist/clis/xiaohongshu/comments.d.ts +5 -0
- package/dist/clis/xiaohongshu/comments.js +74 -0
- package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/comments.test.js +79 -0
- package/dist/clis/xiaohongshu/publish.js +114 -18
- package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +119 -0
- package/dist/commanderAdapter.d.ts +1 -0
- package/dist/commanderAdapter.js +176 -29
- package/dist/commanderAdapter.test.d.ts +1 -0
- package/dist/commanderAdapter.test.js +62 -0
- package/dist/daemon.js +17 -1
- package/dist/discovery.js +8 -14
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +9 -2
- package/dist/download/index.js +63 -51
- package/dist/download/index.test.js +17 -4
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +15 -32
- package/dist/execution.d.ts +1 -3
- package/dist/execution.js +21 -1
- package/dist/hooks.js +2 -0
- package/dist/main.js +5 -0
- package/dist/output.js +5 -1
- package/dist/pipeline/executor.js +3 -4
- package/dist/plugin-manifest.d.ts +70 -0
- package/dist/plugin-manifest.js +160 -0
- package/dist/plugin-manifest.test.d.ts +4 -0
- package/dist/plugin-manifest.test.js +179 -0
- package/dist/plugin.d.ts +38 -5
- package/dist/plugin.js +267 -33
- package/dist/plugin.test.js +220 -3
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +2 -0
- package/dist/runtime-detect.d.ts +21 -0
- package/dist/runtime-detect.js +32 -0
- package/dist/runtime-detect.test.d.ts +1 -0
- package/dist/runtime-detect.test.js +27 -0
- package/dist/runtime.js +1 -1
- package/dist/serialization.d.ts +2 -0
- package/dist/serialization.js +6 -0
- package/dist/types.d.ts +1 -0
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +112 -0
- package/dist/weixin-download.test.d.ts +1 -0
- package/dist/weixin-download.test.js +30 -0
- package/dist/weread-private-api-regression.test.d.ts +1 -0
- package/dist/weread-private-api-regression.test.js +122 -0
- package/dist/yaml-schema.d.ts +3 -0
- package/dist/yaml-schema.js +18 -1
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/36kr.md +47 -0
- package/docs/adapters/browser/douban.md +14 -0
- package/docs/adapters/browser/imdb.md +47 -0
- package/docs/adapters/browser/jd.md +2 -2
- package/docs/adapters/browser/linux-do.md +181 -20
- package/docs/adapters/browser/paperreview.md +43 -0
- package/docs/adapters/browser/producthunt.md +49 -0
- package/docs/adapters/desktop/chatgpt.md +5 -0
- package/docs/adapters/index.md +6 -2
- package/docs/advanced/download.md +4 -0
- package/docs/advanced/rate-limiter-plugin.md +99 -0
- package/docs/guide/electron-app-cli.md +200 -0
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +87 -0
- package/docs/zh/guide/electron-app-cli.md +188 -0
- package/docs/zh/guide/getting-started.md +1 -0
- package/docs/zh/guide/plugins.md +65 -0
- package/extension/package.json +1 -0
- package/extension/scripts/package-release.mjs +179 -0
- package/extension/src/background.ts +2 -0
- package/package.json +4 -1
- package/scripts/postinstall.js +10 -0
- package/src/browser/cdp.ts +2 -1
- package/src/browser/discover.ts +8 -3
- package/src/browser/errors.ts +13 -14
- package/src/browser/mcp.ts +2 -1
- package/src/build-manifest.test.ts +23 -0
- package/src/build-manifest.ts +40 -15
- package/src/capabilityRouting.ts +2 -1
- package/src/cli.ts +35 -3
- package/src/clis/36kr/article.ts +69 -0
- package/src/clis/36kr/hot.test.ts +19 -0
- package/src/clis/36kr/hot.ts +100 -0
- package/src/clis/36kr/news.test.ts +90 -0
- package/src/clis/36kr/news.ts +54 -0
- package/src/clis/36kr/search.ts +78 -0
- package/src/clis/bilibili/comments.test.ts +102 -0
- package/src/clis/bilibili/comments.ts +44 -0
- package/src/clis/chatgpt/ask.ts +28 -14
- package/src/clis/chatgpt/ax.ts +180 -1
- package/src/clis/chatgpt/model.ts +27 -0
- package/src/clis/chatgpt/send.ts +16 -6
- package/src/clis/douban/download.test.ts +196 -0
- package/src/clis/douban/download.ts +78 -0
- package/src/clis/douban/photos.ts +36 -0
- package/src/clis/douban/utils.test.ts +97 -0
- package/src/clis/douban/utils.ts +232 -1
- package/src/clis/imdb/person.ts +232 -0
- package/src/clis/imdb/reviews.ts +111 -0
- package/src/clis/imdb/search.ts +179 -0
- package/src/clis/imdb/title.ts +121 -0
- package/src/clis/imdb/top.ts +67 -0
- package/src/clis/imdb/trending.ts +66 -0
- package/src/clis/imdb/utils.test.ts +117 -0
- package/src/clis/imdb/utils.ts +305 -0
- package/src/clis/jd/item.test.ts +18 -1
- package/src/clis/jd/item.ts +18 -15
- package/src/clis/linux-do/categories.yaml +38 -9
- package/src/clis/linux-do/category.ts +37 -0
- package/src/clis/linux-do/feed.test.ts +132 -0
- package/src/clis/linux-do/feed.ts +501 -0
- package/src/clis/linux-do/hot.ts +26 -0
- package/src/clis/linux-do/latest.ts +19 -0
- package/src/clis/linux-do/tags.yaml +41 -0
- package/src/clis/linux-do/topic.yaml +41 -3
- package/src/clis/linux-do/user-posts.yaml +67 -0
- package/src/clis/linux-do/user-topics.yaml +54 -0
- package/src/clis/paperreview/commands.test.ts +283 -0
- package/src/clis/paperreview/feedback.ts +64 -0
- package/src/clis/paperreview/review.ts +47 -0
- package/src/clis/paperreview/submit.ts +119 -0
- package/src/clis/paperreview/utils.test.ts +68 -0
- package/src/clis/paperreview/utils.ts +276 -0
- package/src/clis/producthunt/browse.ts +109 -0
- package/src/clis/producthunt/hot.ts +127 -0
- package/src/clis/producthunt/posts.ts +29 -0
- package/src/clis/producthunt/today.ts +37 -0
- package/src/clis/producthunt/utils.test.ts +72 -0
- package/src/clis/producthunt/utils.ts +122 -0
- package/src/clis/twitter/article.ts +5 -28
- package/src/clis/twitter/likes.test.ts +91 -0
- package/src/clis/twitter/likes.ts +256 -0
- package/src/clis/twitter/profile.ts +5 -28
- package/src/clis/twitter/search.test.ts +2 -0
- package/src/clis/twitter/search.ts +3 -1
- package/src/clis/twitter/shared.ts +45 -0
- package/src/clis/twitter/timeline.ts +2 -13
- package/src/clis/weixin/download.ts +114 -20
- package/src/clis/weread/book.ts +2 -2
- package/src/clis/weread/commands.test.ts +57 -0
- package/src/clis/weread/highlights.ts +2 -2
- package/src/clis/weread/notebooks.ts +2 -2
- package/src/clis/weread/notes.ts +3 -3
- package/src/clis/weread/shelf.ts +2 -2
- package/src/clis/weread/utils.test.ts +1 -32
- package/src/clis/weread/utils.ts +41 -16
- package/src/clis/xiaohongshu/comments.test.ts +96 -0
- package/src/clis/xiaohongshu/comments.ts +81 -0
- package/src/clis/xiaohongshu/publish.test.ts +137 -0
- package/src/clis/xiaohongshu/publish.ts +129 -18
- package/src/commanderAdapter.test.ts +78 -0
- package/src/commanderAdapter.ts +188 -24
- package/src/daemon.ts +19 -1
- package/src/discovery.ts +8 -15
- package/src/doctor.ts +13 -2
- package/src/download/index.test.ts +14 -4
- package/src/download/index.ts +67 -55
- package/src/errors.ts +25 -66
- package/src/execution.ts +28 -3
- package/src/hooks.ts +1 -0
- package/src/main.ts +6 -0
- package/src/output.ts +3 -1
- package/src/pipeline/executor.ts +4 -6
- package/src/plugin-manifest.test.ts +223 -0
- package/src/plugin-manifest.ts +206 -0
- package/src/plugin.test.ts +246 -2
- package/src/plugin.ts +338 -36
- package/src/registry.ts +6 -1
- package/src/runtime-detect.test.ts +30 -0
- package/src/runtime-detect.ts +36 -0
- package/src/runtime.ts +1 -1
- package/src/serialization.ts +4 -0
- package/src/types.ts +1 -0
- package/src/update-check.ts +114 -0
- package/src/weixin-download.test.ts +64 -0
- package/src/weread-private-api-regression.test.ts +150 -0
- package/src/yaml-schema.ts +20 -0
- package/tests/e2e/browser-auth.test.ts +13 -9
- package/tests/e2e/browser-public-extended.test.ts +1 -1
- package/tests/e2e/browser-public.test.ts +62 -4
- package/tests/e2e/helpers.ts +2 -1
- package/tests/e2e/public-commands.test.ts +37 -3
- package/tests/smoke/api-health.test.ts +1 -1
- package/vitest.config.ts +10 -0
- package/dist/clis/linux-do/category.yaml +0 -51
- package/dist/clis/linux-do/hot.yaml +0 -50
- package/dist/clis/linux-do/latest.yaml +0 -40
- package/src/clis/linux-do/category.yaml +0 -51
- package/src/clis/linux-do/hot.yaml +0 -50
- package/src/clis/linux-do/latest.yaml +0 -40
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ArgumentError, CommandExecutionError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { forceEnglishUrl, isChallengePage, normalizeImdbTitleType, waitForImdbPath, waitForImdbSearchReady, } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Search IMDb via the public search page and parse Next.js payload first.
|
|
6
|
+
*/
|
|
7
|
+
cli({
|
|
8
|
+
site: 'imdb',
|
|
9
|
+
name: 'search',
|
|
10
|
+
description: 'Search IMDb for movies, TV shows, and people',
|
|
11
|
+
domain: 'www.imdb.com',
|
|
12
|
+
strategy: Strategy.PUBLIC,
|
|
13
|
+
browser: true,
|
|
14
|
+
args: [
|
|
15
|
+
{ name: 'query', positional: true, required: true, help: 'Search query' },
|
|
16
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
17
|
+
],
|
|
18
|
+
columns: ['rank', 'id', 'title', 'year', 'type', 'url'],
|
|
19
|
+
func: async (page, args) => {
|
|
20
|
+
const query = String(args.query || '').trim();
|
|
21
|
+
// Reject empty or whitespace-only queries early
|
|
22
|
+
if (!query) {
|
|
23
|
+
throw new ArgumentError('Search query cannot be empty');
|
|
24
|
+
}
|
|
25
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 50));
|
|
26
|
+
const url = forceEnglishUrl(`https://www.imdb.com/find/?q=${encodeURIComponent(query)}&ref_=nv_sr_sm`);
|
|
27
|
+
await page.goto(url);
|
|
28
|
+
const onSearchPage = await waitForImdbPath(page, '^/find/?$');
|
|
29
|
+
const searchReady = await waitForImdbSearchReady(page, 15000);
|
|
30
|
+
if (await isChallengePage(page)) {
|
|
31
|
+
throw new CommandExecutionError('IMDb blocked this request', 'Try again with a normal browser session or extension mode');
|
|
32
|
+
}
|
|
33
|
+
if (!onSearchPage || !searchReady) {
|
|
34
|
+
throw new CommandExecutionError('IMDb search results did not finish loading', 'Retry the command; if it persists, the search page structure may have changed');
|
|
35
|
+
}
|
|
36
|
+
const results = await page.evaluate(`
|
|
37
|
+
(function() {
|
|
38
|
+
var results = [];
|
|
39
|
+
|
|
40
|
+
function pushResult(item) {
|
|
41
|
+
if (!item || !item.id || !item.title) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
results.push(item);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var nextDataEl = document.getElementById('__NEXT_DATA__');
|
|
48
|
+
if (nextDataEl) {
|
|
49
|
+
try {
|
|
50
|
+
var nextData = JSON.parse(nextDataEl.textContent || 'null');
|
|
51
|
+
var pageProps = nextData && nextData.props && nextData.props.pageProps;
|
|
52
|
+
if (pageProps) {
|
|
53
|
+
// IMDb wraps results as {index: "tt...", listItem: {...}}
|
|
54
|
+
var titleResults = (pageProps.titleResults && pageProps.titleResults.results) || [];
|
|
55
|
+
for (var i = 0; i < titleResults.length; i++) {
|
|
56
|
+
var tr = titleResults[i] || {};
|
|
57
|
+
var tItem = tr.listItem || {};
|
|
58
|
+
var tId = tr.index || '';
|
|
59
|
+
var tTitle = typeof tItem.originalTitleText === 'string'
|
|
60
|
+
? tItem.originalTitleText
|
|
61
|
+
: (tItem.originalTitleText && tItem.originalTitleText.text) || '';
|
|
62
|
+
if (!tTitle) {
|
|
63
|
+
tTitle = typeof tItem.titleText === 'string'
|
|
64
|
+
? tItem.titleText
|
|
65
|
+
: (tItem.titleText && tItem.titleText.text) || '';
|
|
66
|
+
}
|
|
67
|
+
var tYear = '';
|
|
68
|
+
if (typeof tItem.releaseYear === 'number' || typeof tItem.releaseYear === 'string') {
|
|
69
|
+
tYear = String(tItem.releaseYear);
|
|
70
|
+
} else if (tItem.releaseYear && typeof tItem.releaseYear === 'object') {
|
|
71
|
+
tYear = String(tItem.releaseYear.year || '');
|
|
72
|
+
}
|
|
73
|
+
pushResult({
|
|
74
|
+
id: tId,
|
|
75
|
+
title: tTitle,
|
|
76
|
+
year: tYear,
|
|
77
|
+
type: tItem.titleType || (tItem.endYear != null ? 'tvSeries' : ''),
|
|
78
|
+
url: tId ? 'https://www.imdb.com/title/' + tId + '/' : ''
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
var nameResults = (pageProps.nameResults && pageProps.nameResults.results) || [];
|
|
83
|
+
for (var j = 0; j < nameResults.length; j++) {
|
|
84
|
+
var nr = nameResults[j] || {};
|
|
85
|
+
var nItem = nr.listItem || {};
|
|
86
|
+
var nId = nr.index || '';
|
|
87
|
+
var nTitle = typeof nItem.nameText === 'string'
|
|
88
|
+
? nItem.nameText
|
|
89
|
+
: (nItem.nameText && nItem.nameText.text) || '';
|
|
90
|
+
if (!nTitle) {
|
|
91
|
+
nTitle = typeof nItem.originalNameText === 'string'
|
|
92
|
+
? nItem.originalNameText
|
|
93
|
+
: (nItem.originalNameText && nItem.originalNameText.text) || '';
|
|
94
|
+
}
|
|
95
|
+
var nType = '';
|
|
96
|
+
if (typeof nItem.primaryProfession === 'string') {
|
|
97
|
+
nType = nItem.primaryProfession;
|
|
98
|
+
} else if (Array.isArray(nItem.primaryProfessions) && nItem.primaryProfessions.length > 0) {
|
|
99
|
+
nType = String(nItem.primaryProfessions[0] || '');
|
|
100
|
+
} else if (Array.isArray(nItem.professions) && nItem.professions.length > 0) {
|
|
101
|
+
nType = String(nItem.professions[0] || '');
|
|
102
|
+
}
|
|
103
|
+
pushResult({
|
|
104
|
+
id: nId,
|
|
105
|
+
title: nTitle,
|
|
106
|
+
year: nItem.knownFor && nItem.knownFor.yearRange ? String(nItem.knownFor.yearRange.year || '') : (nItem.knownForTitleYear ? String(nItem.knownForTitleYear) : ''),
|
|
107
|
+
type: nType || 'Person',
|
|
108
|
+
url: nId ? 'https://www.imdb.com/name/' + nId + '/' : ''
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
void error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (results.length === 0) {
|
|
118
|
+
var items = document.querySelectorAll('[class*="find-title-result"], [class*="find-name-result"], .ipc-metadata-list-summary-item');
|
|
119
|
+
for (var k = 0; k < items.length; k++) {
|
|
120
|
+
var el = items[k];
|
|
121
|
+
var linkEl = el.querySelector('a[href*="/title/"], a[href*="/name/"]');
|
|
122
|
+
if (!linkEl) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var href = linkEl.getAttribute('href') || '';
|
|
127
|
+
var idMatch = href.match(/(tt|nm)\\d{7,8}/);
|
|
128
|
+
if (!idMatch) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var titleEl = el.querySelector('.ipc-metadata-list-summary-item__t, h3, a');
|
|
133
|
+
var metaEls = el.querySelectorAll('.ipc-metadata-list-summary-item__li, span');
|
|
134
|
+
var absoluteUrl = href.startsWith('http') ? href : 'https://www.imdb.com' + href.split('?')[0];
|
|
135
|
+
|
|
136
|
+
pushResult({
|
|
137
|
+
id: idMatch[0],
|
|
138
|
+
title: titleEl ? (titleEl.textContent || '').trim() : '',
|
|
139
|
+
year: metaEls.length > 0 ? (metaEls[0].textContent || '').trim() : '',
|
|
140
|
+
type: metaEls.length > 1 ? (metaEls[1].textContent || '').trim() : '',
|
|
141
|
+
url: absoluteUrl
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return results;
|
|
147
|
+
})()
|
|
148
|
+
`);
|
|
149
|
+
if (!Array.isArray(results)) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return results.slice(0, limit).map((item, index) => ({
|
|
153
|
+
rank: index + 1,
|
|
154
|
+
id: item.id || '',
|
|
155
|
+
title: item.title || '',
|
|
156
|
+
year: item.year || '',
|
|
157
|
+
type: normalizeImdbTitleType(item.type),
|
|
158
|
+
url: item.url || '',
|
|
159
|
+
}));
|
|
160
|
+
},
|
|
161
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CommandExecutionError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { extractJsonLd, forceEnglishUrl, formatDuration, getCurrentImdbId, isChallengePage, normalizeImdbId, waitForImdbPath, } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Read IMDb title details from JSON-LD on the public page.
|
|
6
|
+
*/
|
|
7
|
+
cli({
|
|
8
|
+
site: 'imdb',
|
|
9
|
+
name: 'title',
|
|
10
|
+
description: 'Get movie or TV show details',
|
|
11
|
+
domain: 'www.imdb.com',
|
|
12
|
+
strategy: Strategy.PUBLIC,
|
|
13
|
+
browser: true,
|
|
14
|
+
args: [
|
|
15
|
+
{ name: 'id', positional: true, required: true, help: 'IMDb title ID (tt1375666) or URL' },
|
|
16
|
+
],
|
|
17
|
+
columns: ['field', 'value'],
|
|
18
|
+
func: async (page, args) => {
|
|
19
|
+
const id = normalizeImdbId(String(args.id), 'tt');
|
|
20
|
+
const url = forceEnglishUrl(`https://www.imdb.com/title/${id}/`);
|
|
21
|
+
await page.goto(url);
|
|
22
|
+
const onTitlePage = await waitForImdbPath(page, `^/title/${id}/`);
|
|
23
|
+
if (await isChallengePage(page)) {
|
|
24
|
+
throw new CommandExecutionError('IMDb blocked this request', 'Try again with a normal browser session or extension mode');
|
|
25
|
+
}
|
|
26
|
+
if (!onTitlePage) {
|
|
27
|
+
throw new CommandExecutionError(`Title page did not finish loading: ${id}`, 'Retry the command; if it persists, IMDb may have changed their navigation flow');
|
|
28
|
+
}
|
|
29
|
+
const currentId = await getCurrentImdbId(page, 'tt');
|
|
30
|
+
if (currentId && currentId !== id) {
|
|
31
|
+
throw new CommandExecutionError(`IMDb redirected to a different title: ${currentId}`, 'Retry the command; if it persists, the title page may have changed');
|
|
32
|
+
}
|
|
33
|
+
// Single browser roundtrip: fetch title JSON-LD by type whitelist
|
|
34
|
+
const titleTypes = ['Movie', 'TVSeries', 'TVEpisode', 'TVMiniseries', 'TVMovie', 'TVSpecial', 'VideoGame', 'ShortFilm'];
|
|
35
|
+
const ld = await extractJsonLd(page, titleTypes);
|
|
36
|
+
if (!ld) {
|
|
37
|
+
throw new CommandExecutionError(`Title not found: ${id}`, 'Check the title ID and try again');
|
|
38
|
+
}
|
|
39
|
+
const data = ld;
|
|
40
|
+
const type = String(data['@type'] || '');
|
|
41
|
+
const isTvSeries = type === 'TVSeries' || type === 'TVMiniseries';
|
|
42
|
+
// Handle both array and single-object JSON-LD person fields
|
|
43
|
+
const toPeople = (arr) => {
|
|
44
|
+
if (!arr)
|
|
45
|
+
return '';
|
|
46
|
+
const list = Array.isArray(arr) ? arr : [arr];
|
|
47
|
+
return list
|
|
48
|
+
.slice(0, 5)
|
|
49
|
+
.map((p) => p.name || '')
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join(', ');
|
|
52
|
+
};
|
|
53
|
+
const year = (() => {
|
|
54
|
+
if (isTvSeries && typeof data.startDate === 'string') {
|
|
55
|
+
const startYear = data.startDate.split('-')[0] || '';
|
|
56
|
+
const endYear = typeof data.endDate === 'string' ? data.endDate.split('-')[0] || '' : '';
|
|
57
|
+
// Show "2024-" for ongoing series (no endDate) or "2010-2015" for ended ones
|
|
58
|
+
return endYear ? `${startYear}-${endYear}` : `${startYear}-`;
|
|
59
|
+
}
|
|
60
|
+
if (typeof data.datePublished === 'string') {
|
|
61
|
+
return data.datePublished.split('-')[0] || '';
|
|
62
|
+
}
|
|
63
|
+
return '';
|
|
64
|
+
})();
|
|
65
|
+
const directorField = isTvSeries ? 'creator' : 'director';
|
|
66
|
+
const directorValue = isTvSeries ? toPeople(data.creator) : toPeople(data.director);
|
|
67
|
+
const fields = {
|
|
68
|
+
title: String(data.name || ''),
|
|
69
|
+
type,
|
|
70
|
+
year,
|
|
71
|
+
rating: data.aggregateRating?.ratingValue != null ? String(data.aggregateRating.ratingValue) : '',
|
|
72
|
+
votes: data.aggregateRating?.ratingCount != null ? String(data.aggregateRating.ratingCount) : '',
|
|
73
|
+
genre: Array.isArray(data.genre) ? data.genre.join(', ') : String(data.genre || ''),
|
|
74
|
+
[directorField]: directorValue,
|
|
75
|
+
cast: toPeople(data.actor),
|
|
76
|
+
duration: formatDuration(String(data.duration || '')),
|
|
77
|
+
contentRating: String(data.contentRating || ''),
|
|
78
|
+
plot: String(data.description || ''),
|
|
79
|
+
url: `https://www.imdb.com/title/${id}/`,
|
|
80
|
+
};
|
|
81
|
+
if (isTvSeries) {
|
|
82
|
+
if (data.numberOfSeasons != null) {
|
|
83
|
+
fields.seasons = String(data.numberOfSeasons);
|
|
84
|
+
}
|
|
85
|
+
if (data.numberOfEpisodes != null) {
|
|
86
|
+
fields.episodes = String(data.numberOfEpisodes);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return Object.entries(fields)
|
|
90
|
+
.filter(([, value]) => value !== '')
|
|
91
|
+
.map(([field, value]) => ({ field, value }));
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CommandExecutionError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { extractJsonLd, forceEnglishUrl, isChallengePage } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch the IMDb Top 250 Movies list from JSON-LD structured data on the chart page.
|
|
6
|
+
*/
|
|
7
|
+
cli({
|
|
8
|
+
site: 'imdb',
|
|
9
|
+
name: 'top',
|
|
10
|
+
description: 'IMDb Top 250 Movies',
|
|
11
|
+
domain: 'www.imdb.com',
|
|
12
|
+
strategy: Strategy.PUBLIC,
|
|
13
|
+
browser: true,
|
|
14
|
+
args: [
|
|
15
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
16
|
+
],
|
|
17
|
+
columns: ['rank', 'title', 'rating', 'votes', 'genre', 'url'],
|
|
18
|
+
func: async (page, args) => {
|
|
19
|
+
const url = forceEnglishUrl('https://www.imdb.com/chart/top/');
|
|
20
|
+
await page.goto(url);
|
|
21
|
+
await page.wait(2);
|
|
22
|
+
if (await isChallengePage(page)) {
|
|
23
|
+
throw new CommandExecutionError('IMDb blocked this request', 'Try again with a normal browser session or extension mode');
|
|
24
|
+
}
|
|
25
|
+
// Extract the ItemList JSON-LD block which contains all chart entries
|
|
26
|
+
const ld = await extractJsonLd(page, 'ItemList');
|
|
27
|
+
if (!ld || !Array.isArray(ld.itemListElement)) {
|
|
28
|
+
throw new CommandExecutionError('Could not find chart data on page', 'IMDb may have changed their page structure');
|
|
29
|
+
}
|
|
30
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 250));
|
|
31
|
+
const items = ld.itemListElement.slice(0, limit);
|
|
32
|
+
return items.map((entry, index) => {
|
|
33
|
+
const item = entry.item || {};
|
|
34
|
+
const rating = item.aggregateRating || {};
|
|
35
|
+
const genre = Array.isArray(item.genre)
|
|
36
|
+
? item.genre.join(', ')
|
|
37
|
+
: String(item.genre || '');
|
|
38
|
+
// Normalize relative URLs to absolute IMDb URLs
|
|
39
|
+
let itemUrl = item.url || '';
|
|
40
|
+
if (itemUrl && !/^https?:\/\//.test(itemUrl)) {
|
|
41
|
+
itemUrl = 'https://www.imdb.com' + itemUrl;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
rank: entry.position || index + 1,
|
|
45
|
+
title: String(item.name || ''),
|
|
46
|
+
rating: rating.ratingValue != null ? String(rating.ratingValue) : '',
|
|
47
|
+
votes: rating.ratingCount != null ? String(rating.ratingCount) : '',
|
|
48
|
+
genre,
|
|
49
|
+
url: itemUrl,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CommandExecutionError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { extractJsonLd, forceEnglishUrl, isChallengePage } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch the IMDb Most Popular Movies (MovieMeter) list from JSON-LD structured data.
|
|
6
|
+
*/
|
|
7
|
+
cli({
|
|
8
|
+
site: 'imdb',
|
|
9
|
+
name: 'trending',
|
|
10
|
+
description: 'IMDb Most Popular Movies',
|
|
11
|
+
domain: 'www.imdb.com',
|
|
12
|
+
strategy: Strategy.PUBLIC,
|
|
13
|
+
browser: true,
|
|
14
|
+
args: [
|
|
15
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
16
|
+
],
|
|
17
|
+
columns: ['rank', 'title', 'rating', 'genre', 'url'],
|
|
18
|
+
func: async (page, args) => {
|
|
19
|
+
const url = forceEnglishUrl('https://www.imdb.com/chart/moviemeter/');
|
|
20
|
+
await page.goto(url);
|
|
21
|
+
await page.wait(2);
|
|
22
|
+
if (await isChallengePage(page)) {
|
|
23
|
+
throw new CommandExecutionError('IMDb blocked this request', 'Try again with a normal browser session or extension mode');
|
|
24
|
+
}
|
|
25
|
+
// Extract the ItemList JSON-LD block which contains all chart entries
|
|
26
|
+
const ld = await extractJsonLd(page, 'ItemList');
|
|
27
|
+
if (!ld || !Array.isArray(ld.itemListElement)) {
|
|
28
|
+
throw new CommandExecutionError('Could not find chart data on page', 'IMDb may have changed their page structure');
|
|
29
|
+
}
|
|
30
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
31
|
+
const items = ld.itemListElement.slice(0, limit);
|
|
32
|
+
return items.map((entry, index) => {
|
|
33
|
+
const item = entry.item || {};
|
|
34
|
+
const rating = item.aggregateRating || {};
|
|
35
|
+
const genre = Array.isArray(item.genre)
|
|
36
|
+
? item.genre.join(', ')
|
|
37
|
+
: String(item.genre || '');
|
|
38
|
+
// Normalize relative URLs to absolute IMDb URLs
|
|
39
|
+
let itemUrl = item.url || '';
|
|
40
|
+
if (itemUrl && !/^https?:\/\//.test(itemUrl)) {
|
|
41
|
+
itemUrl = 'https://www.imdb.com' + itemUrl;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
rank: entry.position || index + 1,
|
|
45
|
+
title: String(item.name || ''),
|
|
46
|
+
rating: rating.ratingValue != null ? String(rating.ratingValue) : '',
|
|
47
|
+
genre,
|
|
48
|
+
url: itemUrl,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { IPage } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize an IMDb title or person input to a bare ID.
|
|
4
|
+
* Accepts bare IDs, desktop URLs, mobile URLs, and URLs with language prefixes or query params.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeImdbId(input: string, prefix: 'tt' | 'nm'): string;
|
|
7
|
+
/**
|
|
8
|
+
* Convert an ISO 8601 duration string to a short human-readable format for table display.
|
|
9
|
+
* Example: PT2H28M -> 2h 28m.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatDuration(iso: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Force an IMDb page URL to use the English language parameter,
|
|
14
|
+
* reducing structural differences across localized pages.
|
|
15
|
+
*/
|
|
16
|
+
export declare function forceEnglishUrl(url: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Normalize IMDb title-type payloads that may be represented as an object,
|
|
19
|
+
* a raw string, or an empty text field with only an internal id.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeImdbTitleType(input: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* Extract structured JSON-LD data from the page.
|
|
24
|
+
* Accepts a single type string or an array of types to match against @type.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractJsonLd(page: IPage, type?: string | string[]): Promise<Record<string, unknown> | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Poll until the current IMDb page path matches the expected entity/search path.
|
|
29
|
+
*/
|
|
30
|
+
export declare function waitForImdbPath(page: IPage, pathPattern: string, timeoutMs?: number): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Wait until IMDb search results (or the search UI state) has rendered.
|
|
33
|
+
*/
|
|
34
|
+
export declare function waitForImdbSearchReady(page: IPage, timeoutMs?: number): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Wait until IMDb review cards (or the page review summary) has rendered.
|
|
37
|
+
*/
|
|
38
|
+
export declare function waitForImdbReviewsReady(page: IPage, timeoutMs?: number): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Read the current IMDb entity id from the page URL/canonical metadata.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getCurrentImdbId(page: IPage, prefix: 'tt' | 'nm'): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether the current page is an IMDb bot-challenge or verification page.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isChallengePage(page: IPage): Promise<boolean>;
|