Added alternative layout to Insex for updated Topgrl and Sexually Broken sites.
| Before Width: | Height: | Size: 798 KiB After Width: | Height: | Size: 1.8 MiB | 
| Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 798 KiB | 
| Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.3 KiB | 
| Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.1 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB | 
|  | @ -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', | ||||
| 	}, | ||||
| 	{ | ||||
|  |  | |||
|  | @ -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', | ||||
|  |  | |||
|  | @ -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'], | ||||
|  |  | |||
|  | @ -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') : []), | ||||
|  |  | |||
|  | @ -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 = { | ||||
|  |  | |||