'use strict'; const unprint = require('unprint'); const http = require('../utils/http'); const slugify = require('../utils/slugify'); function scrapeAll(scenes, entity) { return scenes.map(({ query }) => { const release = {}; const networkUrl = entity.type === 'channel' ? entity.parent.url : entity.url; const href = query.url('.shoot-link'); release.url = `${networkUrl}${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: networkUrl }), })); 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/') .replace('_thumb', '_full'), photo, ]); release.trailer = `https://cdnp.kink.com/imagedb/${release.entryId}/trailer/${release.entryId}_trailer_high.mp4`; release.duration = query.dur('.video span'); return release; }); } function scrapeScene({ query }, url, entity) { 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: entity.type === 'channel' ? entity.parent.url : entity.url }), })); release.director = query.content('.director-name'); release.photos = query.imgs('.gallery .thumb img, #gallerySlider .gallery-img', { attribute: 'data-image-file' }); release.poster = query.poster(); release.trailer = query.dataset('.player span[data-type="trailer-src"]', 'url') || `https://cdnp.kink.com/imagedb/${release.entryId}/trailer/${release.entryId}_trailer_high.mp4`; release.tags = query.contents('.tag-list a[href*="/tag"]').map((tag) => tag.replace(/,\s*/, '')); release.channel = slugify(query.url('.shoot-logo a')?.split('/').slice(-1)[0], ''); return release; } async function fetchActorReleases(actorId, entity, page = 1, accReleases = []) { const networkUrl = entity.type === 'channel' ? entity.parent.url : entity.url; const { tab } = await http.getBrowserSession('kink'); const res = await tab.goto(`${networkUrl}/search?type=shoots&performerIds=${actorId}&sort=published&page=${page}`); if (res.status() === 200) { const html = await tab.content(); const item = unprint.init(html); const releases = scrapeAll(unprint.initAll(html, '.results .shoot-card'), entity); const hasNextPage = item.query.exists('.paginated-nav li:last-child:not(.disabled)'); await tab.close(); if (hasNextPage) { return fetchActorReleases(actorId, entity, page + 1, accReleases.concat(releases)); } return accReleases.concat(releases); } await tab.close(); return accReleases; } async function scrapeProfile({ query }, actorUrl, entity, include) { const profile = {}; profile.entryId = actorUrl.match(/\/model\/(\d+)\//)?.[1] || query.attribute('.favorite-button.bio-favorite', 'data-id'); profile.description = query.content('.bio-outer #expand-text'); const tags = query.contents('.bio-tags a').map((tag) => tag.toLowerCase()); 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.entryId) { profile.releases = await fetchActorReleases(profile.entryId, entity); } return profile; } async function fetchLatest(channel, page = 1) { const { tab } = await http.getBrowserSession('kink'); const res = await tab.goto(`${channel.parent.url}/search?type=shoots&channelIds=${channel.parameters?.slug || channel.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, channel); await tab.close(); return scenes; } await tab.close(); 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; } await tab.close(); return status; } async function fetchProfile({ name: actorName }, entity, options) { const networkUrl = entity.type === 'channel' ? entity.parent.url : entity.url; const { tab } = await http.getBrowserSession('kink'); const searchRes = await tab.goto(`${networkUrl}/search?type=performers&q=${actorName}`); const searchStatus = searchRes.status(); if (searchStatus === 200) { const searchHtml = await tab.content(); const searchResItems = unprint.initAll(searchHtml, '.model'); const actorItem = searchResItems.find((item) => item.query.exists(`.model-link img[alt="${actorName}"]`)); if (actorItem) { const actorPath = actorItem.query.url('.model-link'); const actorUrl = `${networkUrl}${actorPath}`; const actorRes = await tab.goto(actorUrl); const actorStatus = actorRes.status(); if (actorStatus === 200) { const actorHtml = await tab.content(); const item = unprint.init(actorHtml); await tab.close(); return scrapeProfile(item, actorUrl, entity, options); } await tab.close(); return actorRes.status; } return null; } return searchRes.status; } module.exports = { // beforeNetwork, fetchLatest, fetchScene, fetchProfile, };