diff --git a/assets/components/tags/tags.vue b/assets/components/tags/tags.vue
index d9ff63b3..0f4f1b02 100644
--- a/assets/components/tags/tags.vue
+++ b/assets/components/tags/tags.vue
@@ -52,7 +52,10 @@
+
Promise.resolve()
.then(async () => upsert('tags_groups', groups, 'slug', knex))
@@ -1637,15 +1489,25 @@ exports.seed = knex => Promise.resolve()
const groupEntries = await knex('tags_groups').select('*');
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const tags = getTags(groupsMap);
+ const tagsWithGroups = tags.map(tag => ({
+ name: tag.name,
+ slug: tag.slug || slugify(tag.name),
+ description: tag.description,
+ priority: tag.priority || 0,
+ group_id: tag.group ? groupsMap[tag.group] : null,
+ alias_for: null,
+ }));
- return upsert('tags', tags, 'slug');
+ return upsert('tags', tagsWithGroups, 'slug');
})
.then(async () => {
- const tags = await knex('tags').select('*').where({ alias_for: null });
- const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
+ const tagEntries = await knex('tags').select('*').where({ alias_for: null });
+ const tagsMap = tagEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const tagAliases = getTagAliases(tagsMap);
+ const tagAliases = aliases.map(alias => ({
+ name: alias.name,
+ alias_for: tagsMap[alias.for],
+ }));
return upsert('tags', tagAliases, 'name');
});
diff --git a/seeds/01_networks.js b/seeds/01_networks.js
index 005bddaf..aabcfae6 100644
--- a/seeds/01_networks.js
+++ b/seeds/01_networks.js
@@ -60,6 +60,12 @@ const networks = [
url: 'https://www.brazzers.com',
description: 'Brazzers homepage is updated daily with official HD porn scenes. Our hottest videos and sex series are filled with big tits, sexy milf, top pornstars and special events.',
},
+ {
+ slug: 'cherrypimps',
+ name: 'Cherry Pimps',
+ url: 'https://www.cherrypimps.com',
+ description: 'CherryPimps your premium porn site to Download and Stream the hottest and most exclusive 4K HD videos and pictures on your phone, tablet, TV or console.',
+ },
{
slug: 'ddfnetwork',
name: 'DDF Network',
@@ -202,6 +208,12 @@ const networks = [
url: 'https://www.pervcity.com',
description: '',
},
+ {
+ slug: 'pimpxxx',
+ name: 'Pimp.XXX',
+ url: 'https://www.pimp.xxx',
+ description: 'PIMP.XXX is the NEWEST and HOTTEST site featuring Exclusive Ultra High Definition 4k videos updated daily! Featuring the newest and the biggest pornstars with Big Tits, Tight Asses, and more!',
+ },
{
slug: 'pornpros',
name: 'Porn Pros',
diff --git a/seeds/02_sites.js b/seeds/02_sites.js
index de31de8b..b9b18c75 100644
--- a/seeds/02_sites.js
+++ b/seeds/02_sites.js
@@ -1171,6 +1171,33 @@ const sites = [
description: "Giant black dicks paired with round asses and garnished with the tightest pussies of all colors. Butts and Blacks delivers on its name sake, only the biggest dicks rocking the thickest chicks. These round honeys can take it all in and bounce around like it's a pogo stick. Come check out these soft round asses getting the attention they deserve.",
network: 'brazzers',
},
+ // CHERRY PIMPS
+ {
+ slug: 'cherrypimps',
+ name: 'Cherry Pimps',
+ url: 'https://cherrypimps.com',
+ description: 'CherryPimps your premium porn site to Download and Stream the hottest and most exclusive 4K HD videos and pictures on your phone, tablet, TV or console.',
+ network: 'cherrypimps',
+ parameters: {
+ extract: true,
+ },
+ },
+ {
+ slug: 'wildoncam',
+ name: 'Wild On Cam',
+ url: 'https://wildoncam.com',
+ tags: ['live'],
+ network: 'cherrypimps',
+ },
+ {
+ slug: 'britneyamber',
+ name: 'Britney Amber',
+ url: 'https://www.britneyamber.com',
+ network: 'cherrypimps',
+ parameters: {
+ extract: true,
+ },
+ },
// DDF NETWORK
{
slug: 'ddfbusty',
@@ -3081,6 +3108,46 @@ const sites = [
network: 'pervcity',
parameters: { tourId: 9 },
},
+ // PIMP XXX
+ {
+ slug: 'drilledxxx',
+ name: 'Drilled.XXX',
+ url: 'https://drilled.xxx',
+ tags: ['anal'],
+ network: 'pimpxxx',
+ },
+ {
+ slug: 'cuckedxxx',
+ name: 'Cucked.XXX',
+ url: 'https://cucked.xxx',
+ tags: ['cuckold'],
+ network: 'pimpxxx',
+ },
+ {
+ slug: 'familyxxx',
+ name: 'Family.XXX',
+ url: 'https://family.xxx',
+ tags: ['family'],
+ network: 'pimpxxx',
+ },
+ {
+ slug: 'petitexxx',
+ name: 'Petite.XXX',
+ url: 'https://petite.xxx',
+ network: 'pimpxxx',
+ },
+ {
+ slug: 'confessionsxxx',
+ name: 'Confessions.XXX',
+ url: 'https://confessions.xxx',
+ network: 'pimpxxx',
+ },
+ {
+ slug: 'bcmxxx',
+ name: 'BCM.XXX',
+ url: 'https://bcm.xxx',
+ network: 'pimpxxx',
+ },
// PORN PROS
{
name: 'Real Ex Girlfriends',
diff --git a/src/scrapers/cherrypimps.js b/src/scrapers/cherrypimps.js
new file mode 100644
index 00000000..ac731bb0
--- /dev/null
+++ b/src/scrapers/cherrypimps.js
@@ -0,0 +1,140 @@
+'use strict';
+
+const { get, geta, ctxa, fd } = require('../utils/q');
+const slugify = require('../utils/slugify');
+
+function scrapeAll(scenes, site) {
+ return scenes.map(({ q, qa, qu, qd, ql, qi, qt }) => {
+ const url = qu('.text-thumb a');
+ const { pathname } = new URL(url);
+ const channelUrl = qu('.badge');
+
+ if (site?.parameters?.extract && q('.badge', true) !== site.name) {
+ return null;
+ }
+
+ const release = {};
+
+ release.url = channelUrl ? `${channelUrl}${pathname}` : url;
+ release.entryId = pathname.match(/\/\d+/)[0].slice(1);
+ release.title = q('.text-thumb a', true);
+
+ release.date = qd('.date', 'YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/);
+ release.duration = ql('.date', /(\d{2}:)?\d{2}:\d{2}/);
+
+ release.actors = qa('.category a', true);
+
+ release.poster = qi('img.video_placeholder, .video-images img');
+ release.teaser = { src: qt() };
+
+ return release;
+ }).filter(Boolean);
+}
+
+function scrapeScene({ q, qd, qa }, url, _site, baseRelease) {
+ const release = { url };
+
+ const { pathname } = new URL(url);
+ release.entryId = pathname.match(/\/\d+/)[0].slice(1);
+
+ release.title = q('.trailer-block_title', true);
+ release.description = q('.info-block:nth-child(3) .text', true);
+ release.date = qd('.info-block_data .text', 'MMMM D, YYYY', /\w+ \d{1,2}, \d{4}/);
+
+ const duration = baseRelease?.duration || Number(q('.info-block_data .text', true).match(/(\d+)\s+min/)?.[1]) * 60;
+ if (duration) release.duration = duration;
+
+ release.actors = qa('.info-block_data a[href*="/models"]', true);
+ release.tags = qa('.info-block a[href*="/categories"]', true);
+
+ const posterEl = q('.update_thumb');
+ const poster = posterEl.getAttribute('src0_3x') || posterEl.getAttribute('src0_2x') || posterEl.dataset.src;
+
+ if (poster && baseRelease?.poster) release.photos = [poster];
+ else if (poster) release.poster = poster;
+
+ return release;
+}
+
+function scrapeProfile({ q, qa, qtx }) {
+ const profile = {};
+
+ const keys = qa('.model-descr_line:not(.model-descr_rait) p.text span', true);
+ const values = qa('.model-descr_line:not(.model-descr_rait) p.text').map(el => qtx(el));
+ const bio = keys.reduce((acc, key, index) => ({ ...acc, [slugify(key, { delimiter: '_' })]: values[index] }), {});
+
+ if (bio.height) profile.height = Number(bio.height.match(/\((\d+)cm\)/)[1]);
+ if (bio.weight) profile.weight = Number(bio.weight.match(/\((\d+)kg\)/)[1]);
+ if (bio.race) profile.ethnicity = bio.race;
+
+ if (bio.date_of_birth) profile.birthdate = fd(bio.date_of_birth, 'MMMM D, YYYY');
+ if (bio.birthplace) profile.birthPlace = bio.birthplace;
+
+ if (bio.measurements) {
+ const [bust, waist, hip] = bio.measurements.split('-');
+ if (!/\?/.test(bust)) profile.bust = bust;
+ if (!/\?/.test(waist)) profile.waist = waist;
+ if (!/\?/.test(hip)) profile.hip = hip;
+ }
+
+ if (bio.hair) profile.hair = bio.hair;
+ if (bio.eyes) profile.eyes = bio.eyes;
+
+ if (/various/i.test(bio.tattoos)) profile.hasTattoos = true;
+ else if (/none/i.test(bio.tattoos)) profile.hasTattoos = false;
+ else if (bio.tattoos) {
+ profile.hasTattoos = true;
+ profile.tattoos = bio.tattoos;
+ }
+
+ if (/various/i.test(bio.piercings)) profile.hasPiercings = true;
+ else if (/none/i.test(bio.piercings)) profile.hasPiercings = false;
+ else if (bio.piercings) {
+ profile.hasPiercings = true;
+ profile.piercings = bio.piercings;
+ }
+
+ if (bio.aliases) profile.aliases = bio.aliases.split(',').map(alias => alias.trim());
+
+ const avatar = q('.model-img img');
+ profile.avatar = avatar.getAttribute('src0_3x') || avatar.getAttribute('src0_2x') || avatar.dataset.src;
+
+ const releases = qa('.video-thumb');
+ profile.releases = scrapeAll(ctxa(releases));
+
+ return profile;
+}
+
+async function fetchLatest(site, page = 1) {
+ const url = site.parameters?.extract
+ ? `https://cherrypimps.com/categories/movies_${page}.html`
+ : `${site.url}/categories/movies_${page}.html`;
+ const qLatest = await geta(url, 'div.video-thumb');
+
+ return qLatest && scrapeAll(qLatest, site);
+}
+
+async function fetchScene(url, site, release) {
+ const qScene = await get(url);
+
+ return qScene && scrapeScene(qScene, url, site, release);
+}
+
+async function fetchProfile(actorName, scraperSlug) {
+ const actorSlug = slugify(actorName);
+ const actorSlug2 = slugify(actorName, { delimiter: '' });
+
+ const [url, url2] = ['cherrypimps', 'wildoncam'].includes(scraperSlug)
+ ? [`https://${scraperSlug}.com/models/${actorSlug}.html`, `https://${scraperSlug}.com/models/${actorSlug2}.html`]
+ : [`https://${scraperSlug.replace('xxx', '')}.xxx/models/${actorSlug}.html`, `https://${scraperSlug.replace('xxx', '')}.xxx/models/${actorSlug2}.html`];
+
+ const qActor = await get(url) || await get(url2);
+
+ return qActor && scrapeProfile(qActor);
+}
+
+module.exports = {
+ fetchLatest,
+ fetchScene,
+ fetchProfile,
+};
diff --git a/src/scrapers/scrapers.js b/src/scrapers/scrapers.js
index ab8260a5..882503cc 100644
--- a/src/scrapers/scrapers.js
+++ b/src/scrapers/scrapers.js
@@ -8,6 +8,7 @@ const bangbros = require('./bangbros');
const blowpass = require('./blowpass');
const boobpedia = require('./boobpedia');
const brazzers = require('./brazzers');
+const cherrypimps = require('./cherrypimps');
const ddfnetwork = require('./ddfnetwork');
const digitalplayground = require('./digitalplayground');
const dogfart = require('./dogfart');
@@ -65,6 +66,7 @@ module.exports = {
bangbros,
blowpass,
brazzers,
+ cherrypimps,
ddfnetwork,
digitalplayground,
dogfart,
@@ -90,6 +92,7 @@ module.exports = {
nubiles,
perfectgonzo,
pervcity,
+ pimpxxx: cherrypimps,
pornpros,
private: privateNetwork,
puretaboo,
@@ -112,6 +115,7 @@ module.exports = {
boobpedia,
brattysis: nubiles,
brazzers,
+ cherrypimps,
ddfnetwork,
deeplush: nubiles,
digitalplayground,
@@ -134,6 +138,7 @@ module.exports = {
nubilefilms: nubiles,
nubiles,
nubilesporn: nubiles,
+ pimpxxx: cherrypimps,
pornhub,
realitykings,
score,
diff --git a/src/scrapers/template.js b/src/scrapers/template.js
index b92c0fad..73531665 100644
--- a/src/scrapers/template.js
+++ b/src/scrapers/template.js
@@ -25,7 +25,7 @@ function scrapeLatest(scenes, site) {
});
}
-function scrapeScene(({ q }), _site) {
+function scrapeScene({ q }, _site) {
const release = {};
console.log(release);
@@ -47,4 +47,5 @@ async function fetchScene(url, site) {
module.exports = {
fetchLatest,
+ fetchScene,
};