Added chapters and shoot location. Added In The Crack.
|
@ -55,7 +55,7 @@ To build traxxx, run the following command:
|
||||||
|
|
||||||
`npm run build`
|
`npm run build`
|
||||||
|
|
||||||
To generate thumbnails for logos and tag photos, install ImageMagick and run:
|
To generate thumbnails for new logos and tag photos, install ImageMagick and run:
|
||||||
|
|
||||||
`npm run logos-thumbs`
|
`npm run logos-thumbs`
|
||||||
|
|
||||||
|
|
|
@ -105,18 +105,20 @@ function sfw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function photos() {
|
function photos() {
|
||||||
|
const photosWithChapterPosters = (this.release.photos || []).concat(this.release.chapters ? this.release.chapters.map(chapter => chapter.poster) : []);
|
||||||
|
|
||||||
if (this.release.trailer || this.release.teaser) {
|
if (this.release.trailer || this.release.teaser) {
|
||||||
// poster will be on trailer video
|
// poster will be on trailer video
|
||||||
return this.release.photos;
|
return photosWithChapterPosters;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.release.poster) {
|
if (this.release.poster) {
|
||||||
// no trailer, add poster to photos
|
// no trailer, add poster to photos
|
||||||
return [this.release.poster].concat(this.release.photos);
|
return [this.release.poster].concat(this.release.photos).concat(photosWithChapterPosters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no poster available
|
// no poster available
|
||||||
return this.release.photos;
|
return photosWithChapterPosters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -53,6 +53,7 @@ export default {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: $breakpoint) {
|
@media(max-width: $breakpoint) {
|
||||||
|
|
|
@ -99,6 +99,51 @@
|
||||||
<p class="description">{{ release.description }}</p>
|
<p class="description">{{ release.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-if="release.chapters && release.chapters.length > 0"
|
||||||
|
class="chapters row nolist"
|
||||||
|
>
|
||||||
|
<span class="row-label">Chapters</span>
|
||||||
|
<li
|
||||||
|
v-for="chapter in release.chapters"
|
||||||
|
:key="`chapter-${chapter.id}`"
|
||||||
|
class="chapter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
v-if="chapter.poster"
|
||||||
|
:href="`/media/${chapter.poster.path}`"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="chapter-poster-link"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="`/media/${chapter.poster.thumbnail}`"
|
||||||
|
class="chapter-poster"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="chapter-info">
|
||||||
|
<div class="chapter-header">
|
||||||
|
<h3 class="chapter-title">{{ chapter.title }}</h3>
|
||||||
|
<span
|
||||||
|
v-if="chapter.duration"
|
||||||
|
class="chapter-duration"
|
||||||
|
>{{ chapter.duration }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="chapter.description"
|
||||||
|
class="chapter-description"
|
||||||
|
>{{ chapter.description }}</p>
|
||||||
|
|
||||||
|
<ul class="nolist"><li
|
||||||
|
v-for="tag in chapter.tags"
|
||||||
|
:key="`chapter-tag-${tag.id}`"
|
||||||
|
>{{ tag.name }}</li></ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="row row-tidbits">
|
<div class="row row-tidbits">
|
||||||
<div
|
<div
|
||||||
v-if="release.duration"
|
v-if="release.duration"
|
||||||
|
@ -143,6 +188,32 @@
|
||||||
<span class="row-label">Shoot date</span>
|
<span class="row-label">Shoot date</span>
|
||||||
{{ formatDate(release.productionDate, 'MMMM D, YYYY') }}
|
{{ formatDate(release.productionDate, 'MMMM D, YYYY') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="release.productionLocation"
|
||||||
|
class="row-tidbit"
|
||||||
|
>
|
||||||
|
<span class="row-label">Location</span>
|
||||||
|
<span class="location">
|
||||||
|
<span
|
||||||
|
v-if="release.productionLocation.city"
|
||||||
|
class="location-segment"
|
||||||
|
>{{ release.productionLocation.city }}, </span>
|
||||||
|
<span
|
||||||
|
v-if="release.productionLocation.state"
|
||||||
|
class="location-segment"
|
||||||
|
>{{ release.productionLocation.state }}, </span>
|
||||||
|
<span
|
||||||
|
v-if="release.productionLocation.country"
|
||||||
|
class="location-segment"
|
||||||
|
>{{ release.productionLocation.country.alias || release.productionLocation.country.name }}
|
||||||
|
<img
|
||||||
|
class="flag"
|
||||||
|
:src="`/img/flags/${release.productionLocation.country.alpha2.toLowerCase()}.svg`"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -324,6 +395,41 @@ export default {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chapter {
|
||||||
|
display: flex;
|
||||||
|
background: var(--background);
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak);
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-poster {
|
||||||
|
width: 12rem;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 1rem 1rem .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-title {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0 0 -.15rem .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
|
|
|
@ -69,6 +69,7 @@ function curateRelease(release) {
|
||||||
|
|
||||||
if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene));
|
if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene));
|
||||||
if (release.movies) curatedRelease.movies = release.movies.map(({ movie }) => curateRelease(movie));
|
if (release.movies) curatedRelease.movies = release.movies.map(({ movie }) => curateRelease(movie));
|
||||||
|
if (release.chapters) curatedRelease.chapters = release.chapters.map(chapter => curateRelease(chapter));
|
||||||
if (release.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
|
if (release.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
|
||||||
if (release.covers) curatedRelease.covers = release.covers.map(({ media }) => media);
|
if (release.covers) curatedRelease.covers = release.covers.map(({ media }) => media);
|
||||||
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
||||||
|
@ -77,6 +78,15 @@ function curateRelease(release) {
|
||||||
if (release.movieTags && release.movieTags.length > 0) curatedRelease.tags = release.movieTags.map(({ tag }) => tag);
|
if (release.movieTags && release.movieTags.length > 0) curatedRelease.tags = release.movieTags.map(({ tag }) => tag);
|
||||||
if (release.movieActors && release.movieActors.length > 0) curatedRelease.actors = release.movieActors.map(({ actor }) => curateActor(actor, curatedRelease));
|
if (release.movieActors && release.movieActors.length > 0) curatedRelease.actors = release.movieActors.map(({ actor }) => curateActor(actor, curatedRelease));
|
||||||
|
|
||||||
|
if (release.productionLocation) {
|
||||||
|
curatedRelease.productionLocation = {
|
||||||
|
raw: release.productionLocation,
|
||||||
|
city: release.productionCity,
|
||||||
|
state: release.productionState,
|
||||||
|
country: release.productionCountry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return curatedRelease;
|
return curatedRelease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -237,6 +237,14 @@ const releaseFragment = `
|
||||||
createdAt
|
createdAt
|
||||||
shootId
|
shootId
|
||||||
productionDate
|
productionDate
|
||||||
|
productionLocation
|
||||||
|
productionCity
|
||||||
|
productionState
|
||||||
|
productionCountry: countryByProductionCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
comment
|
comment
|
||||||
url
|
url
|
||||||
${releaseActorsFragment}
|
${releaseActorsFragment}
|
||||||
|
@ -247,6 +255,35 @@ const releaseFragment = `
|
||||||
${releaseTrailerFragment}
|
${releaseTrailerFragment}
|
||||||
${releaseTeaserFragment}
|
${releaseTeaserFragment}
|
||||||
${siteFragment}
|
${siteFragment}
|
||||||
|
chapters {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
duration
|
||||||
|
tags: chaptersTags {
|
||||||
|
tag {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
poster: chaptersPosterByChapterId {
|
||||||
|
media {
|
||||||
|
index
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
lazy
|
||||||
|
comment
|
||||||
|
sfw: sfwMedia {
|
||||||
|
id
|
||||||
|
thumbnail
|
||||||
|
lazy
|
||||||
|
path
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
studio {
|
studio {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
@ -258,7 +295,7 @@ const releaseFragment = `
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
covers: moviesCoversByReleaseId {
|
covers: moviesCovers {
|
||||||
media {
|
media {
|
||||||
index
|
index
|
||||||
path
|
path
|
||||||
|
|
|
@ -7,8 +7,6 @@ function initReleasesActions(store, _router) {
|
||||||
async function fetchReleases({ _commit }, { limit = 10, pageNumber = 1, range = 'latest' }) {
|
async function fetchReleases({ _commit }, { limit = 10, pageNumber = 1, range = 'latest' }) {
|
||||||
const { before, after, orderBy } = getDateRange(range);
|
const { before, after, orderBy } = getDateRange(range);
|
||||||
|
|
||||||
console.log(after, before, orderBy);
|
|
||||||
|
|
||||||
const { connection: { releases, totalCount } } = await graphql(`
|
const { connection: { releases, totalCount } } = await graphql(`
|
||||||
query Releases(
|
query Releases(
|
||||||
$limit:Int = 1000,
|
$limit:Int = 1000,
|
||||||
|
@ -89,7 +87,7 @@ function initReleasesActions(store, _router) {
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
covers: moviesCoversByReleaseId {
|
covers: moviesCovers {
|
||||||
media {
|
media {
|
||||||
id
|
id
|
||||||
path
|
path
|
||||||
|
@ -139,14 +137,14 @@ function initReleasesActions(store, _router) {
|
||||||
lazy
|
lazy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
covers: moviesCoversByReleaseId {
|
covers: moviesCovers {
|
||||||
media {
|
media {
|
||||||
id
|
id
|
||||||
path
|
path
|
||||||
thumbnail
|
thumbnail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trailer: moviesTrailerByReleaseId {
|
trailer: moviesTrailerByMovieId {
|
||||||
media {
|
media {
|
||||||
id
|
id
|
||||||
path
|
path
|
||||||
|
|
|
@ -26,8 +26,6 @@ module.exports = {
|
||||||
'amberathome',
|
'amberathome',
|
||||||
'marycarey',
|
'marycarey',
|
||||||
'racqueldevonshire',
|
'racqueldevonshire',
|
||||||
// boobpedia
|
|
||||||
'boobpedia',
|
|
||||||
// blowpass
|
// blowpass
|
||||||
'sunlustxxx',
|
'sunlustxxx',
|
||||||
// ddfnetwork
|
// ddfnetwork
|
||||||
|
|
|
@ -614,6 +614,7 @@ exports.up = knex => Promise.resolve()
|
||||||
|
|
||||||
table.text('shoot_id');
|
table.text('shoot_id');
|
||||||
table.text('entry_id');
|
table.text('entry_id');
|
||||||
|
|
||||||
table.unique(['entity_id', 'entry_id']);
|
table.unique(['entity_id', 'entry_id']);
|
||||||
|
|
||||||
table.text('url', 1000);
|
table.text('url', 1000);
|
||||||
|
@ -625,6 +626,13 @@ exports.up = knex => Promise.resolve()
|
||||||
|
|
||||||
table.date('production_date');
|
table.date('production_date');
|
||||||
|
|
||||||
|
table.text('production_location');
|
||||||
|
table.text('production_city');
|
||||||
|
table.text('production_state');
|
||||||
|
table.text('production_country_alpha2', 2)
|
||||||
|
.references('alpha2')
|
||||||
|
.inTable('countries');
|
||||||
|
|
||||||
table.enum('date_precision', ['year', 'month', 'day', 'hour', 'minute', 'second'])
|
table.enum('date_precision', ['year', 'month', 'day', 'hour', 'minute', 'second'])
|
||||||
.defaultTo('day');
|
.defaultTo('day');
|
||||||
|
|
||||||
|
@ -821,7 +829,7 @@ exports.up = knex => Promise.resolve()
|
||||||
.defaultTo(knex.fn.now());
|
.defaultTo(knex.fn.now());
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('movies_covers', (table) => {
|
.then(() => knex.schema.createTable('movies_covers', (table) => {
|
||||||
table.integer('release_id', 16)
|
table.integer('movie_id', 16)
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('movies');
|
.inTable('movies');
|
||||||
|
@ -831,10 +839,10 @@ exports.up = knex => Promise.resolve()
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('media');
|
.inTable('media');
|
||||||
|
|
||||||
table.unique(['release_id', 'media_id']);
|
table.unique(['movie_id', 'media_id']);
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('movies_trailers', (table) => {
|
.then(() => knex.schema.createTable('movies_trailers', (table) => {
|
||||||
table.integer('release_id', 16)
|
table.integer('movie_id', 16)
|
||||||
.unique()
|
.unique()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references('id')
|
.references('id')
|
||||||
|
@ -845,6 +853,74 @@ exports.up = knex => Promise.resolve()
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('media');
|
.inTable('media');
|
||||||
}))
|
}))
|
||||||
|
.then(() => knex.schema.createTable('chapters', (table) => {
|
||||||
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
table.integer('release_id', 12)
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases')
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.integer('chapter', 6);
|
||||||
|
|
||||||
|
table.unique(['release_id', 'chapter']);
|
||||||
|
|
||||||
|
table.text('title');
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
|
table.integer('duration')
|
||||||
|
.unsigned();
|
||||||
|
|
||||||
|
table.integer('created_batch_id', 12)
|
||||||
|
.references('id')
|
||||||
|
.inTable('batches');
|
||||||
|
|
||||||
|
table.integer('updated_batch_id', 12)
|
||||||
|
.references('id')
|
||||||
|
.inTable('batches');
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('chapters_posters', (table) => {
|
||||||
|
table.integer('chapter_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('chapters');
|
||||||
|
|
||||||
|
table.text('media_id', 21)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique('chapter_id');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('chapters_photos', (table) => {
|
||||||
|
table.integer('chapter_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('chapters');
|
||||||
|
|
||||||
|
table.text('media_id', 21)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique(['chapter_id', 'media_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('chapters_tags', (table) => {
|
||||||
|
table.integer('tag_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags');
|
||||||
|
|
||||||
|
table.integer('chapter_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('chapters');
|
||||||
|
|
||||||
|
table.unique(['tag_id', 'chapter_id']);
|
||||||
|
}))
|
||||||
// SEARCH
|
// SEARCH
|
||||||
.then(() => { // eslint-disable-line arrow-body-style
|
.then(() => { // eslint-disable-line arrow-body-style
|
||||||
// allow vim fold
|
// allow vim fold
|
||||||
|
@ -1024,6 +1100,10 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
||||||
DROP TABLE IF EXISTS movies_scenes CASCADE;
|
DROP TABLE IF EXISTS movies_scenes CASCADE;
|
||||||
DROP TABLE IF EXISTS movies_trailers CASCADE;
|
DROP TABLE IF EXISTS movies_trailers CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS chapters_tags CASCADE;
|
||||||
|
DROP TABLE IF EXISTS chapters_posters CASCADE;
|
||||||
|
DROP TABLE IF EXISTS chapters_photos CASCADE;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS batches CASCADE;
|
DROP TABLE IF EXISTS batches CASCADE;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS actors_avatars CASCADE;
|
DROP TABLE IF EXISTS actors_avatars CASCADE;
|
||||||
|
@ -1042,6 +1122,7 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
||||||
DROP TABLE IF EXISTS tags_posters CASCADE;
|
DROP TABLE IF EXISTS tags_posters CASCADE;
|
||||||
DROP TABLE IF EXISTS tags_photos CASCADE;
|
DROP TABLE IF EXISTS tags_photos CASCADE;
|
||||||
DROP TABLE IF EXISTS movies CASCADE;
|
DROP TABLE IF EXISTS movies CASCADE;
|
||||||
|
DROP TABLE IF EXISTS chapters CASCADE;
|
||||||
DROP TABLE IF EXISTS releases CASCADE;
|
DROP TABLE IF EXISTS releases CASCADE;
|
||||||
DROP TABLE IF EXISTS actors CASCADE;
|
DROP TABLE IF EXISTS actors CASCADE;
|
||||||
DROP TABLE IF EXISTS directors CASCADE;
|
DROP TABLE IF EXISTS directors CASCADE;
|
||||||
|
|
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -57,6 +57,11 @@ const groups = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
|
{
|
||||||
|
name: '3d',
|
||||||
|
slug: '3d',
|
||||||
|
description: 'Available in 3D.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '4K',
|
name: '4K',
|
||||||
slug: '4k',
|
slug: '4k',
|
||||||
|
|
|
@ -2645,6 +2645,12 @@ const sites = [
|
||||||
accFilter: true,
|
accFilter: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// IN THE CRACK
|
||||||
|
{
|
||||||
|
slug: 'inthecrack',
|
||||||
|
name: 'InTheCrack',
|
||||||
|
url: 'https://inthecrack.com/',
|
||||||
|
},
|
||||||
// INTERRACIAL PASS
|
// INTERRACIAL PASS
|
||||||
{
|
{
|
||||||
slug: '2bigtobetrue',
|
slug: '2bigtobetrue',
|
||||||
|
|
|
@ -608,7 +608,7 @@ async function scrapeActors(argNames) {
|
||||||
|
|
||||||
logger.info(`Scraping profiles for ${actorNames.length} actors`);
|
logger.info(`Scraping profiles for ${actorNames.length} actors`);
|
||||||
|
|
||||||
const sources = argv.sources || config.profiles || Object.keys(scrapers.actors);
|
const sources = argv.actorsSources || config.profiles || Object.keys(scrapers.actors);
|
||||||
const entitySlugs = sources.flat();
|
const entitySlugs = sources.flat();
|
||||||
|
|
||||||
const [entities, existingActorEntries] = await Promise.all([
|
const [entities, existingActorEntries] = await Promise.all([
|
||||||
|
|
|
@ -617,7 +617,7 @@ async function storeMedias(baseMedias) {
|
||||||
return [...newMediaWithEntries, ...existingHashMedias];
|
return [...newMediaWithEntries, ...existingHashMedias];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function associateReleaseMedia(releases, type = 'releases') {
|
async function associateReleaseMedia(releases, type = 'release') {
|
||||||
if (!argv.media) {
|
if (!argv.media) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -664,7 +664,7 @@ async function associateReleaseMedia(releases, type = 'releases') {
|
||||||
|
|
||||||
if (media) {
|
if (media) {
|
||||||
acc.push({
|
acc.push({
|
||||||
release_id: releaseId,
|
[`${type}_id`]: releaseId,
|
||||||
media_id: media.use || media.entry.id,
|
media_id: media.use || media.entry.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@ async function associateReleaseMedia(releases, type = 'releases') {
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
if (associations.length > 0) {
|
if (associations.length > 0) {
|
||||||
await bulkInsert(`${type}_${role}`, associations, false);
|
await bulkInsert(`${type}s_${role}`, associations, false);
|
||||||
}
|
}
|
||||||
}, Promise.resolve());
|
}, Promise.resolve());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const qu = require('../utils/q');
|
||||||
|
const slugify = require('../utils/slugify');
|
||||||
|
|
||||||
|
function scrapeAll(scenes, channel) {
|
||||||
|
return scenes.map(({ query }) => {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.url = query.url('a', 'href', { origin: channel.url });
|
||||||
|
release.entryId = new URL(release.url).pathname.match(/\/Collection\/(\d+)/)[1];
|
||||||
|
|
||||||
|
release.shootId = query.cnt('a span:nth-of-type(1)').match(/^\d+/)?.[0];
|
||||||
|
release.date = query.date('a span:nth-of-type(2)', 'YYYY-MM-DD');
|
||||||
|
|
||||||
|
release.actors = (query.q('a img', 'alt') || query.cnt('a span:nth-of-type(1)'))?.match(/[a-zA-Z]+(\s[A-Za-z]+)*/g);
|
||||||
|
|
||||||
|
release.poster = release.shootId
|
||||||
|
? `https://inthecrack.com/assets/images/posters/collections/${release.shootId}.jpg`
|
||||||
|
: query.img('a img', 'src', { origin: channel.url });
|
||||||
|
|
||||||
|
return release;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeScene({ query, html }, url, channel) {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.entryId = new URL(url).pathname.match(/\/Collection\/(\d+)/)[1];
|
||||||
|
release.shootId = query.cnt('h2 span').match(/^\d+/)?.[0];
|
||||||
|
|
||||||
|
release.actors = query.cnt('h2 span')?.match(/[a-zA-Z]+(\s[A-Za-z]+)*/g);
|
||||||
|
|
||||||
|
release.description = query.cnt('p#CollectionDescription');
|
||||||
|
release.productionLocation = query.cnt('.modelCollectionHeader p')?.match(/Shoot Location: (.*)/)?.[1];
|
||||||
|
|
||||||
|
release.poster = qu.prefixUrl(html.match(/background-image: url\('(.*)'\)/)?.[1], channel.url);
|
||||||
|
|
||||||
|
release.chapters = query.all('.ClipOuter').map((el) => {
|
||||||
|
const chapter = {};
|
||||||
|
|
||||||
|
chapter.title = query.text(el, 'h4');
|
||||||
|
chapter.description = query.cnt(el, 'p');
|
||||||
|
chapter.duration = query.dur(el, '.InlineDuration');
|
||||||
|
|
||||||
|
const posterStyle = query.style(el, '.clipImage', 'background-image');
|
||||||
|
const poster = qu.prefixUrl(posterStyle.match(/url\((.*)\)/)?.[1], channel.url);
|
||||||
|
|
||||||
|
if (poster) {
|
||||||
|
const { origin, pathname } = new URL(poster);
|
||||||
|
|
||||||
|
chapter.poster = [
|
||||||
|
`${origin}${pathname}`, // full size
|
||||||
|
poster,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.exists(el, '.ThreeDInfo')) {
|
||||||
|
chapter.tags = ['3d'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapter;
|
||||||
|
});
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeProfile({ query, el }, actorName, entity, include) {
|
||||||
|
const profile = {};
|
||||||
|
|
||||||
|
profile.description = query.cnt('.bio-text');
|
||||||
|
profile.birthPlace = query.cnt('.birth-place span');
|
||||||
|
|
||||||
|
profile.avatar = query.img('.actor-photo img');
|
||||||
|
|
||||||
|
if (include.releases) {
|
||||||
|
return scrapeAll(qu.initAll(el, '.scene'));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(profile);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const year = moment().subtract(page - 1, ' year').year();
|
||||||
|
|
||||||
|
const url = `${channel.url}/Collections/Date/${year}`;
|
||||||
|
const res = await qu.getAll(url, '.collectionGridLayout li');
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeAll(res.items, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchScene(url, channel) {
|
||||||
|
const res = await qu.get(url);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeScene(res.item, url, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile({ name: actorName }, entity, include) {
|
||||||
|
const url = `${entity.url}/actors/${slugify(actorName, '_')}`;
|
||||||
|
const res = await qu.get(url);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeProfile(res.item, actorName, entity, include);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchLatest,
|
||||||
|
fetchScene,
|
||||||
|
// fetchProfile,
|
||||||
|
};
|
|
@ -27,6 +27,7 @@ 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');
|
||||||
|
const inthecrack = require('./inthecrack');
|
||||||
const jayrock = require('./jayrock');
|
const jayrock = require('./jayrock');
|
||||||
const jesseloadsmonsterfacials = require('./jesseloadsmonsterfacials');
|
const jesseloadsmonsterfacials = require('./jesseloadsmonsterfacials');
|
||||||
const julesjordan = require('./julesjordan');
|
const julesjordan = require('./julesjordan');
|
||||||
|
@ -108,6 +109,7 @@ module.exports = {
|
||||||
hushpass: hush,
|
hushpass: hush,
|
||||||
insex,
|
insex,
|
||||||
interracialpass: hush,
|
interracialpass: hush,
|
||||||
|
inthecrack,
|
||||||
jayrock,
|
jayrock,
|
||||||
jesseloadsmonsterfacials,
|
jesseloadsmonsterfacials,
|
||||||
julesjordan,
|
julesjordan,
|
||||||
|
|
|
@ -7,13 +7,14 @@ const logger = require('./logger')(__filename);
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
const slugify = require('./utils/slugify');
|
const slugify = require('./utils/slugify');
|
||||||
const bulkInsert = require('./utils/bulk-insert');
|
const bulkInsert = require('./utils/bulk-insert');
|
||||||
|
const resolvePlace = require('./utils/resolve-place');
|
||||||
const { formatDate } = require('./utils/qu');
|
const { formatDate } = require('./utils/qu');
|
||||||
const { associateActors, scrapeActors } = require('./actors');
|
const { associateActors, scrapeActors } = require('./actors');
|
||||||
const { associateReleaseTags } = require('./tags');
|
const { associateReleaseTags } = require('./tags');
|
||||||
const { curateEntity } = require('./entities');
|
const { curateEntity } = require('./entities');
|
||||||
const { associateReleaseMedia } = require('./media');
|
const { associateReleaseMedia } = require('./media');
|
||||||
|
|
||||||
function curateReleaseEntry(release, batchId, existingRelease, type = 'scene') {
|
async function curateReleaseEntry(release, batchId, existingRelease, type = 'scene') {
|
||||||
const slugBase = release.title
|
const slugBase = release.title
|
||||||
|| (release.actors?.length && `${release.entity.slug} ${release.actors.map(actor => actor.name).join(' ')}`)
|
|| (release.actors?.length && `${release.entity.slug} ${release.actors.map(actor => actor.name).join(' ')}`)
|
||||||
|| (release.date && `${release.entity.slug} ${formatDate(release.date, 'YYYY MM DD')}`)
|
|| (release.date && `${release.entity.slug} ${formatDate(release.date, 'YYYY MM DD')}`)
|
||||||
|
@ -50,6 +51,20 @@ function curateReleaseEntry(release, batchId, existingRelease, type = 'scene') {
|
||||||
curatedRelease.duration = release.duration;
|
curatedRelease.duration = release.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (release.productionLocation) {
|
||||||
|
curatedRelease.production_location = release.productionLocation;
|
||||||
|
|
||||||
|
if (argv.resolvePlace) {
|
||||||
|
const productionLocation = await resolvePlace(release.productionLocation);
|
||||||
|
|
||||||
|
if (productionLocation) {
|
||||||
|
curatedRelease.production_city = productionLocation.city;
|
||||||
|
curatedRelease.production_state = productionLocation.state;
|
||||||
|
curatedRelease.production_country_alpha2 = productionLocation.country;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!existingRelease && !release.id) {
|
if (!existingRelease && !release.id) {
|
||||||
curatedRelease.created_batch_id = batchId;
|
curatedRelease.created_batch_id = batchId;
|
||||||
}
|
}
|
||||||
|
@ -228,6 +243,46 @@ async function updateReleasesSearch(releaseIds) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function storeChapters(releases) {
|
||||||
|
const chapters = releases.map(release => release.chapters?.map((chapter, index) => ({
|
||||||
|
title: chapter.title,
|
||||||
|
description: chapter.description,
|
||||||
|
releaseId: release.id,
|
||||||
|
chapter: index + 1,
|
||||||
|
duration: chapter.duration,
|
||||||
|
poster: chapter.poster,
|
||||||
|
photos: chapter.photos,
|
||||||
|
tags: chapter.tags,
|
||||||
|
}))).flat().filter(Boolean);
|
||||||
|
|
||||||
|
const curatedChapterEntries = chapters.map(chapter => ({
|
||||||
|
title: chapter.title,
|
||||||
|
description: chapter.description,
|
||||||
|
duration: chapter.duration,
|
||||||
|
release_id: chapter.releaseId,
|
||||||
|
chapter: chapter.chapter,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const storedChapters = await bulkInsert('chapters', curatedChapterEntries);
|
||||||
|
const chapterIdsByReleaseIdAndChapter = storedChapters.reduce((acc, chapter) => ({
|
||||||
|
...acc,
|
||||||
|
[chapter.release_id]: {
|
||||||
|
...acc[chapter.release_id],
|
||||||
|
[chapter.chapter]: chapter.id,
|
||||||
|
},
|
||||||
|
}), {});
|
||||||
|
|
||||||
|
const chaptersWithId = chapters.map(chapter => ({
|
||||||
|
...chapter,
|
||||||
|
id: chapterIdsByReleaseIdAndChapter[chapter.releaseId][chapter.chapter],
|
||||||
|
}));
|
||||||
|
|
||||||
|
await associateReleaseTags(chaptersWithId, 'chapter');
|
||||||
|
|
||||||
|
// media is more error-prone, associate separately
|
||||||
|
await associateReleaseMedia(chaptersWithId, 'chapter');
|
||||||
|
}
|
||||||
|
|
||||||
async function storeScenes(releases) {
|
async function storeScenes(releases) {
|
||||||
if (releases.length === 0) {
|
if (releases.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -241,7 +296,7 @@ async function storeScenes(releases) {
|
||||||
// uniqueness is entity ID + entry ID, filter uniques after adding entities
|
// uniqueness is entity ID + entry ID, filter uniques after adding entities
|
||||||
const { uniqueReleases, duplicateReleases, duplicateReleaseEntries } = await filterDuplicateReleases(releasesWithStudios);
|
const { uniqueReleases, duplicateReleases, duplicateReleaseEntries } = await filterDuplicateReleases(releasesWithStudios);
|
||||||
|
|
||||||
const curatedNewReleaseEntries = uniqueReleases.map(release => curateReleaseEntry(release, batchId));
|
const curatedNewReleaseEntries = await Promise.all(uniqueReleases.map(release => curateReleaseEntry(release, batchId)));
|
||||||
|
|
||||||
const storedReleases = await bulkInsert('releases', curatedNewReleaseEntries);
|
const storedReleases = await bulkInsert('releases', curatedNewReleaseEntries);
|
||||||
// TODO: update duplicate releases
|
// TODO: update duplicate releases
|
||||||
|
@ -263,6 +318,8 @@ async function storeScenes(releases) {
|
||||||
await scrapeActors(actors.map(actor => actor.name));
|
await scrapeActors(actors.map(actor => actor.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await storeChapters(releasesWithId);
|
||||||
|
|
||||||
logger.info(`Stored ${storedReleaseEntries.length} releases`);
|
logger.info(`Stored ${storedReleaseEntries.length} releases`);
|
||||||
|
|
||||||
return releasesWithId;
|
return releasesWithId;
|
||||||
|
@ -303,13 +360,13 @@ async function storeMovies(movies, movieScenes) {
|
||||||
const { uniqueReleases } = await filterDuplicateReleases(movies);
|
const { uniqueReleases } = await filterDuplicateReleases(movies);
|
||||||
const [batchId] = await knex('batches').insert({ comment: null }).returning('id');
|
const [batchId] = await knex('batches').insert({ comment: null }).returning('id');
|
||||||
|
|
||||||
const curatedMovieEntries = uniqueReleases.map(release => curateReleaseEntry(release, batchId, null, 'movie'));
|
const curatedMovieEntries = await Promise.all(uniqueReleases.map(release => curateReleaseEntry(release, batchId, null, 'movie')));
|
||||||
|
|
||||||
const storedMovies = await bulkInsert('movies', curatedMovieEntries, ['entity_id', 'entry_id'], true);
|
const storedMovies = await bulkInsert('movies', curatedMovieEntries, ['entity_id', 'entry_id'], true);
|
||||||
const moviesWithId = attachReleaseIds(movies, storedMovies);
|
const moviesWithId = attachReleaseIds(movies, storedMovies);
|
||||||
|
|
||||||
await associateMovieScenes(moviesWithId, movieScenes);
|
await associateMovieScenes(moviesWithId, movieScenes);
|
||||||
await associateReleaseMedia(moviesWithId, 'movies');
|
await associateReleaseMedia(moviesWithId, 'movie');
|
||||||
|
|
||||||
return storedMovies;
|
return storedMovies;
|
||||||
}
|
}
|
||||||
|
|
36
src/tags.js
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
const slugify = require('./utils/slugify');
|
const slugify = require('./utils/slugify');
|
||||||
|
const bulkInsert = require('./utils/bulk-insert');
|
||||||
|
|
||||||
async function matchReleaseTags(releases) {
|
async function matchReleaseTags(releases) {
|
||||||
const rawTags = releases
|
const rawTags = releases
|
||||||
|
@ -28,7 +29,7 @@ async function matchReleaseTags(releases) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEntityTags(releases) {
|
async function getEntityTags(releases) {
|
||||||
const entityIds = releases.map(release => release.entity.id);
|
const entityIds = releases.map(release => release.entity?.id).filter(Boolean);
|
||||||
const entityTags = await knex('entities_tags').whereIn('entity_id', entityIds);
|
const entityTags = await knex('entities_tags').whereIn('entity_id', entityIds);
|
||||||
|
|
||||||
const entityTagIdsByEntityId = entityTags.reduce((acc, entityTag) => {
|
const entityTagIdsByEntityId = entityTags.reduce((acc, entityTag) => {
|
||||||
|
@ -44,10 +45,10 @@ async function getEntityTags(releases) {
|
||||||
return entityTagIdsByEntityId;
|
return entityTagIdsByEntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId) {
|
function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type) {
|
||||||
const tagAssociations = releases
|
const tagAssociations = releases
|
||||||
.map((release) => {
|
.map((release) => {
|
||||||
const entityTagIds = entityTagIdsByEntityId[release.entity.id];
|
const entityTagIds = entityTagIdsByEntityId[release.entity?.id] || [];
|
||||||
const releaseTags = release.tags || [];
|
const releaseTags = release.tags || [];
|
||||||
|
|
||||||
const releaseTagIds = releaseTags.every(tag => typeof tag === 'number')
|
const releaseTagIds = releaseTags.every(tag => typeof tag === 'number')
|
||||||
|
@ -61,7 +62,7 @@ function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntit
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
)]
|
)]
|
||||||
.map(tagId => ({
|
.map(tagId => ({
|
||||||
release_id: release.id,
|
[`${type}_id`]: release.id,
|
||||||
tag_id: tagId,
|
tag_id: tagId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -72,34 +73,13 @@ function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntit
|
||||||
return tagAssociations;
|
return tagAssociations;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterUniqueAssociations(tagAssociations) {
|
async function associateReleaseTags(releases, type = 'release') {
|
||||||
const duplicateAssociations = await knex('releases_tags')
|
|
||||||
.whereIn(['release_id', 'tag_id'], tagAssociations.map(association => [association.release_id, association.tag_id]));
|
|
||||||
|
|
||||||
const duplicateAssociationsByReleaseIdAndTagId = duplicateAssociations.reduce((acc, association) => {
|
|
||||||
if (!acc[association.release_id]) {
|
|
||||||
acc[association.release_id] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
acc[association.release_id][association.tag_id] = true;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const uniqueAssociations = tagAssociations
|
|
||||||
.filter(association => !duplicateAssociationsByReleaseIdAndTagId[association.release_id]?.[association.tag_id]);
|
|
||||||
|
|
||||||
return uniqueAssociations;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function associateReleaseTags(releases) {
|
|
||||||
const tagIdsBySlug = await matchReleaseTags(releases);
|
const tagIdsBySlug = await matchReleaseTags(releases);
|
||||||
const EntityTagIdsByEntityId = await getEntityTags(releases);
|
const EntityTagIdsByEntityId = await getEntityTags(releases);
|
||||||
|
|
||||||
const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, EntityTagIdsByEntityId);
|
const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, EntityTagIdsByEntityId, type);
|
||||||
const uniqueAssociations = await filterUniqueAssociations(tagAssociations);
|
|
||||||
|
|
||||||
await knex('releases_tags').insert(uniqueAssociations);
|
await bulkInsert(`${type}s_tags`, tagAssociations, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -4,6 +4,10 @@ const knex = require('../knex');
|
||||||
const chunk = require('./chunk');
|
const chunk = require('./chunk');
|
||||||
|
|
||||||
async function bulkUpsert(table, items, conflict, update = true, chunkSize) {
|
async function bulkUpsert(table, items, conflict, update = true, chunkSize) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const updated = (conflict === false && ':query ON CONFLICT DO NOTHING RETURNING *;')
|
const updated = (conflict === false && ':query ON CONFLICT DO NOTHING RETURNING *;')
|
||||||
|| (conflict && update && `
|
|| (conflict && update && `
|
||||||
:query ON CONFLICT (${conflict})
|
:query ON CONFLICT (${conflict})
|
||||||
|
|
|
@ -20,7 +20,15 @@ async function resolvePlace(query) {
|
||||||
const rawPlace = item.address;
|
const rawPlace = item.address;
|
||||||
const place = {};
|
const place = {};
|
||||||
|
|
||||||
if (rawPlace.city) place.city = rawPlace.city;
|
if (item.class === 'place' || item.class === 'boundary') {
|
||||||
|
const location = rawPlace[item.type] || rawPlace.city || rawPlace.place;
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
place.place = location;
|
||||||
|
place.city = rawPlace.city || location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rawPlace.state) place.state = rawPlace.state;
|
if (rawPlace.state) place.state = rawPlace.state;
|
||||||
if (rawPlace.country_code) place.country = rawPlace.country_code.toUpperCase();
|
if (rawPlace.country_code) place.country = rawPlace.country_code.toUpperCase();
|
||||||
if (rawPlace.continent) place.continent = rawPlace.continent;
|
if (rawPlace.continent) place.continent = rawPlace.continent;
|
||||||
|
|