Added Hitzefrei. Fixed date averaging.
|
@ -22,13 +22,13 @@
|
||||||
|
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<ChannelFilter
|
<ChannelFilter
|
||||||
class="filter"
|
class="filters-filter"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
:available-channels="availableChannels"
|
:available-channels="availableChannels"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TagFilter
|
<TagFilter
|
||||||
class="filter"
|
class="filters-filter"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
:available-tags="availableTags"
|
:available-tags="availableTags"
|
||||||
/>
|
/>
|
||||||
|
@ -251,6 +251,14 @@ export default {
|
||||||
.filter-applied {
|
.filter-applied {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters-filter:not(:last-child) .filter {
|
||||||
|
padding: 1rem .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-filter:last-child .filter {
|
||||||
|
padding: 1rem 0 1rem .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -312,7 +320,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filters-filter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
|
|
@ -161,14 +161,15 @@ module.exports = {
|
||||||
'pervertgallery',
|
'pervertgallery',
|
||||||
'povperverts',
|
'povperverts',
|
||||||
],
|
],
|
||||||
|
'kellymadison',
|
||||||
'private',
|
'private',
|
||||||
'ddfnetwork',
|
'ddfnetwork',
|
||||||
'bangbros',
|
'bangbros',
|
||||||
|
'hitzefrei',
|
||||||
[
|
[
|
||||||
'silverstonedvd',
|
'silverstonedvd',
|
||||||
'silviasaint',
|
'silviasaint',
|
||||||
],
|
],
|
||||||
'kellymadison',
|
|
||||||
'gangbangcreampie',
|
'gangbangcreampie',
|
||||||
'gloryholesecrets',
|
'gloryholesecrets',
|
||||||
'aziani',
|
'aziani',
|
||||||
|
|
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 756 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 8.6 KiB |
|
@ -218,6 +218,11 @@ const networks = [
|
||||||
description: 'Girlsway.com has the best lesbian porn videos online! The hottest pornstars & first time lesbians in real girl on girl sex, tribbing, squirting & pussy licking action right HERE!',
|
description: 'Girlsway.com has the best lesbian porn videos online! The hottest pornstars & first time lesbians in real girl on girl sex, tribbing, squirting & pussy licking action right HERE!',
|
||||||
parent: 'gamma',
|
parent: 'gamma',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'hitzefrei',
|
||||||
|
name: 'Hitzefrei',
|
||||||
|
url: 'http://www.hitzefrei.com',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: 'hussiepass',
|
slug: 'hussiepass',
|
||||||
name: 'Hussie Pass',
|
name: 'Hussie Pass',
|
||||||
|
|
|
@ -2354,6 +2354,83 @@ const sites = [
|
||||||
referer: 'https://www.girlsway.com',
|
referer: 'https://www.girlsway.com',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// HITZEFREI
|
||||||
|
{
|
||||||
|
slug: 'unleashed',
|
||||||
|
name: 'Unleashed',
|
||||||
|
url: 'https://unleashed.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
parameters: {
|
||||||
|
siteId: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'citycheck',
|
||||||
|
name: 'City Check',
|
||||||
|
url: 'https://citycheck.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
parameters: {
|
||||||
|
siteId: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'milfhunters',
|
||||||
|
name: 'MILF Hunters',
|
||||||
|
url: 'https://milfhunters.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
tags: ['milf'],
|
||||||
|
parameters: {
|
||||||
|
siteId: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'cuffemall',
|
||||||
|
name: 'Cuff\'em All',
|
||||||
|
url: 'https://cuffemall.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
parameters: {
|
||||||
|
siteId: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'fanalarm',
|
||||||
|
name: 'fANALarm',
|
||||||
|
url: 'https://fanalarm.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
tags: ['anal'],
|
||||||
|
parameters: {
|
||||||
|
siteId: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'fuckonarrival',
|
||||||
|
name: 'Fuck On Arrival',
|
||||||
|
url: 'https://fuckonarrival.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
parameters: {
|
||||||
|
siteId: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'familyaffairs',
|
||||||
|
name: 'Family Affairs',
|
||||||
|
url: 'https://familyaffairs.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
tags: ['family'],
|
||||||
|
parameters: {
|
||||||
|
siteId: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'pattisanals',
|
||||||
|
name: 'Patti\'s Anals',
|
||||||
|
url: 'https://pattisanals.hitzefrei.com',
|
||||||
|
parent: 'hitzefrei',
|
||||||
|
tags: ['anal'],
|
||||||
|
parameters: {
|
||||||
|
siteId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
// HUSSIE PASS
|
// HUSSIE PASS
|
||||||
{
|
{
|
||||||
slug: 'hussiepass',
|
slug: 'hussiepass',
|
||||||
|
|
|
@ -97,11 +97,11 @@ function getMostFrequentDate(dates) {
|
||||||
const month = getMostFrequent(dates.map(dateX => dateX.getMonth()));
|
const month = getMostFrequent(dates.map(dateX => dateX.getMonth()));
|
||||||
const date = getMostFrequent(dates.map(dateX => dateX.getDate()));
|
const date = getMostFrequent(dates.map(dateX => dateX.getDate()));
|
||||||
|
|
||||||
if (year && month && date) {
|
if (year === null || month === null || date === null) {
|
||||||
return moment({ year, month, date }).toDate();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return moment({ year, month, date }).toDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLongest(items) {
|
function getLongest(items) {
|
||||||
|
@ -216,7 +216,7 @@ function curateActorEntry(baseActor, batchId) {
|
||||||
name: baseActor.name,
|
name: baseActor.name,
|
||||||
slug: baseActor.slug,
|
slug: baseActor.slug,
|
||||||
entity_id: null,
|
entity_id: null,
|
||||||
entry_id: baseActor.entry_id,
|
entry_id: baseActor.entryId,
|
||||||
batch_id: batchId,
|
batch_id: batchId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -615,6 +615,8 @@ async function scrapeActors(actorNames) {
|
||||||
const [batchId] = newBaseActors.length > 0 ? await knex('batches').insert({ comment: null }).returning('id') : [null];
|
const [batchId] = newBaseActors.length > 0 ? await knex('batches').insert({ comment: null }).returning('id') : [null];
|
||||||
const curatedActorEntries = batchId && curateActorEntries(newBaseActors, batchId);
|
const curatedActorEntries = batchId && curateActorEntries(newBaseActors, batchId);
|
||||||
|
|
||||||
|
// TODO: associate entity when entry ID is provided
|
||||||
|
|
||||||
const newActorEntries = batchId && await knex('actors')
|
const newActorEntries = batchId && await knex('actors')
|
||||||
.insert(curatedActorEntries)
|
.insert(curatedActorEntries)
|
||||||
.returning(['id', 'name', 'slug', 'entry_id']);
|
.returning(['id', 'name', 'slug', 'entry_id']);
|
||||||
|
|
|
@ -57,8 +57,8 @@ async function scrapeScene({ query, html }, url, _site) {
|
||||||
release.description = query.text('#scene-description p[itemprop="description"]');
|
release.description = query.text('#scene-description p[itemprop="description"]');
|
||||||
|
|
||||||
release.date = query.date('.more-scene-info .scene-date', 'MMMM DD, YYYY');
|
release.date = query.date('.more-scene-info .scene-date', 'MMMM DD, YYYY');
|
||||||
release.duration = Number(query.q('#trailer-player-container', 'data-duration')) // more accurate
|
release.duration = query.number('#trailer-player-container', 'data-duration') // more accurate
|
||||||
|| Number(query.q('.scene-length[itemprop="duration"]', 'content').slice(1, -1) * 60);
|
|| query.number('.scene-length[itemprop="duration"]', 'content') * 60; // fallback
|
||||||
|
|
||||||
// actor cards have avatar, but truncated name
|
// actor cards have avatar, but truncated name
|
||||||
const actorImagesByActorId = query.imgs('.featured-model .card-image img').reduce((acc, img) => ({
|
const actorImagesByActorId = query.imgs('.featured-model .card-image img').reduce((acc, img) => ({
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const qu = require('../utils/qu');
|
||||||
|
const http = require('../utils/http');
|
||||||
|
const { lbsToKg, feetInchesToCm } = require('../utils/convert');
|
||||||
|
const slugify = require('../utils/slugify');
|
||||||
|
|
||||||
|
function scrapeAll(scenes) {
|
||||||
|
return scenes.map(({ query }) => {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.url = query.url('.content-title a');
|
||||||
|
release.entryId = new URL(release.url).pathname.match(/\/view\/(\d+)/)[1];
|
||||||
|
|
||||||
|
release.title = query.cnt('.content-title a');
|
||||||
|
release.date = query.date('.content-date strong', 'DD/MM/YYYY');
|
||||||
|
release.duration = query.dur('.content-date');
|
||||||
|
|
||||||
|
release.actors = query.cnts('.content-models a');
|
||||||
|
|
||||||
|
release.poster = query.img('.content-thumbnail img, .large-thumbnail img') || query.poster('.content-thumbnail video, .large-thumbnail video');
|
||||||
|
|
||||||
|
const teaser = query.video('.vid-hover source');
|
||||||
|
release.teaser = { src: teaser };
|
||||||
|
|
||||||
|
release.channel = slugify(query.cnt('.content-site a'), '');
|
||||||
|
|
||||||
|
return release;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeScene({ query }, url) {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.entryId = new URL(url).pathname.match(/\/view\/(\d+)/)[1];
|
||||||
|
|
||||||
|
release.title = query.cnt('.content-title');
|
||||||
|
release.description = query.cnt('.content-description p');
|
||||||
|
|
||||||
|
release.date = query.date('.content-metas span:nth-child(4)', 'DD/MM/YYYY');
|
||||||
|
release.duration = query.dur('.content-metas span:nth-child(2)');
|
||||||
|
release.likes = query.number('.content-metas span:nth-child(6)');
|
||||||
|
|
||||||
|
release.actors = query.all('.model-thumb img').map(el => ({
|
||||||
|
name: query.q(el, null, 'alt'),
|
||||||
|
avatar: query.img(el, null, 'src'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
release.poster = query.poster('.content-video video');
|
||||||
|
release.photos = query.urls('#photo-carousel a').map(photo => [
|
||||||
|
photo.replace('/full', ''),
|
||||||
|
photo,
|
||||||
|
photo.replace('/full', '/thumbs'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const trailer = query.video('.content-video source');
|
||||||
|
release.trailer = { src: trailer };
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchActorScenes({ query }, accReleases = []) {
|
||||||
|
const releases = scrapeAll(qu.initAll(query.all('.container-large-video-thumb')));
|
||||||
|
const nextPage = query.url('.pagination li:nth-last-child(2) a');
|
||||||
|
|
||||||
|
if (nextPage) {
|
||||||
|
const res = await qu.get(nextPage);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return fetchActorScenes(res.item, accReleases.concat(releases));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accReleases.concat(releases);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeProfile({ query }, include) {
|
||||||
|
const profile = {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
const bio = query.all('.model-stats-info div div').reduce((acc, el) => ({
|
||||||
|
...acc,
|
||||||
|
[slugify(query.cnt(el, '.stat-label'), '_')]: query.cnt(el, '.stat-value'),
|
||||||
|
}), {});
|
||||||
|
*/
|
||||||
|
|
||||||
|
profile.dateOfBirth = query.date('.col-birtdate .stat-value, .col-birthdate .stat-value', 'YYYY-MM-DD'); // sic
|
||||||
|
profile.birthPlace = query.cnt('.col-birth .stat-value');
|
||||||
|
|
||||||
|
[profile.bust, profile.waist, profile.hip] = query.cnt('.col-measurements .stat-value').split('-').map(Number);
|
||||||
|
profile.height = feetInchesToCm(query.cnt('.col-height .stat-value'));
|
||||||
|
profile.weight = lbsToKg(query.number('.col-weight .stat-value'));
|
||||||
|
|
||||||
|
profile.eyes = query.cnt('.col-eyes .stat-value');
|
||||||
|
profile.hair = query.cnt('.col-hair .stat-value');
|
||||||
|
|
||||||
|
profile.description = query.cnt('.model-profile .model-profile');
|
||||||
|
profile.avatar = query.img('.model-thumbnail img');
|
||||||
|
|
||||||
|
if (include.releases) {
|
||||||
|
profile.releases = await fetchActorScenes({ query });
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const res = await qu.getAll(`https://tour.hitzefrei.com/videos?site=${channel.parameters.siteId}&page=${page}`, '.hitem');
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeAll(res.items, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchScene(url) {
|
||||||
|
const res = await qu.get(url, '#content-details');
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeScene(res.item, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile(baseActor, entity, include) {
|
||||||
|
const searchRes = await http.post('https://tour.hitzefrei.com/search-preview', {
|
||||||
|
q: baseActor.name,
|
||||||
|
}, {
|
||||||
|
'Accept-Language': 'en-US',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchRes.ok) {
|
||||||
|
const actor = searchRes.body.find(result => result.type === 'model' && result.title === baseActor.name);
|
||||||
|
|
||||||
|
if (actor) {
|
||||||
|
const actorRes = await qu.get(actor.url);
|
||||||
|
|
||||||
|
if (actorRes.ok) {
|
||||||
|
return scrapeProfile(actorRes.item, include);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actorRes.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchRes.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchLatest,
|
||||||
|
fetchScene,
|
||||||
|
fetchProfile,
|
||||||
|
};
|
|
@ -23,6 +23,7 @@ const fantasymassage = require('./fantasymassage');
|
||||||
const fcuk = require('./fcuk');
|
const fcuk = require('./fcuk');
|
||||||
const fullpornnetwork = require('./fullpornnetwork');
|
const fullpornnetwork = require('./fullpornnetwork');
|
||||||
const girlsway = require('./girlsway');
|
const girlsway = require('./girlsway');
|
||||||
|
const hitzefrei = require('./hitzefrei');
|
||||||
const hush = require('./hush');
|
const hush = require('./hush');
|
||||||
const iconmale = require('./iconmale');
|
const iconmale = require('./iconmale');
|
||||||
const insex = require('./insex');
|
const insex = require('./insex');
|
||||||
|
@ -101,6 +102,7 @@ module.exports = {
|
||||||
fullpornnetwork,
|
fullpornnetwork,
|
||||||
girlsway,
|
girlsway,
|
||||||
girlgirl: julesjordan,
|
girlgirl: julesjordan,
|
||||||
|
hitzefrei,
|
||||||
hussiepass: hush,
|
hussiepass: hush,
|
||||||
hushpass: hush,
|
hushpass: hush,
|
||||||
insex,
|
insex,
|
||||||
|
@ -183,6 +185,7 @@ module.exports = {
|
||||||
girlfaction: fullpornnetwork,
|
girlfaction: fullpornnetwork,
|
||||||
gloryholesecrets: aziani,
|
gloryholesecrets: aziani,
|
||||||
hergape: fullpornnetwork,
|
hergape: fullpornnetwork,
|
||||||
|
hitzefrei,
|
||||||
homemadeanalwhores: fullpornnetwork,
|
homemadeanalwhores: fullpornnetwork,
|
||||||
hotcrazymess: nubiles,
|
hotcrazymess: nubiles,
|
||||||
hushpass: hush,
|
hushpass: hush,
|
||||||
|
|