const { Source, RunnerScraper, Hoster } = require("source-scraper-core"); const { PuppeteerRunner } = require("source-scraper-puppeteer-runner"); const cheerio = require("cheerio"); const u = require("url"); class NinexbuddySource extends Source { constructor(source) { super(source); this.quality = source.quality; } } function parseSourceQuality(raw) { const ar1 = /^([a-zA-Z\d]+)\s*-\s*(\d+)([a-zA-Z]+)?$/i.exec(raw); if (ar1 !== null && ar1.length >= 4) return { raw, description: ar1[1], value: Number(ar1[2]), unit: ar1[3] }; const ar2 = /^(\d+)([a-zA-Z]+)\s*\(?([a-zA-Z\s]+)\)?$/i.exec(raw); if (ar2 !== null && ar2.length >= 3) return { raw, description: ar2[3], value: Number(ar2[1]), unit: ar2[2] }; } function parseHosterName(raw) { const ar = /^(?:embed\s*-)?\s*([a-zA-Z\d.]+)$/i.exec(raw.trim()); if (ar === null || ar.length < 2) return; return ar[ar.length - 1]; } function isDefined(value) { return typeof value !== 'undefined'; } class NinexbuddyScraper extends RunnerScraper { constructor() { super(); this.name = 'ninexbuddy'; this.domains = []; this.urlPattern = /.*/i; this.runner = new PuppeteerRunner(); this.defaultOptions = { runnerOptions: { navigationOptions: { waitUntil: 'networkidle0' } } }; } transformUrl(url) { return `https://9xbuddy.com/process?url=${url}`; } async execWithArgs({ page, url: ninexbuddyUrl }) { const isHoster = !!(await page.$('.playlist_el').catch(() => false)); if (isHoster) { const items = await page.$$('.playlist_el .text-blue'); const hosters = (await Promise.all(items.map(async (item) => { const urlPromise = item.getProperty('href').then(async (e) => e.jsonValue()).catch(() => undefined); const namePromise = item.getProperty('textContent').then(async (e) => e.jsonValue()).catch(() => undefined); const [url, name] = await Promise.all([urlPromise, namePromise]); const hosterUrl = u.parse(url, true).query.url; if (!hosterUrl) return; const parsed = name ? parseHosterName(name) : undefined; if (!parsed) return; return new Hoster({ url: hosterUrl, name: parsed, }); }))).filter(isDefined); return { ninexbuddyUrl, hosters }; } else { const selector = '.tracking-wide.items-center.night-white'; const items = await page.$$(selector); const sources = (await Promise.all(items.map(async (item) => { const formatPromise = item.$eval('.w-24.text-blue', e => e.textContent).catch(() => undefined); const qualityPromise = item.$eval('.w-1\\/2', e => e.textContent).catch(() => undefined); const urlPromise = item.$eval('.leading-normal.sm\\:leading-none', e => e.getAttribute('href')).catch(() => undefined); const [format, quality, url] = await Promise.all([formatPromise, qualityPromise, urlPromise]); if (!url) return; return new NinexbuddySource({ url, quality: quality ? parseSourceQuality(quality) : undefined, type: format || undefined, codec: undefined }); }))).filter(isDefined); const poster = await page.$eval('img.shadow.border-2.border-grey-lighter.h-48', e => e.src).catch(() => undefined); return { ninexbuddyUrl, title: await page.title(), poster: poster && !poster.startsWith('data:image') ? poster : undefined, sources }; } } } module.exports = { NinexbuddySource, NinexbuddyScraper }