diff --git a/public/img/tags/double-dildo-blowjob/4.jpeg b/public/img/tags/double-dildo-blowjob/4.jpeg index 7ddeb00e..50fb9f17 100644 Binary files a/public/img/tags/double-dildo-blowjob/4.jpeg and b/public/img/tags/double-dildo-blowjob/4.jpeg differ diff --git a/public/img/tags/double-dildo-blowjob/4a.jpeg b/public/img/tags/double-dildo-blowjob/4a.jpeg index 50fb9f17..7ddeb00e 100644 Binary files a/public/img/tags/double-dildo-blowjob/4a.jpeg and b/public/img/tags/double-dildo-blowjob/4a.jpeg differ diff --git a/public/img/tags/double-dildo-blowjob/lazy/4.jpeg b/public/img/tags/double-dildo-blowjob/lazy/4.jpeg index 4902ae10..bc534ab4 100644 Binary files a/public/img/tags/double-dildo-blowjob/lazy/4.jpeg and b/public/img/tags/double-dildo-blowjob/lazy/4.jpeg differ diff --git a/public/img/tags/double-dildo-blowjob/lazy/4a.jpeg b/public/img/tags/double-dildo-blowjob/lazy/4a.jpeg index bc534ab4..4902ae10 100644 Binary files a/public/img/tags/double-dildo-blowjob/lazy/4a.jpeg and b/public/img/tags/double-dildo-blowjob/lazy/4a.jpeg differ diff --git a/public/img/tags/double-dildo-blowjob/thumbs/4.jpeg b/public/img/tags/double-dildo-blowjob/thumbs/4.jpeg index 8e0baf9f..4ea98ce5 100644 Binary files a/public/img/tags/double-dildo-blowjob/thumbs/4.jpeg and b/public/img/tags/double-dildo-blowjob/thumbs/4.jpeg differ diff --git a/public/img/tags/double-dildo-blowjob/thumbs/4a.jpeg b/public/img/tags/double-dildo-blowjob/thumbs/4a.jpeg index 4ea98ce5..8e0baf9f 100644 Binary files a/public/img/tags/double-dildo-blowjob/thumbs/4a.jpeg and b/public/img/tags/double-dildo-blowjob/thumbs/4a.jpeg differ diff --git a/seeds/00_tags.js b/seeds/00_tags.js index 649314f8..f9ec1f3e 100644 --- a/seeds/00_tags.js +++ b/seeds/00_tags.js @@ -610,7 +610,7 @@ const tags = [ name: 'MFM threesome', slug: 'mfm', priority: 9, - description: 'Two men fucking one woman, but not eachother. Typically involves a \'spitroast\', where one guy gets a blowjob and the other fucks her pussy.', + description: 'Two men fucking one woman, but not eachother. Typically involves a \'spitroast\', where one guy gets a blowjob and the other fucks her pussy or ass.', group: 'group', }, { diff --git a/seeds/02_sites.js b/seeds/02_sites.js index daf9075c..c0cceab1 100644 --- a/seeds/02_sites.js +++ b/seeds/02_sites.js @@ -3380,6 +3380,10 @@ const sites = [ url: 'https://www.sexuallybroken.com', tags: ['bdsm'], parent: 'insex', + parameters: { + scraper: 'alt', + latest: 'https://www.sexuallybroken.com/sb', + }, }, { slug: 'infernalrestraints', @@ -3411,6 +3415,10 @@ const sites = [ url: 'https://www.topgrl.com', tags: ['bdsm', 'femdom'], parent: 'insex', + parameters: { + scraper: 'alt', + latest: 'https://www.topgrl.com/tg', + }, }, { slug: 'paintoy', diff --git a/seeds/04_media.js b/seeds/04_media.js index cb20ed21..118695f0 100644 --- a/seeds/04_media.js +++ b/seeds/04_media.js @@ -730,6 +730,7 @@ const tagPhotos = [ ['double-dildo-anal', 2, 'Adria Rae and Megan Rain in "Best Friends Anal" for Holed'], ['double-dildo-anal', 4, 'Ashley Fires, Sammie Rhodes and Kiara Diane in "Real Romance" for Reality Kings'], ['double-dildo-blowjob', 6, 'Indigo Vanity and Kendall Woods in "My White Stepdad: Part 3" for Digital Playground'], + ['double-dildo-blowjob', 8, 'Morgan Lee and Reena Sky in "Power Play" for Brazzers'], ['double-dildo-blowjob', 1, 'Aidra Fox and Reena Sky in "Reena\'s Got A Staring Problem" for Brazzers'], ['double-dildo-blowjob', 7, 'Jasmine Webb and Aria Alexander in "Homeless Horny" for Digital Playground'], ['double-dildo-blowjob', 3, 'Angela White and Madison Ivy in "Sunbathing Babes" for Brazzers'], diff --git a/src/media.js b/src/media.js index 93a329d5..a6808917 100644 --- a/src/media.js +++ b/src/media.js @@ -659,7 +659,7 @@ async function associateReleaseMedia(releases, type = 'release') { ...acc, [release.id]: [ ...(argv.images && argv.poster ? toBaseMedias([release.poster], 'posters') : []), - ...(argv.images && argv.poster ? toBaseMedias(release.covers, 'covers') : []), + ...(argv.images && argv.covers ? toBaseMedias(release.covers, 'covers') : []), ...(argv.images && argv.photos ? toBaseMedias(release.photos, 'photos') : []), ...(argv.videos && argv.trailer ? toBaseMedias([release.trailer], 'trailers') : []), ...(argv.videos && argv.teaser ? toBaseMedias([release.teaser], 'teasers') : []), diff --git a/src/scrapers/insex.js b/src/scrapers/insex.js index 4aec50bc..26efaaa2 100644 --- a/src/scrapers/insex.js +++ b/src/scrapers/insex.js @@ -1,22 +1,19 @@ 'use strict'; -const bhttp = require('bhttp'); -const { get, exa, ed } = require('../utils/q'); +const qu = require('../utils/qu'); +const http = require('../utils/http'); +const slugify = require('../utils/slugify'); -function scrapeLatest(html, site) { - const scenes = site.slug === 'paintoy' - ? exa(html, '#articleTable table[cellspacing="2"]') - : exa(html, 'body > table'); - - return scenes.map(({ qu }) => { +function scrapeLatest(scenes, site) { + return scenes.map(({ query }) => { // if (q('.articleTitleText')) return scrapeFirstLatest(ctx(el), site); const release = {}; - const titleEl = qu.q('.galleryTitleText, .articleTitleText'); + const titleEl = query.q('.galleryTitleText, .articleTitleText'); const [title, ...actors] = titleEl.textContent.split('|'); - const date = qu.date('.articlePostDateText td', 'MMM D, YYYY'); + const date = query.date('.articlePostDateText td', 'MMM D, YYYY'); - const url = qu.url(titleEl, 'a'); + const url = query.url(titleEl, 'a'); [release.entryId] = url.split('/').slice(-2); release.url = `${site.url}${url}`; @@ -26,20 +23,20 @@ function scrapeLatest(html, site) { } else { // title should contain date instead, not applicable in brief mode release.title = title.slice(title.indexOf(':') + 1).trim(); - release.date = ed(title.slice(0, title.indexOf(':')), 'MMM D, YYYY'); + release.date = qu.ed(title.slice(0, title.indexOf(':')), 'MMM D, YYYY'); } release.actors = actors.map(actor => actor.trim()); - const description = qu.q('.articleCopyText', true); + const description = query.q('.articleCopyText', true); if (description) release.description = description.slice(0, description.lastIndexOf('(')); - const duration = qu.dur('.articleCopyText a:nth-child(2)'); + const duration = query.dur('.articleCopyText a:nth-child(2)'); if (duration) release.duration = duration; - release.likes = parseInt(qu.q('.articlePostDateText td:nth-child(3)', true), 10); + release.likes = parseInt(query.q('.articlePostDateText td:nth-child(3)', true), 10); - const cover = qu.img('a img'); + const cover = query.img('a img'); release.covers = [[ cover.replace('_thumbnail', ''), cover, @@ -49,56 +46,123 @@ function scrapeLatest(html, site) { }); } -function scrapeScene({ qu }, site) { +function scrapeLatestAlt(scenes, site) { + return scenes.map(({ query }) => { + const release = {}; + + release.url = query.url('figure a', 'href', { origin: site.parameters.latest }); + + release.title = query.cnt('.has-text-weight-bold'); + release.date = query.date('span.tag', 'YYYY-MM-DD'); + release.actors = query.cnts('a.tag'); + + const cover = query.img('.image img'); + + release.poster = cover.replace('poster_noplay', 'trailer_noplay'); + release.covers = [cover]; + + release.entryId = `${qu.formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; + + return release; + }); +} + +function scrapeScene({ query }, site) { const release = {}; - const titleEl = qu.q('.articleTitleText'); + const titleEl = query.q('.articleTitleText'); const [title, ...actors] = titleEl.textContent.split('|'); - const url = qu.url(titleEl, 'a'); + const url = query.url(titleEl, 'a'); [release.entryId] = url.split('/').slice(-2); release.url = `${site.url}${url}`; release.title = title.trim(); - release.description = qu.q('.articleCopyText', true); + release.description = query.q('.articleCopyText', true); release.actors = actors.map(actor => actor.trim()); - release.date = qu.date('.articlePostDateText', 'MMMM D, YYYY'); - release.duration = qu.dur('.articlePostDateText a:nth-child(2)'); + release.date = query.date('.articlePostDateText', 'MMMM D, YYYY'); + release.duration = query.dur('.articlePostDateText a:nth-child(2)'); - const [cover, ...photos] = qu.imgs('img[src*="images"]'); + const [cover, ...photos] = query.imgs('img[src*="images"]'); release.covers = [cover]; release.photos = photos; - release.poster = qu.poster(); + release.poster = query.poster(); - const trailer = qu.trailer(); + const trailer = query.trailer(); if (trailer) release.trailer = { src: trailer }; return release; } -async function fetchLatest(site, page = 1) { - const url = site.slug === 'paintoy' // paintoy's site is partially broken, use front page - ? `${site.url}/corporal/punishment/gallery.php?type=brief&page=${page}` - : `${site.url}/scripts/switch_tour.php?type=brief&page=${page}`; +async function scrapeSceneAlt({ query }, url, channel, session) { + const release = {}; - const res = await bhttp.get(url, { - type: 'brief', - page, - }); + release.title = query.cnt('.columns div.is-size-5'); + release.description = query.cnt('.has-background-black-ter > div:nth-child(4)'); + release.date = query.date('.has-text-white-ter span.tag', 'YYYY-MM-DD'); - if (res.statusCode === 200) { - return scrapeLatest(site.slug === 'paintoy' ? res.body.toString() : res.body.html, site); + release.actors = query.cnts('.has-text-white-ter a.tag[href*="home.php"]'); + release.tags = query.cnts('.has-background-black-ter > div:nth-child(6) > span'); + + release.poster = query.img('#videoPlayer, #iodvideo', 'poster'); + release.photos = query.imgs('body > div:nth-child(6) img'); + + release.entryId = `${qu.formatDate(release.date, 'YYYY-MM-DD')}-${slugify(release.title)}`; + + release.trailer = query.video(); + + if (!release.trailer) { + const trailerRes = await http.get(`${channel.url}/api/play-api.php`, null, { useSession: session }); + + if (trailerRes.ok) { + release.trailer = trailerRes.body; + } } - return null; + return release; +} + +async function fetchLatest(site, page = 1) { + const url = (site.parameters?.scraper === 'alt' && `${site.parameters.latest}/home.php?o=latest&p=${page}`) + // || (site.slug === 'paintoy' && `${site.url}/corporal/punishment/gallery.php?type=brief&page=${page}`) // paintoy's site is (was?) partially broken, use front page + || `${site.url}/scripts/switch_tour.php?type=brief&page=${page}`; + + const res = await ((site.parameters?.scraper === 'alt' && qu.getAll(url, 'body > .columns .column')) + // || (site.slug === 'paintoy' && qu.getAll(url, '#articleTable table[cellspacing="2"]')) + || qu.get(url)); // JSON containing html as a property + + if (res.ok) { + if (site.parameters?.scraper === 'alt') { + return scrapeLatestAlt(res.items, site); + } + + /* + if (site.slug === 'paintoy') { + return scrapeLatest(res.items, site); + } + */ + + return scrapeLatest(qu.extractAll(res.body.html, 'body > table'), site); + } + + return res.status; } async function fetchScene(url, site) { - const res = await get(url); + const session = http.session(); + const res = await qu.get(url, null, null, { useSession: session }); - return res.ok ? scrapeScene(res.item, site) : res.status; + if (res.ok) { + if (site.parameters?.scraper === 'alt') { + return scrapeSceneAlt(res.item, url, site, session); + } + + return scrapeScene(res.item, site); + } + + return res.status; } module.exports = {