'use strict'; const unprint = require('unprint'); const slugify = require('../utils/slugify'); const capitalize = require('../utils/capitalize'); const tryUrls = require('../utils/try-urls'); function scrapeAll(scenes, channel, discard = true) { return scenes.reduce((acc, { query, element }) => { const release = {}; release.url = unprint.query.url(element, null, { attribute: 'href', origin: channel.url }); const { hostname, pathname } = new URL(release.url); release.entryId = pathname.match(/_(\d+)/)?.[1]; release.channel = hostname.match(/(\w+)\.com/)?.[1]; if (discard && release.channel !== channel.slug) { acc.unextracted.concat(release); return acc; } release.title = query.content('.title, .informations h3'); release.duration = query.duration('.duration, .timer, .infos'); release.actors = query.content('.sub')?.split(/,\s*/); release.poster = query.img('.thumb, picture img'); acc.scenes = acc.scenes.concat(release); return acc; }, { scenes: [], unextracted: [], }); } async function fetchLatest(channel, page) { const res = await unprint.get(channel.parameters?.latest ? `${channel.parameters.latest}?page=${page}` : `${channel.url}/videos?page=${page}`, { selectAll: '.items .scene' }); if (res.ok) { return scrapeAll(res.context, channel); } return res.status; } function scrapeScene({ query, html }, { url, entity }) { const release = {}; release.entryId = new URL(url).pathname.match(/_(\d+)/)?.[1]; const title = query.content('.page_title h1, h2'); const wunfTitle = title.match(/wunf \d+/i)?.[0]; release.title = wunfTitle ? wunfTitle.toUpperCase() : title; release.description = query.content('.info_container .description'); release.date = query.date('.info_container .info_line:nth-child(1)', 'YYYY-MM-DD') || query.date('.description', 'D MMMM YYYY', { match: /\d{1,2} \w+ \d{4}/ }); release.actors = query.all('.girl_item, .starring .item').map((actorEl) => { const avatar = unprint.query.img(actorEl); return { name: capitalize(unprint.query.content(actorEl, '.name, .informations p'), { uncapitalize: true }), url: unprint.query.url(actorEl, null, { origin: entity.url }), avatar, }; }); release.duration = query.duration('.infos .description'); if (!release.duration) { const duration = query.content('.info_container .info_line:nth-child(2)'); release.duration = (duration.match(/(\d+) hour/)?.[1] || 0) * 3600 + (duration.match(/(\d+) minutes/)?.[1] || 0) * 60; } release.tags = query.contents('.tags a:not(.more_tag)'); release.poster = html.match(/image: "(.*?)"/)?.[1]; release.trailer = html.match(/url: "(.*mp4.*)"/g)?.map((src) => ({ src: src.match(/"(.*)"/)?.[1], quality: Number(src.match(/[-/](\d+)p/)?.[1]), })); if (query.exists('.download-icon-4k')) { release.qualities = [2160]; } return release; } function scrapeProfile({ query }, url, entity) { const profile = { url }; profile.avatar = query.img('.actor img, .avatar img'); profile.nationality = query.content(['.nationality, .nationnality', '//strong[contains(text(), "Nationnality")]'])?.replace(/nationn?ality\s*:/i, '').trim(); // sic profile.scenes = scrapeAll(unprint.initAll(query.all('.videos .item, .list .scene.item')), entity, false); return profile; } async function getActorUrl(actor) { if (actor.url) { return [actor.url]; } const res = await unprint.get('https://www.woodmancastingx.com'); if (!res.ok) { return res.status; } const searchUrl = unprint.prefixUrl(res.context.html.match(/"(.*searchCompletion\.js)"/)?.[1], 'https://www.woodmancastingx.com'); if (!searchUrl) { return null; } const searchRes = await unprint.get(searchUrl); if (!searchRes.ok) { return searchRes.status; } const [actorId] = searchRes.data.actors.find(([_actorId, actorName]) => slugify(actorName) === actor.slug) || []; if (!actorId) { return null; } // WUNF has the same avatars at higher quality, but not all performers return [ `https://www.wakeupnfuck.com/actor/${actor.slug}_${actorId}`, `https://www.woodmancastingx.com/girl/${actor.slug}_${actorId}`, ]; } async function fetchProfile(actor, entity) { const actorUrls = await getActorUrl(actor); if (!Array.isArray(actorUrls)) { return actorUrls; } const { res, url } = await tryUrls(actorUrls); if (res.ok) { return scrapeProfile(res.context, url, entity); } return res.status; } module.exports = { fetchLatest, scrapeScene, fetchProfile, };