Showing all unique descriptions on profile with network logo. Fixed Fame Digital scraper.
|
@ -243,10 +243,33 @@
|
||||||
@click="expanded = true"
|
@click="expanded = true"
|
||||||
><Icon icon="arrow-down3" /></span>
|
><Icon icon="arrow-down3" /></span>
|
||||||
|
|
||||||
|
<div class="descriptions-container">
|
||||||
|
<div
|
||||||
|
v-if="actor.descriptions && actor.descriptions.length > 0"
|
||||||
|
class="descriptions"
|
||||||
|
>
|
||||||
<p
|
<p
|
||||||
v-if="actor.description"
|
v-for="description in actor.descriptions"
|
||||||
|
:key="`description-${description.network.id}`"
|
||||||
class="description"
|
class="description"
|
||||||
>{{ actor.description }}</p>
|
>
|
||||||
|
{{ description.text }}
|
||||||
|
<router-link :to="{ name: 'network', params: { networkSlug: description.network.slug } }">
|
||||||
|
<img
|
||||||
|
v-if="description.site"
|
||||||
|
:src="`/img/logos/${description.network.slug}/${description.site.slug}.png`"
|
||||||
|
class="description-logo"
|
||||||
|
>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
:src="`/img/logos/${description.network.slug}/network.png`"
|
||||||
|
class="description-logo"
|
||||||
|
>
|
||||||
|
</router-link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Social
|
<Social
|
||||||
v-if="actor.social && actor.social.length > 0"
|
v-if="actor.social && actor.social.length > 0"
|
||||||
|
@ -540,17 +563,28 @@ export default {
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.descriptions-container {
|
||||||
max-width: 30rem;
|
max-width: 30rem;
|
||||||
max-height: 12rem;
|
max-height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 2rem 0 0;
|
overflow: hidden;
|
||||||
line-height: 1.5;
|
|
||||||
text-overflow: ellipsis;
|
&::after {
|
||||||
font-size: .9rem;
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5rem;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(transparent, 25%, var(--profile) 75%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.descriptions {
|
||||||
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
@ -559,6 +593,22 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 2rem 0 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-logo {
|
||||||
|
display: block;
|
||||||
|
width: 15rem;
|
||||||
|
max-height: 2rem;
|
||||||
|
margin: .5rem 0 1.5rem 0;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: 0 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.actor-content {
|
.actor-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
|
@ -49,6 +49,7 @@ async function mounted() {
|
||||||
'creampie',
|
'creampie',
|
||||||
],
|
],
|
||||||
oral: [
|
oral: [
|
||||||
|
'blowjob',
|
||||||
'deepthroat',
|
'deepthroat',
|
||||||
'facefucking',
|
'facefucking',
|
||||||
'double-blowjob',
|
'double-blowjob',
|
||||||
|
@ -87,6 +88,14 @@ async function mounted() {
|
||||||
'nurse',
|
'nurse',
|
||||||
'maid',
|
'maid',
|
||||||
],
|
],
|
||||||
|
fetish: [
|
||||||
|
'bdsm',
|
||||||
|
'femdom',
|
||||||
|
],
|
||||||
|
toys: [
|
||||||
|
'double-dildo',
|
||||||
|
'double-dildo-blowjob',
|
||||||
|
],
|
||||||
misc: [
|
misc: [
|
||||||
'gaping',
|
'gaping',
|
||||||
'oil',
|
'oil',
|
||||||
|
|
|
@ -78,6 +78,17 @@ function initActorActions(store, _router) {
|
||||||
}
|
}
|
||||||
profiles: actorsProfiles {
|
profiles: actorsProfiles {
|
||||||
description
|
description
|
||||||
|
descriptionHash
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
}
|
||||||
|
site {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
}
|
||||||
avatar: avatarMedia {
|
avatar: avatarMedia {
|
||||||
id
|
id
|
||||||
path
|
path
|
||||||
|
|
|
@ -34,7 +34,19 @@ function curateActor(actor, release, curateActorRelease) {
|
||||||
.map(profile => profile.avatar)
|
.map(profile => profile.avatar)
|
||||||
.filter(avatar => avatar && (!curatedActor.avatar || avatar.hash !== curatedActor.avatar.hash));
|
.filter(avatar => avatar && (!curatedActor.avatar || avatar.hash !== curatedActor.avatar.hash));
|
||||||
|
|
||||||
|
const descriptions = actor.profiles.reduce((acc, profile) => ({
|
||||||
|
...acc,
|
||||||
|
...(profile.description && {
|
||||||
|
[profile.descriptionHash]: {
|
||||||
|
text: profile.description,
|
||||||
|
network: profile.network,
|
||||||
|
site: profile.site,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}), {});
|
||||||
|
|
||||||
curatedActor.photos = Object.values(photos.reduce((acc, photo) => ({ ...acc, [photo.hash]: photo }), {}));
|
curatedActor.photos = Object.values(photos.reduce((acc, photo) => ({ ...acc, [photo.hash]: photo }), {}));
|
||||||
|
curatedActor.descriptions = Object.values(descriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (release && release.date && curatedActor.birthdate) {
|
if (release && release.date && curatedActor.birthdate) {
|
||||||
|
|
|
@ -349,12 +349,13 @@ exports.up = knex => Promise.resolve()
|
||||||
.defaultTo(1);
|
.defaultTo(1);
|
||||||
|
|
||||||
table.string('real_name');
|
table.string('real_name');
|
||||||
|
table.string('gender', 18);
|
||||||
|
|
||||||
table.date('date_of_birth');
|
table.date('date_of_birth');
|
||||||
table.date('date_of_death');
|
table.date('date_of_death');
|
||||||
|
|
||||||
table.string('gender', 18);
|
|
||||||
table.text('description');
|
table.text('description');
|
||||||
|
table.string('description_hash');
|
||||||
|
|
||||||
table.string('birth_city');
|
table.string('birth_city');
|
||||||
table.string('birth_state');
|
table.string('birth_state');
|
||||||
|
|
|
@ -3401,6 +3401,11 @@
|
||||||
"domelementtype": "1"
|
"domelementtype": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dompurify": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-qVoGPjIW9IqxRij7klDQQ2j6nSe4UNWANBhZNLnsS7ScTtLb+3YdxkRY8brNTpkUiTtcXsCJO+jS0UCDfenLuA=="
|
||||||
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
"config": "^3.2.5",
|
"config": "^3.2.5",
|
||||||
"csv-stringify": "^5.3.6",
|
"csv-stringify": "^5.3.6",
|
||||||
"dayjs": "^1.8.21",
|
"dayjs": "^1.8.21",
|
||||||
|
"dompurify": "^2.0.11",
|
||||||
"ejs": "^3.0.1",
|
"ejs": "^3.0.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-promise-router": "^3.0.3",
|
"express-promise-router": "^3.0.3",
|
||||||
|
|
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 689 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 932 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 416 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 868 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 37 KiB |
|
@ -22,6 +22,7 @@ const tagPosters = [
|
||||||
['deepthroat', 0, 'Chanel Grey in "Deepthroating Is Fun" for Throated'],
|
['deepthroat', 0, 'Chanel Grey in "Deepthroating Is Fun" for Throated'],
|
||||||
['double-anal', 7, 'Adriana Chechik in "DP Masters 6" for Jules Jordan'],
|
['double-anal', 7, 'Adriana Chechik in "DP Masters 6" for Jules Jordan'],
|
||||||
['double-blowjob', 1, 'Veronica Rodriguez and Penny Pax in "Fucking Older Guys 5" for Penthouse'],
|
['double-blowjob', 1, 'Veronica Rodriguez and Penny Pax in "Fucking Older Guys 5" for Penthouse'],
|
||||||
|
['double-dildo', 0, 'Kali Roses in "Double Dildo Party" for KaliRoses.com'],
|
||||||
['double-dildo-blowjob', 0, 'Adriana Chechik and Vicki Chase in "Anal Savages 1" for Jules Jordan'],
|
['double-dildo-blowjob', 0, 'Adriana Chechik and Vicki Chase in "Anal Savages 1" for Jules Jordan'],
|
||||||
['double-penetration', 2, 'Megan Rain in "DP Masters 4" for Jules Jordan'],
|
['double-penetration', 2, 'Megan Rain in "DP Masters 4" for Jules Jordan'],
|
||||||
['double-vaginal', 'poster', 'Riley Reid in "Pizza That Ass" for Reid My Lips'],
|
['double-vaginal', 'poster', 'Riley Reid in "Pizza That Ass" for Reid My Lips'],
|
||||||
|
@ -31,6 +32,7 @@ const tagPosters = [
|
||||||
['facial', 0, 'Brooklyn Gray in "All About Ass 4" for Evil Angel'],
|
['facial', 0, 'Brooklyn Gray in "All About Ass 4" for Evil Angel'],
|
||||||
['fake-boobs', 1, 'Lela Star in "Thick" for Jules Jordan'],
|
['fake-boobs', 1, 'Lela Star in "Thick" for Jules Jordan'],
|
||||||
['family', 0, 'Teanna Trump in "A Family Appear: Part One" for Brazzers'],
|
['family', 0, 'Teanna Trump in "A Family Appear: Part One" for Brazzers'],
|
||||||
|
['femdom', 0, 'Alina Li in "Asian Domination… She Holds Jules Jordan\'s Cock Hostage!" for Jules Jordan'],
|
||||||
['gangbang', 5, 'Carter Cruise\'s first gangbang in "Slut Puppies 9" for Jules Jordan'],
|
['gangbang', 5, 'Carter Cruise\'s first gangbang in "Slut Puppies 9" for Jules Jordan'],
|
||||||
['gaping', 1, 'Vina Sky in "Vina Sky Does Anal" for HardX'],
|
['gaping', 1, 'Vina Sky in "Vina Sky Does Anal" for HardX'],
|
||||||
['interracial', 0, 'Jaye Summers and Prince Yahshua in "Platinum Pussy 3" for Jules Jordan'],
|
['interracial', 0, 'Jaye Summers and Prince Yahshua in "Platinum Pussy 3" for Jules Jordan'],
|
||||||
|
@ -100,6 +102,7 @@ const tagPhotos = [
|
||||||
['dv-tp', 0, 'Luna Rival in LegalPorno SZ1490'],
|
['dv-tp', 0, 'Luna Rival in LegalPorno SZ1490'],
|
||||||
['facial', 1, 'Ella Knox in "Mr Saltys Adult Emporium Adventure 2" for Aziani'],
|
['facial', 1, 'Ella Knox in "Mr Saltys Adult Emporium Adventure 2" for Aziani'],
|
||||||
['facial', 'poster', 'Jynx Maze'],
|
['facial', 'poster', 'Jynx Maze'],
|
||||||
|
['facefucking', 3, 'Adriana Chechik in "Performing Magic Butt Tricks With Jules Jordan. What Will Disappear In Her Ass?" for Jules Jordan'],
|
||||||
['facefucking', 1, 'Carrie for Young Throats'],
|
['facefucking', 1, 'Carrie for Young Throats'],
|
||||||
// ['fake-boobs', 0, 'Marsha May in "Once You Go Black 7" for Jules Jordan'],
|
// ['fake-boobs', 0, 'Marsha May in "Once You Go Black 7" for Jules Jordan'],
|
||||||
['gangbang', 'poster', 'Kristen Scott in "Interracial Gangbang!" for Jules Jordan'],
|
['gangbang', 'poster', 'Kristen Scott in "Interracial Gangbang!" for Jules Jordan'],
|
||||||
|
|
|
@ -3,6 +3,12 @@
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const blake2 = require('blake2');
|
||||||
|
const DOMPurify = require('dompurify');
|
||||||
|
const { JSDOM } = require('jsdom');
|
||||||
|
|
||||||
|
const { window } = new JSDOM('');
|
||||||
|
const domPurify = DOMPurify(window);
|
||||||
|
|
||||||
// const logger = require('./logger')(__filename);
|
// const logger = require('./logger')(__filename);
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
|
@ -148,6 +154,7 @@ function curateProfileEntry(profile) {
|
||||||
gender: profile.gender,
|
gender: profile.gender,
|
||||||
ethnicity: profile.ethnicity,
|
ethnicity: profile.ethnicity,
|
||||||
description: profile.description,
|
description: profile.description,
|
||||||
|
description_hash: profile.descriptionHash,
|
||||||
birth_city: profile.placeOfBirth?.city || null,
|
birth_city: profile.placeOfBirth?.city || null,
|
||||||
birth_state: profile.placeOfBirth?.state || null,
|
birth_state: profile.placeOfBirth?.state || null,
|
||||||
birth_country_alpha2: profile.placeOfBirth?.country || null,
|
birth_country_alpha2: profile.placeOfBirth?.country || null,
|
||||||
|
@ -189,7 +196,14 @@ async function curateProfile(profile) {
|
||||||
update: profile.update,
|
update: profile.update,
|
||||||
};
|
};
|
||||||
|
|
||||||
curatedProfile.description = profile.description?.trim() || null;
|
curatedProfile.description = domPurify.sanitize(profile.description, { ALLOWED_TAGS: [] }).trim() || null;
|
||||||
|
|
||||||
|
const hasher = curatedProfile.description && blake2
|
||||||
|
.createHash('blake2b')
|
||||||
|
.update(Buffer.from(slugify(curatedProfile.description)));
|
||||||
|
|
||||||
|
curatedProfile.descriptionHash = curatedProfile.description && hasher.digest('hex');
|
||||||
|
|
||||||
curatedProfile.nationality = profile.nationality?.trim() || null; // used to derive country when country not available
|
curatedProfile.nationality = profile.nationality?.trim() || null; // used to derive country when country not available
|
||||||
|
|
||||||
curatedProfile.ethnicity = ethnicities[profile.ethnicity?.trim().toLowerCase()] || null;
|
curatedProfile.ethnicity = ethnicities[profile.ethnicity?.trim().toLowerCase()] || null;
|
||||||
|
|
|
@ -39,9 +39,8 @@ function decodeId(id) {
|
||||||
.toString('hex');
|
.toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrapeScene(scene, site) {
|
function scrapeScene(scene) {
|
||||||
const release = {
|
const release = {
|
||||||
site,
|
|
||||||
entryId: scene.id,
|
entryId: scene.id,
|
||||||
title: scene.name,
|
title: scene.name,
|
||||||
description: scene.description,
|
description: scene.description,
|
||||||
|
@ -80,11 +79,11 @@ function scrapeScene(scene, site) {
|
||||||
return release;
|
return release;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrapeAll(scenes, site) {
|
function scrapeAll(scenes) {
|
||||||
return scenes.map(({ _source: scene }) => scrapeScene(scene, site));
|
return scenes.map(({ _source: scene }) => scrapeScene(scene));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchActorReleases(actor, site) {
|
async function fetchActorReleases(actor) {
|
||||||
const res = await bhttp.post(`https://${clusterId}.us-east-1.aws.found.io/videos/video/_search`, {
|
const res = await bhttp.post(`https://${clusterId}.us-east-1.aws.found.io/videos/video/_search`, {
|
||||||
size: 50,
|
size: 50,
|
||||||
query: {
|
query: {
|
||||||
|
@ -138,11 +137,10 @@ async function fetchActorReleases(actor, site) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return scrapeAll(res.body.hits.hits, site);
|
return scrapeAll(res.body.hits.hits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function scrapeProfile(actor, include) {
|
||||||
async function scrapeProfile(actor, site, include) {
|
|
||||||
const profile = {};
|
const profile = {};
|
||||||
|
|
||||||
profile.aliases = actor.aliases;
|
profile.aliases = actor.aliases;
|
||||||
|
@ -174,7 +172,7 @@ async function scrapeProfile(actor, site, include) {
|
||||||
if (actor.image) profile.avatar = `https://i.bang.com/pornstars/${actor.identifier}.jpg`;
|
if (actor.image) profile.avatar = `https://i.bang.com/pornstars/${actor.identifier}.jpg`;
|
||||||
|
|
||||||
if (include.releases) {
|
if (include.releases) {
|
||||||
profile.releases = await fetchActorReleases(actor, site);
|
profile.releases = await fetchActorReleases(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
|
@ -267,7 +265,7 @@ async function fetchLatest(site, page = 1) {
|
||||||
return scrapeAll(res.body.hits.hits, site);
|
return scrapeAll(res.body.hits.hits, site);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchScene(url, site) {
|
async function fetchScene(url) {
|
||||||
const encodedId = new URL(url).pathname.split('/')[2];
|
const encodedId = new URL(url).pathname.split('/')[2];
|
||||||
const entryId = decodeId(encodedId);
|
const entryId = decodeId(encodedId);
|
||||||
|
|
||||||
|
@ -277,10 +275,10 @@ async function fetchScene(url, site) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return scrapeScene(res.body._source, site); // eslint-disable-line no-underscore-dangle
|
return scrapeScene(res.body._source); // eslint-disable-line no-underscore-dangle
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchProfile(actorName, actorSlug, site, include) {
|
async function fetchProfile(actorName, context, include) {
|
||||||
const res = await post(`https://${clusterId}.us-east-1.aws.found.io/actors/actor/_search`, {
|
const res = await post(`https://${clusterId}.us-east-1.aws.found.io/actors/actor/_search`, {
|
||||||
size: 5,
|
size: 5,
|
||||||
sort: [{
|
sort: [{
|
||||||
|
@ -315,7 +313,7 @@ async function fetchProfile(actorName, actorSlug, site, include) {
|
||||||
const actor = res.body.hits.hits.find(hit => hit._source.name.toLowerCase() === actorName.toLowerCase());
|
const actor = res.body.hits.hits.find(hit => hit._source.name.toLowerCase() === actorName.toLowerCase());
|
||||||
|
|
||||||
if (actor) {
|
if (actor) {
|
||||||
return scrapeProfile(actor._source, site, include);
|
return scrapeProfile(actor._source, include);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -91,8 +91,8 @@ async function fetchClassicProfile(actorName, { site }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function networkFetchProfile(actorName, context, include) {
|
async function networkFetchProfile(actorName, context, include) {
|
||||||
const profile = await ((context.site.parameters.api && fetchApiProfile(actorName, context, include))
|
const profile = await ((context.site.parameters?.api && fetchApiProfile(actorName, context, include))
|
||||||
|| (context.site.parameters.classic && include.scenes && fetchClassicProfile(actorName, context, include)) // classic profiles only have scenes, no bio
|
|| (context.site.parameters?.classic && include.scenes && fetchClassicProfile(actorName, context, include)) // classic profiles only have scenes, no bio
|
||||||
|| fetchProfile(actorName, context, true, getActorReleasesUrl, include));
|
|| fetchProfile(actorName, context, true, getActorReleasesUrl, include));
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
|
|