From 06a5f5bec7f9d8dcbae4cd7045048ceefc0a79fd Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Fri, 24 Jul 2026 05:52:17 +0200 Subject: [PATCH] Extended Aziani scraper to support Cosplayground. --- seeds/00_tags.js | 4 ++++ seeds/01_networks.js | 6 ++++++ seeds/02_sites.js | 20 +++++++++++++++++ src/scrapers/actors.js | 1 + src/scrapers/aziani.js | 46 +++++++++++++++++++++++++--------------- src/scrapers/releases.js | 1 + 6 files changed, 61 insertions(+), 17 deletions(-) diff --git a/seeds/00_tags.js b/seeds/00_tags.js index 0de59c3f..75b13b34 100755 --- a/seeds/00_tags.js +++ b/seeds/00_tags.js @@ -1715,6 +1715,10 @@ const aliases = [ name: 'busty - big boobs', for: 'big-boobs', }, + { + name: 'big breasts', + for: 'big-boobs', + }, { name: 'busty: big beautiful breast', for: 'big-boobs', diff --git a/seeds/01_networks.js b/seeds/01_networks.js index 1ad71d00..a5bd4a18 100755 --- a/seeds/01_networks.js +++ b/seeds/01_networks.js @@ -284,6 +284,7 @@ const networks = [ areaId: '3b4c609c-6a0d-4cb9-9cce-0605f32b79ec', blockId: 114458, scene: 'https://aziani.com', + api: 'https://azianistudios.com/tour_api.php', cdn: 'https://c75c0c3063.mjedge.net', }, }, @@ -593,6 +594,11 @@ const networks = [ concurrency: 1, }, }, + { + slug: 'ksm', + name: 'KSM', + hasLogo: false, + }, { slug: 'letsdoeit', name: 'LetsDoeIt', diff --git a/seeds/02_sites.js b/seeds/02_sites.js index 7feea4f4..4dec752e 100755 --- a/seeds/02_sites.js +++ b/seeds/02_sites.js @@ -13529,6 +13529,26 @@ const sites = [ }, }, }, + // SKM + { + slug: 'cosplayground', + name: 'Cosplayground', + url: 'https://cosplayground.com', + tags: ['cosplay'], + parent: 'ksm', + independent: true, + parameters: { + // seems to use same CMS as Aziani + api: 'https://api.cosplayground.com/tour_api.php', + seriesId: null, + areaId: '2bc2444e-a053-40a2-bc4b-ca3618521563', + cdn: 'https://c880f36aa8.mjedge.net', + entryIdSlug: true, + videos: '/videos', + scenePath: '/videos', + modelPath: '/models', + }, + }, // TEEN CORE CLUB { name: 'Analyzed Girls', diff --git a/src/scrapers/actors.js b/src/scrapers/actors.js index 8a285a20..eba2fa62 100644 --- a/src/scrapers/actors.js +++ b/src/scrapers/actors.js @@ -279,6 +279,7 @@ module.exports = { // etc '18vr': badoink, theflourishxxx: theflourish, + cosplayground: aziani, pierrewoodman, exploitedx, // only from known URL that will specify site fullpornnetwork, diff --git a/src/scrapers/aziani.js b/src/scrapers/aziani.js index caf0df10..826550c6 100755 --- a/src/scrapers/aziani.js +++ b/src/scrapers/aziani.js @@ -6,11 +6,13 @@ const { decode } = require('html-entities'); const slugify = require('../utils/slugify'); const { feetInchesToCm, femaleFeetUsToEu } = require('../utils/convert'); +const tagKeys = ['tags', 'category', 'categories']; + function scrapeScene(data, channel, parameters) { const release = {}; release.entryId = data.cms_set_id; - release.url = `${parameters.scene || channel.url}/video/${data.cms_set_id}`; + release.url = `${parameters.scene || channel.url}${parameters.scenePath || '/video'}/${parameters.entryIdSlug ? data.slug : data.cms_set_id}`; release.title = data.name; release.description = data.description; @@ -20,7 +22,7 @@ function scrapeScene(data, channel, parameters) { release.actors = data.data_types.find((dataType) => dataType.data_type === 'Models' || dataType.data_type === 'Talent')?.data_values.map((actor) => ({ name: actor.name, - url: `${channel.url}/model/${actor.cms_data_value_id}?models=${encodeURI(actor.name)}`, // slug does not work unless it's also the ID + url: `${channel.url}${parameters.modelPath || '/model'}/${parameters.entryIdSlug && actor.slug ? actor.slug : actor.cms_data_value_id}?models=${encodeURI(actor.name)}`, // slug does not work unless it's also the ID })); release.directors = data.data_types.find((dataType) => dataType.data_type === 'Videographers')?.data_values.map((director) => ({ @@ -29,7 +31,7 @@ function scrapeScene(data, channel, parameters) { })); release.tags = data.data_types - .filter((dataType) => dataType.data_type === 'Tags' || dataType.data_type === 'Category') + .filter((dataType) => tagKeys.includes(dataType.data_type?.toLowerCase())) .flatMap((tags) => tags.data_values.map((tag) => tag.name)); const poster = data.preview_formatted.thumb; @@ -66,13 +68,14 @@ function scrapeScene(data, channel, parameters) { release.trailer = unprint.prefixUrl(`${trailer.fileuri}?${trailer.signature}`, parameters.cdn); } - release.channel = slugify(data.data_types.find((dataType) => dataType.data_type === 'Series')?.data_values[0]?.name, ''); + release.channel = slugify(data.data_types.find((dataType) => dataType.data_type === 'Series')?.data_values[0]?.name, '') || null; return release; } async function getBlockId(slug, dataSource, entity, parameters) { - const res = await unprint.get(`https://azianistudios.com/tour_api.php/content/page?slug=${slug}&data_source=${JSON.stringify(dataSource)}`, { + const res = await unprint.get(`${parameters.api || 'https://azianistudios.com/tour_api.php'}/content/page?slug=${slug}&data_source=${JSON.stringify(dataSource)}`, { + interface: 'request', // necessary for KSM/Cosplayground CF wall, ok for Aziani headers: { Referer: entity.url, 'x-nats-cms-area-id': parameters.areaId, @@ -109,9 +112,10 @@ async function fetchLatest(channel, page = 1, { parameters }) { data_type_search: parameters.seriesId && JSON.stringify({ 7: parameters.seriesId.toString() }), // doesn't seem relevant }).toString(); - const url = `https://azianistudios.com/tour_api.php/content/sets?${query}`; + const url = `${parameters.api || 'https://azianistudios.com/tour_api.php'}/content/sets?${query}`; const res = await unprint.get(url, { + interface: 'request', // necessary for KSM/Cosplayground CF wall, ok for Aziani headers: { Referer: channel.url, 'x-nats-cms-area-id': parameters.areaId, @@ -126,20 +130,21 @@ async function fetchLatest(channel, page = 1, { parameters }) { } async function fetchScene(url, entity, _baseRelease, { parameters }) { - const entryId = new URL(url).pathname.match(/\/video\/(\w+)/)[1]; + const entryId = new URL(url).pathname.match(/\/videos?\/([\w-]+)/)[1]; if (!entryId) { return null; } - const blockId = await getBlockId('/video/:id', entryId, entity, parameters); + const blockId = await getBlockId(`${parameters.videos || '/video'}/${parameters.entryIdSlug ? ':slug' : ':id'}`, entryId, entity, parameters); if (!blockId) { return null; } const query = new URLSearchParams({ - cms_set_ids: entryId, + cms_set_ids: parameters.entryIdSlug ? '' : entryId, + ...parameters.entryIdSlug ? { slug: entryId } : null, cms_area_id: parameters.areaId, cms_block_id: blockId, content: 1, @@ -150,9 +155,10 @@ async function fetchScene(url, entity, _baseRelease, { parameters }) { content_count: 1, }).toString(); - const apiUrl = `https://azianistudios.com/tour_api.php/content/sets?${query}`; + const apiUrl = `${parameters.api || 'https://azianistudios.com/tour_api.php'}/content/sets?${query}`; const res = await unprint.get(apiUrl, { + interface: 'request', // necessary for KSM/Cosplayground CF wall, ok for Aziani headers: { Referer: entity.url, 'x-nats-cms-area-id': parameters.areaId, @@ -174,7 +180,7 @@ function scrapeProfile(data, entity, parameters) { decode(detail.value || detail.content_formatted || detail.content), ])); - profile.url = `${entity.url}/model/${data.cms_data_value_id}`; + profile.url = `${entity.url}/model/${parameters.entryIdSlug ? data.slug : data.cms_data_value_id}`; profile.entryId = data.cms_data_value_id; profile.description = data.description; @@ -182,6 +188,7 @@ function scrapeProfile(data, entity, parameters) { profile.gender = bio.gender?.toLowerCase(); profile.age = bio.age; profile.dateOfBirth = unprint.extractDate(`${bio.born} 0`, 'MMMM Do YYYY', { match: /\w+ \d+\w{2} \d{1,4}/ }); + profile.ethnicity = bio.ethnicity; profile.measurements = bio.measurements; profile.height = feetInchesToCm(bio.height); @@ -190,7 +197,9 @@ function scrapeProfile(data, entity, parameters) { profile.hairColor = bio.hair_color; profile.eyeColor = bio.eye_color; - const avatar = bio.thumbnail?.image[0]; + const avatar = bio.thumbnail?.image?.[0] // aziani + || bio.biopic?.image?.[0] // cosplayground profile + || bio.headshot?.image?.[0]; // cosplayground headshot if (avatar) { profile.avatar = { @@ -210,27 +219,30 @@ async function fetchProfile({ name, url }, { entity, parameters }) { return null; } - const actorId = new URL(url).pathname.match(/model\/(\d+)/)[1]; + const actorId = new URL(url).pathname.match(/models?\/(\d+)/)?.[1]; - if (!actorId) { + if (!actorId && !parameters.entryIdSlug) { return null; } - const blockId = await getBlockId('/model/:id', { models: name, id: actorId }, entity, parameters); + const slug = slugify(name); + const blockId = await getBlockId(`${parameters.modelPath || '/model'}/${parameters.entryIdSlug ? ':slug' : 'id'}`, { models: name, id: actorId, slug }, entity, parameters); if (!blockId) { return null; } const query = new URLSearchParams({ - cms_data_value_ids: actorId, + cms_data_value_ids: actorId || '', + ...parameters.entryIdSlug ? { slug } : null, cms_block_id: blockId, cms_data_type_id: 4, }).toString(); - const apiUrl = `https://azianistudios.com/tour_api.php/content/data-values?${query}`; + const apiUrl = `${parameters.api || 'https://azianistudios.com/tour_api.php'}/content/data-values?${query}`; const res = await unprint.get(apiUrl, { + interface: 'request', // necessary for KSM/Cosplayground CF wall, ok for Aziani headers: { Referer: entity.url, 'x-nats-cms-area-id': entity.parameters.areaId, diff --git a/src/scrapers/releases.js b/src/scrapers/releases.js index 24b0e55f..fd3908bf 100644 --- a/src/scrapers/releases.js +++ b/src/scrapers/releases.js @@ -102,6 +102,7 @@ module.exports = { acam, analvids: pornbox, pornbox, + cosplayground: aziani, kellymadison, kink, '8kmembers': kellymadison,