import { CommandExecutionError } from '@jackwener/opencli/errors'; import { cli, Strategy } from '@jackwener/opencli/registry'; import { extractJsonLd, forceEnglishUrl, isChallengePage } from './utils.js'; /** * Fetch the IMDb Top 250 Movies list from JSON-LD structured data on the chart page. */ cli({ site: 'imdb', name: 'top', access: 'read', description: 'IMDb Top 250 Movies', domain: 'www.imdb.com', strategy: Strategy.PUBLIC, browser: true, args: [ { name: 'limit', type: 'int', default: 20, help: 'Number of results' }, ], columns: ['rank', 'title', 'rating', 'votes', 'genre', 'url'], func: async (page, args) => { const url = forceEnglishUrl('https://www.imdb.com/chart/top/'); await page.goto(url); await page.wait(2); if (await isChallengePage(page)) { throw new CommandExecutionError('IMDb blocked this request', 'Try again with a normal browser session or extension mode'); } // Extract the ItemList JSON-LD block which contains all chart entries const ld = await extractJsonLd(page, 'ItemList'); if (!ld || !Array.isArray(ld.itemListElement)) { throw new CommandExecutionError('Could not find chart data on page', 'IMDb may have changed their page structure'); } const limit = Math.max(1, Math.min(Number(args.limit) || 20, 250)); const items = ld.itemListElement.slice(0, limit); return items.map((entry, index) => { const item = entry.item || {}; const rating = item.aggregateRating || {}; const genre = Array.isArray(item.genre) ? item.genre.join(', ') : String(item.genre || ''); // Normalize relative URLs to absolute IMDb URLs let itemUrl = item.url || ''; if (itemUrl && !/^https?:\/\//.test(itemUrl)) { itemUrl = 'https://www.imdb.com' + itemUrl; } return { rank: entry.position || index + 1, title: String(item.name || ''), rating: rating.ratingValue != null ? String(rating.ratingValue) : '', votes: rating.ratingCount != null ? String(rating.ratingCount) : '', genre, url: itemUrl, }; }); }, });