'use strict'; const unprint = require('unprint'); const qu = require('../utils/qu'); const http = require('../utils/http'); const slugify = require('../utils/slugify'); function scrapeAll(scenes) { return scenes.map(({ query }) => { const release = {}; const href = query.url('.shoot-link'); release.url = `https://www.kink.com${href}`; release.shootId = href.split('/').slice(-1)[0]; release.entryId = release.shootId; release.title = query.content('.shoot-thumb-title a', true); release.date = query.date('.date', 'MMM DD, YYYY'); release.actors = query.all('.shoot-thumb-models a').map((actorEl) => ({ name: unprint.query.content(actorEl), url: unprint.query.url(actorEl, null, { origin: 'https://www.kink.com' }), })); release.rating = query.number('.thumb-ratings') / 10; release.poster = query.img('.adimage'); release.photos = query.imgs('.rollover .roll-image', { attribute: 'data-imagesrc' }).map((photo) => [ photo.replace('410/', '830/'), photo, ]); release.duration = query.dur('.video span'); return release; }); } function scrapeScene({ query }, url) { const release = { url }; release.shootId = new URL(url).pathname.split('/')[2]; release.entryId = release.shootId; release.title = query.attribute('.shoot-title .favorite-button', 'data-title') || query.content('.shoot-title'); release.description = query.content('.description-text'); release.date = query.date('.shoot-date', 'MMMM DD, YYYY'); release.actors = query.elements('.names a').map((actorEl) => ({ name: unprint.query.content(actorEl).replace(/,\s*/, ''), url: unprint.query.url(actorEl, null, { origin: 'https://www.kink.com' }), })); release.director = query.content('.director-name'); release.photos = query.imgs('.gallery .thumb img, #gallerySlider .gallery-img', 'data-image-file'); release.poster = query.poster(); release.tags = query.contents('.tag-list a[href*="/tag"]').map((tag) => tag.replace(/,\s*/, '')); const trailer = query.attribute('.player span[data-type="trailer-src"]', 'data-url'); if (trailer) { release.trailer = [ { src: trailer.replace('480p', '1080p'), quality: 1080, }, { src: trailer.replace('480p', '720p'), quality: 720, }, { src: trailer, quality: 480, }, { src: trailer.replace('480p', '360p'), quality: 360, }, ]; } release.channel = slugify(query.url('.shoot-logo a')?.split('/').slice(-1)[0], ''); console.log(release); return release; } async function fetchActorReleases(actorUrl, page = 1, accReleases = []) { const res = await qu.get(`${actorUrl}?page=${page}`); if (res.ok) { const releases = scrapeAll(qu.initAll(res.item.el, '.shoot-list .shoot')); const hasNextPage = res.item.query.exists('.paginated-nav li:last-child:not(.disabled)'); if (hasNextPage) { return fetchActorReleases(actorUrl, page + 1, accReleases.concat(releases)); } return accReleases.concat(releases); } return accReleases; } async function scrapeProfile({ query }, actorUrl, include) { const profile = {}; profile.description = query.q('.bio #expand-text', true); const tags = query.all('.bio-tags a', true); if (tags.includes('brunette') || tags.includes('brunet')) profile.hairColor = 'brown'; if (tags.includes('blonde') || tags.includes('blond')) profile.hairColor = 'blonde'; if (tags.includes('black hair')) profile.hairColor = 'black'; if (tags.includes('redhead')) profile.hairColor = 'red'; if (tags.includes('natural boobs')) profile.naturalBoobs = true; if (tags.includes('fake boobs')) profile.naturalBoobs = false; if (tags.includes('white')) profile.ethnicity = 'white'; if (tags.includes('latin')) profile.ethnicity = 'latin'; if (tags.includes('Black')) profile.ethnicity = 'black'; if (tags.includes('pierced nipples')) profile.hasPiercings = true; if (tags.includes('tattoo')) profile.hasTattoos = true; if (tags.includes('foreskin')) profile.hasForeskin = true; if ((tags.includes('big dick') || tags.includes('foreskin')) && (tags.includes('fake boobs') || tags.includes('big tits'))) profile.gender = 'transsexual'; profile.avatar = query.img('.bio-slider-img, .bio-img:not([src*="Missing"])'); profile.social = query.urls('a.social-link'); if (include.releases) { profile.releases = await fetchActorReleases(actorUrl); } return profile; } async function fetchLatest(site, page = 1) { const { tab } = await http.getBrowserSession('kink', { headless: false }); const res = await tab.goto(`https://www.kink.com/search?type=shoots&channelIds=${site.slug}&sort=published&page=${page}`); const status = res.status(); if (status === 200) { const html = await tab.content(); const items = unprint.initAll(html, '.results .shoot-card'); const scenes = scrapeAll(items, site); await tab.close(); return scenes; } return status; } async function fetchScene(url, channel) { const { tab } = await http.getBrowserSession('kink'); const res = await tab.goto(url); const status = res.status(); if (status === 200) { const html = await tab.content(); const item = unprint.init(html); const scene = scrapeScene(item, url, channel); await tab.close(); return scene; } return status; } async function fetchProfile({ name: actorName }, entity, include) { const searchRes = await qu.getAll(`https://kink.com/search?type=performers&q=${actorName}`, '.model'); if (searchRes.ok) { const actorItem = searchRes.items.find((item) => item.query.exists(`.model-link img[alt="${actorName}"]`)); if (actorItem) { const actorPath = actorItem.query.url('.model-link'); const actorUrl = `https://kink.com${actorPath}`; const actorRes = await qu.get(actorUrl); if (actorRes.ok) { return scrapeProfile(actorRes.item, actorUrl, include); } return actorRes.status; } return null; } return searchRes.status; } module.exports = { // beforeNetwork, fetchLatest, fetchScene, fetchProfile, };