Compare commits
46 Commits
old
...
e202e887f9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e202e887f9 | ||
|
|
574c117ab0 | ||
|
|
d59a57f311 | ||
|
|
5e499c3685 | ||
|
|
17e5ce71b2 | ||
|
|
5352186319 | ||
|
|
e9ba02d65d | ||
|
|
39813d4461 | ||
|
|
829a285a2d | ||
|
|
a19a77e165 | ||
|
|
122dd3eaee | ||
|
|
18b219850e | ||
|
|
33a327a04b | ||
|
|
a46061e247 | ||
|
|
94e07ff23d | ||
|
|
4811befcf6 | ||
|
|
c455f02c66 | ||
|
|
efc5620a28 | ||
|
|
61123fdb6a | ||
|
|
3ec6911d46 | ||
|
|
2021093645 | ||
|
|
1c72dc202f | ||
|
|
1ef946fa77 | ||
|
|
3b6bbc39ff | ||
|
|
481c9feada | ||
|
|
953b3e9568 | ||
|
|
bdd2e68f49 | ||
|
|
e4cc349302 | ||
|
|
6547b93e55 | ||
|
|
bb9649d23b | ||
|
|
9e2eaef9d1 | ||
|
|
1c3ee75d3b | ||
|
|
15c9af8057 | ||
|
|
295573c1ef | ||
|
|
e93e8ace5c | ||
|
|
43af7ba777 | ||
|
|
0dad5b0d68 | ||
|
|
ae9b793318 | ||
|
|
fd8170f223 | ||
|
|
661b8b716b | ||
|
|
5ff076cac3 | ||
|
|
41c100ac4e | ||
|
|
c6e977f842 | ||
|
|
50b7f521b5 | ||
|
|
f0d0ee3acc | ||
|
|
ccb99e278c |
52
assets/components/container/container.vue
Executable file → Normal file
@@ -8,40 +8,35 @@
|
||||
/>
|
||||
|
||||
<transition name="slide">
|
||||
<Sidebar v-if="showSidebar" />
|
||||
<Sidebar
|
||||
v-if="showSidebar"
|
||||
@toggle-sidebar="(state) => toggleSidebar(state)"
|
||||
@show-filters="(state) => toggleFilters(state)"
|
||||
/>
|
||||
</transition>
|
||||
|
||||
<Header />
|
||||
<Header
|
||||
@toggle-sidebar="(state) => toggleSidebar(state)"
|
||||
@show-filters="(state) => toggleFilters(state)"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="config.showDisclaimer"
|
||||
class="disclaimer"
|
||||
v-html="config.disclaimer"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="config.showAnnouncement"
|
||||
class="announcement"
|
||||
v-html="config.announcement"
|
||||
/>
|
||||
>{{ config.disclaimer }}</p>
|
||||
|
||||
<div
|
||||
ref="content"
|
||||
class="content"
|
||||
@scroll="scroll"
|
||||
>
|
||||
<RouterView @scroll="scrollToTop" />
|
||||
<router-view @scroll="scrollToTop" />
|
||||
</div>
|
||||
|
||||
<Filters
|
||||
v-if="showFilters"
|
||||
@close="toggleFilters(false)"
|
||||
/>
|
||||
|
||||
<Settings
|
||||
v-if="showSettings"
|
||||
@close="toggleSettings(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +45,6 @@ import Warning from './warning.vue';
|
||||
import Header from '../header/header.vue';
|
||||
import Sidebar from '../sidebar/sidebar.vue';
|
||||
import Filters from '../filters/filters.vue';
|
||||
import Settings from '../settings/settings.vue';
|
||||
|
||||
function toggleSidebar(state) {
|
||||
this.showSidebar = typeof state === 'boolean' ? state : !this.showSidebar;
|
||||
@@ -61,11 +55,6 @@ function toggleFilters(state) {
|
||||
this.showSidebar = false;
|
||||
}
|
||||
|
||||
function toggleSettings(state) {
|
||||
this.showSettings = state;
|
||||
this.showSidebar = false;
|
||||
}
|
||||
|
||||
async function setConsent(consent, includeQueer) {
|
||||
if (consent) {
|
||||
this.showWarning = false;
|
||||
@@ -99,9 +88,6 @@ function scrollToTop() {
|
||||
function mounted() {
|
||||
document.addEventListener('click', this.blur);
|
||||
window.addEventListener('resize', this.resize);
|
||||
|
||||
this.events.on('toggleSettings', this.toggleSettings);
|
||||
this.events.on('toggleSidebar', this.toggleSidebar);
|
||||
}
|
||||
|
||||
function beforeUnmount() {
|
||||
@@ -115,15 +101,12 @@ export default {
|
||||
Sidebar,
|
||||
Warning,
|
||||
Filters,
|
||||
Settings,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSidebar: false,
|
||||
showWarning: localStorage.getItem('consent') !== window.env.sessionId,
|
||||
showFilters: false,
|
||||
showSettings: false,
|
||||
selected: null,
|
||||
};
|
||||
},
|
||||
mounted,
|
||||
@@ -131,7 +114,6 @@ export default {
|
||||
methods: {
|
||||
toggleSidebar,
|
||||
toggleFilters,
|
||||
toggleSettings,
|
||||
setConsent,
|
||||
blur,
|
||||
resize,
|
||||
@@ -201,21 +183,13 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.disclaimer,
|
||||
.announcement {
|
||||
.disclaimer {
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
color: var(--text-light);
|
||||
background: var(--warn);
|
||||
font-weight: bold;
|
||||
box-shadow: inset 0 0 3px var(--darken-weak);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
background: var(--warn);
|
||||
}
|
||||
|
||||
.announcement {
|
||||
background: var(--notice);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
:fetch-releases="fetchEntity"
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
:available-tags="entity.tags"
|
||||
/>
|
||||
|
||||
<div class="releases">
|
||||
|
||||
@@ -102,6 +102,8 @@ export default {
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-light);
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default;
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default; // eslint-disable-line global-require, import/no-dynamic-require
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -11,16 +11,31 @@
|
||||
class="empty"
|
||||
>No results for "{{ $route.query.query }}"</span>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in entities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
<template v-else>
|
||||
<h2 class="heading">Popular</h2>
|
||||
|
||||
<div
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in popularEntities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="heading">All networks</h2>
|
||||
|
||||
<div
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in entities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
@@ -58,6 +73,45 @@ async function searchEntities() {
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
function popularEntities() {
|
||||
const entitiesBySlug = Object.fromEntries(this.entities.map((entity) => [entity.slug, entity]));
|
||||
|
||||
return [
|
||||
'21sextury',
|
||||
'amateurallure',
|
||||
'analvids',
|
||||
'bamvisions',
|
||||
'bang',
|
||||
'bangbros',
|
||||
'blowpass',
|
||||
'brazzers',
|
||||
'burningangel',
|
||||
'digitalplayground',
|
||||
'dogfartnetwork',
|
||||
'dorcel',
|
||||
'elegantangel',
|
||||
'evilangel',
|
||||
'fakehub',
|
||||
'girlsway',
|
||||
'hookuphotshot',
|
||||
'hussiepass',
|
||||
'insex',
|
||||
'julesjordan',
|
||||
'kellymadison',
|
||||
'kink',
|
||||
'mofos',
|
||||
'naughtyamerica',
|
||||
'newsensations',
|
||||
'pervcity',
|
||||
'pornpros',
|
||||
'private',
|
||||
'realitykings',
|
||||
'twistys',
|
||||
'vixen',
|
||||
'xempire',
|
||||
].map((slug) => entitiesBySlug[slug]).filter(Boolean);
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.pageTitle = 'Channels';
|
||||
|
||||
@@ -82,6 +136,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
channelCount,
|
||||
popularEntities,
|
||||
},
|
||||
watch: {
|
||||
$route: fetchEntities,
|
||||
@@ -130,6 +185,10 @@ export default {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint2) {
|
||||
.entity-tiles {
|
||||
grid-gap: .5rem;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="media-container">
|
||||
<div
|
||||
class="media"
|
||||
:class="{ center: release.photos.length < 2, preview: !me }"
|
||||
:class="{ center: (release.photos?.length || 0) + (release.scenesPhotos?.length || 0) < 2, preview: !me }"
|
||||
>
|
||||
<div
|
||||
v-if="release.trailer || release.teaser"
|
||||
@@ -71,24 +71,28 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<template v-if="release.covers && release.covers.length > 0">
|
||||
<a
|
||||
<template v-if="release.covers?.length > 0">
|
||||
<div
|
||||
v-for="cover in release.covers"
|
||||
:key="`cover-${cover.id}`"
|
||||
:href="getPath(cover)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="item-container"
|
||||
>
|
||||
<img
|
||||
:src="getPath(cover, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||
:width="cover.thumbnailWidth"
|
||||
:height="cover.thumbnailHeight"
|
||||
class="item cover"
|
||||
loading="lazy"
|
||||
@load="$emit('load', $event)"
|
||||
<a
|
||||
:href="getPath(cover)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
</a>
|
||||
<img
|
||||
:src="getPath(cover, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||
:width="cover.thumbnailWidth"
|
||||
:height="cover.thumbnailHeight"
|
||||
class="item cover"
|
||||
loading="lazy"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
@@ -164,8 +168,8 @@ function poster() {
|
||||
function photos() {
|
||||
const clips = this.release.clips || [];
|
||||
const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {});
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map(clip => clip.poster.id) || [])).map(posterId => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(uniqueClipPosters);
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map((clip) => clip.poster.id) || [])).map((posterId) => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(this.release.scenesPhotos || []).concat(uniqueClipPosters);
|
||||
|
||||
if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) {
|
||||
// poster will be on trailer video
|
||||
|
||||
@@ -3,12 +3,6 @@
|
||||
<div class="content-inner">
|
||||
<SearchBar :placeholder="`Search ${totalCount} movies`" />
|
||||
|
||||
<TagFilter
|
||||
class="filters-filter"
|
||||
:filter="filter"
|
||||
:available-tags="availableTags"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref="tiles"
|
||||
class="tiles"
|
||||
@@ -36,7 +30,6 @@
|
||||
import MovieTile from './movie-tile.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import TagFilter from '../filters/tag-filter.vue';
|
||||
|
||||
async function fetchMovies() {
|
||||
if (this.$route.query.query) {
|
||||
@@ -80,7 +73,6 @@ export default {
|
||||
MovieTile,
|
||||
SearchBar,
|
||||
Pagination,
|
||||
TagFilter,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
<Details :release="release" />
|
||||
|
||||
<button
|
||||
v-if="release.photos.length > 0"
|
||||
v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0"
|
||||
class="album-toggle"
|
||||
@click="$router.push({ hash: '#album' })"
|
||||
><Icon icon="grid3" />View album</button>
|
||||
|
||||
<Album
|
||||
v-if="showAlbum"
|
||||
:items="[release.poster, ...release.photos]"
|
||||
:items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]"
|
||||
:title="release.title"
|
||||
:path="config.media.mediaPath"
|
||||
@close="$router.replace({ hash: undefined })"
|
||||
@@ -79,22 +79,23 @@
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="release.movies && release.movies.length > 0"
|
||||
v-if="release.movies?.length > 0 || release.series?.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Part of</span>
|
||||
|
||||
<div class="movies">
|
||||
<router-link
|
||||
v-for="movie in release.movies"
|
||||
v-for="movie in [...release.movies, ...release.series]"
|
||||
:key="`movie-${movie.id}`"
|
||||
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
:to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="movie"
|
||||
>
|
||||
<span class="movie-title">{{ movie.title }}</span>
|
||||
|
||||
<img
|
||||
v-if="movie.covers.length > 0"
|
||||
:src="getPath(movie.covers[0], 'thumbnail')"
|
||||
v-if="movie.covers.length > 0 || movie.poster"
|
||||
:src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
|
||||
class="movie-cover"
|
||||
>
|
||||
</router-link>
|
||||
@@ -202,6 +203,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.qualities"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Available qualities</span>
|
||||
|
||||
<span
|
||||
v-for="quality in release.qualities"
|
||||
:key="quality"
|
||||
class="quality"
|
||||
>{{ quality }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.comment"
|
||||
class="row"
|
||||
@@ -243,6 +257,10 @@ async function fetchRelease(scroll = true) {
|
||||
this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (this.$route.name === 'serie') {
|
||||
this.release = await this.$store.dispatch('fetchSerieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (scroll && this.$refs.content) {
|
||||
this.$refs.content.scrollTop = 0;
|
||||
}
|
||||
@@ -282,7 +300,7 @@ function pageTitle() {
|
||||
}
|
||||
|
||||
function showAlbum() {
|
||||
return this.release.photos?.length > 0 && this.$route.hash === '#album';
|
||||
return (this.release.photos?.length > 0 || this.release.scenesPhotos?.length > 0) && this.$route.hash === '#album';
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -465,6 +483,16 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quality {
|
||||
&::after {
|
||||
content: 'p, ';
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
content: 'p',
|
||||
}
|
||||
}
|
||||
|
||||
.releases {
|
||||
margin: 0 0 .5rem 0;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ const tagSlugsByCategory = {
|
||||
'titty-fucking',
|
||||
'fisting',
|
||||
'anal-fisting',
|
||||
'fisting-dp',
|
||||
],
|
||||
group: [
|
||||
'mfm',
|
||||
@@ -108,16 +107,6 @@ const tagSlugsByCategory = {
|
||||
'bukkake',
|
||||
'fake-cum',
|
||||
],
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
roleplay: [
|
||||
'family',
|
||||
'parody',
|
||||
@@ -126,6 +115,15 @@ const tagSlugsByCategory = {
|
||||
'maid',
|
||||
'nun',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'triple-penetration',
|
||||
'tap',
|
||||
'tvp',
|
||||
],
|
||||
fetish: [
|
||||
'bdsm',
|
||||
'femdom',
|
||||
@@ -134,15 +132,15 @@ const tagSlugsByCategory = {
|
||||
'latex',
|
||||
'blindfold',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'da-tp',
|
||||
'dv-tp',
|
||||
'tap',
|
||||
'tvp',
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
misc: [
|
||||
'gaping',
|
||||
|
||||
@@ -12,6 +12,7 @@ export default {
|
||||
selectableTags: [
|
||||
'airtight',
|
||||
'anal',
|
||||
'bdsm',
|
||||
'blowbang',
|
||||
'blowjob',
|
||||
'creampie',
|
||||
|
||||
@@ -65,19 +65,23 @@ function curateActor(actor, release) {
|
||||
return curatedActor;
|
||||
}
|
||||
|
||||
function curateRelease(release) {
|
||||
function curateRelease(release, type = 'scene') {
|
||||
const curatedRelease = {
|
||||
...release,
|
||||
type: release.type || type,
|
||||
actors: [],
|
||||
poster: release.poster && release.poster.media,
|
||||
tags: release.tags ? release.tags.map((tag) => tag.tag || tag) : [],
|
||||
};
|
||||
|
||||
if (release.scenes) curatedRelease.scenes = release.scenes.filter(Boolean).map(({ scene }) => curateRelease(scene));
|
||||
if (release.movies) curatedRelease.movies = release.movies.filter(Boolean).map(({ movie }) => curateRelease(movie));
|
||||
if (release.chapters) curatedRelease.chapters = release.chapters.filter(Boolean).map((chapter) => curateRelease(chapter));
|
||||
if (release.photos) curatedRelease.photos = release.photos.filter(Boolean).map((photo) => photo.media || photo);
|
||||
if (release.covers) curatedRelease.covers = release.covers.filter(Boolean).map(({ media }) => media);
|
||||
curatedRelease.scenes = release.scenes?.filter(Boolean).map(({ scene }) => curateRelease(scene, 'scene')) || [];
|
||||
curatedRelease.movies = release.movies?.filter(Boolean).map(({ movie }) => curateRelease(movie, 'movie')) || [];
|
||||
curatedRelease.series = release.series?.filter(Boolean).map(({ serie }) => curateRelease(serie, 'serie')) || [];
|
||||
curatedRelease.chapters = release.chapters?.filter(Boolean).map((chapter) => curateRelease(chapter)) || [];
|
||||
curatedRelease.photos = release.photos?.filter(Boolean).map((photo) => photo.media || photo) || [];
|
||||
curatedRelease.scenesPhotos = release.scenesPhotos?.filter(Boolean).map((photo) => photo.media || photo) || [];
|
||||
curatedRelease.covers = release.covers?.filter(Boolean).map(({ media }) => media) || [];
|
||||
|
||||
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
||||
if (release.teaser) curatedRelease.teaser = release.teaser.media;
|
||||
if (release.actors) curatedRelease.actors = release.actors.filter(Boolean).map((actor) => curateActor(actor.actor || actor, curatedRelease));
|
||||
@@ -105,6 +109,7 @@ function curateEntity(entity, parent, releases) {
|
||||
};
|
||||
|
||||
if (entity.tags) curatedEntity.tags = entity.tags.map(({ tag }) => tag);
|
||||
if (entity.sceneTags) curatedEntity.sceneTags = entity.sceneTags;
|
||||
|
||||
if (entity.children) {
|
||||
if (entity.children.nodes) {
|
||||
|
||||
@@ -41,6 +41,11 @@ function initEntitiesActions(store, router) {
|
||||
slug
|
||||
}
|
||||
}
|
||||
sceneTags {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
children: childEntitiesConnection(
|
||||
orderBy: [PRIORITY_DESC, NAME_ASC],
|
||||
filter: {
|
||||
@@ -96,7 +101,7 @@ function initEntitiesActions(store, router) {
|
||||
}
|
||||
}
|
||||
]
|
||||
date: {
|
||||
effectiveDate: {
|
||||
lessThan: $before,
|
||||
greaterThan: $after
|
||||
}
|
||||
|
||||
@@ -367,6 +367,7 @@ const releaseFields = `
|
||||
date
|
||||
datePrecision
|
||||
slug
|
||||
qualities
|
||||
shootId
|
||||
productionDate
|
||||
comment
|
||||
@@ -442,6 +443,29 @@ const releasesFragment = `
|
||||
}
|
||||
`;
|
||||
|
||||
const mediaFields = `
|
||||
id
|
||||
index
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
isS3
|
||||
comment
|
||||
sfw: sfwMedia {
|
||||
id
|
||||
thumbnail
|
||||
lazy
|
||||
path
|
||||
comment
|
||||
}
|
||||
`;
|
||||
|
||||
const mediaFragment = `
|
||||
media {
|
||||
${mediaFields}
|
||||
}
|
||||
`;
|
||||
|
||||
const releaseFragment = `
|
||||
release(id: $releaseId) {
|
||||
id
|
||||
@@ -452,6 +476,7 @@ const releaseFragment = `
|
||||
duration
|
||||
createdAt
|
||||
shootId
|
||||
qualities
|
||||
productionDate
|
||||
createdBatchId
|
||||
productionLocation
|
||||
@@ -536,6 +561,19 @@ const releaseFragment = `
|
||||
}
|
||||
}
|
||||
}
|
||||
series: seriesScenesBySceneId {
|
||||
serie {
|
||||
id
|
||||
title
|
||||
slug
|
||||
covers: seriesCoversBySerieId(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
${mediaFragment}
|
||||
}
|
||||
poster: seriesPosterBySerieId {
|
||||
${mediaFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFavorited
|
||||
isStashed(includeFavorites: false)
|
||||
stashes: stashesScenesBySceneId(
|
||||
@@ -624,6 +662,8 @@ export {
|
||||
actorFields,
|
||||
actorStashesFields,
|
||||
campaignsFragment,
|
||||
mediaFields,
|
||||
mediaFragment,
|
||||
movieFields,
|
||||
releaseActorsFragment,
|
||||
releaseFields,
|
||||
|
||||
@@ -7,22 +7,22 @@ const dateRanges = {
|
||||
latest: () => ({
|
||||
after: '1900-01-01',
|
||||
before: dayjs.utc().toDate(),
|
||||
orderBy: ['DATE_DESC'],
|
||||
orderBy: ['EFFECTIVE_DATE_DESC'],
|
||||
}),
|
||||
upcoming: () => ({
|
||||
after: dayjs.utc().toDate(),
|
||||
before: '2100-01-01',
|
||||
orderBy: ['DATE_DESC'],
|
||||
orderBy: ['EFFECTIVE_DATE_DESC'],
|
||||
}),
|
||||
new: () => ({
|
||||
after: '1900-01-01 00:00:00',
|
||||
before: '2100-01-01',
|
||||
orderBy: ['CREATED_AT_DESC', 'DATE_ASC'],
|
||||
orderBy: ['CREATED_AT_DESC', 'EFFECTIVE_DATE_ASC'],
|
||||
}),
|
||||
all: () => ({
|
||||
after: '1900-01-01',
|
||||
before: '2100-01-01',
|
||||
orderBy: ['DATE_DESC'],
|
||||
orderBy: ['EFFECTIVE_DATE_DESC'],
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
releaseFragment,
|
||||
releaseFields,
|
||||
movieFields,
|
||||
mediaFragment,
|
||||
mediaFields,
|
||||
} from '../fragments';
|
||||
import { curateRelease } from '../curate';
|
||||
import getDateRange from '../get-date-range';
|
||||
@@ -141,16 +143,16 @@ function initReleasesActions(store, router) {
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchMovieById({ _commit }, movieId) {
|
||||
async function fetchCollectionById({ _commit }, movieId, type = 'movie') {
|
||||
// const release = await get(`/releases/${releaseId}`);
|
||||
|
||||
const { movie } = await graphql(`
|
||||
const result = await graphql(`
|
||||
query Movie(
|
||||
$movieId: Int!
|
||||
$hasAuth: Boolean!
|
||||
$userId: Int
|
||||
) {
|
||||
movie(id: $movieId) {
|
||||
${type}(id: $movieId) {
|
||||
id
|
||||
title
|
||||
description
|
||||
@@ -182,7 +184,7 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
poster: moviesPoster {
|
||||
poster: ${type === 'series' ? 'seriesPosterBySerieId' : 'moviesPoster'} {
|
||||
media {
|
||||
id
|
||||
path
|
||||
@@ -195,7 +197,7 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
covers: moviesCovers(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
covers: ${type === 'series' ? 'seriesCoversBySerieId' : 'moviesCovers'}(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
media {
|
||||
id
|
||||
path
|
||||
@@ -208,14 +210,14 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
trailer: moviesTrailer {
|
||||
trailer: ${type === 'series' ? 'seriesTrailerBySerieId' : 'moviesTrailer'} {
|
||||
media {
|
||||
id
|
||||
path
|
||||
isS3
|
||||
}
|
||||
}
|
||||
scenes: moviesScenes {
|
||||
scenes: ${type === 'series' ? 'seriesScenesBySerieId' : 'moviesScenes'} {
|
||||
scene {
|
||||
${releaseFields}
|
||||
}
|
||||
@@ -225,25 +227,11 @@ function initReleasesActions(store, router) {
|
||||
slug
|
||||
name
|
||||
}
|
||||
photos {
|
||||
id
|
||||
index
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
width
|
||||
height
|
||||
thumbnailWidth
|
||||
thumbnailHeight
|
||||
isS3
|
||||
comment
|
||||
sfw: sfwMedia {
|
||||
id
|
||||
thumbnail
|
||||
lazy
|
||||
path
|
||||
comment
|
||||
}
|
||||
photos: ${type === 'series' ? 'seriesPhotosBySerieId' : 'moviesPhotos'} {
|
||||
${mediaFragment}
|
||||
}
|
||||
scenesPhotos {
|
||||
${mediaFields}
|
||||
}
|
||||
entity {
|
||||
id
|
||||
@@ -259,7 +247,7 @@ function initReleasesActions(store, router) {
|
||||
hasLogo
|
||||
}
|
||||
}
|
||||
stashes: stashesMovies(
|
||||
stashes: ${type === 'series' ? 'stashesSeriesBySerieId' : 'stashesMovies'}(
|
||||
filter: {
|
||||
stash: {
|
||||
userId: {
|
||||
@@ -283,12 +271,20 @@ function initReleasesActions(store, router) {
|
||||
userId: store.state.auth.user?.id,
|
||||
});
|
||||
|
||||
if (!movie) {
|
||||
if (!result[type]) {
|
||||
router.replace('/not-found');
|
||||
return null;
|
||||
}
|
||||
|
||||
return curateRelease(movie);
|
||||
return curateRelease(result[type]);
|
||||
}
|
||||
|
||||
async function fetchMovieById(context, movieId) {
|
||||
return fetchCollectionById(context, movieId, 'movie');
|
||||
}
|
||||
|
||||
async function fetchSerieById(context, serieId) {
|
||||
return fetchCollectionById(context, serieId, 'series');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -296,6 +292,7 @@ function initReleasesActions(store, router) {
|
||||
fetchReleaseById,
|
||||
fetchMovies,
|
||||
fetchMovieById,
|
||||
fetchSerieById,
|
||||
searchMovies,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ const routes = [
|
||||
component: Release,
|
||||
name: 'movie',
|
||||
},
|
||||
{
|
||||
path: '/serie/:releaseId/:releaseSlug?',
|
||||
component: Release,
|
||||
name: 'serie',
|
||||
},
|
||||
{
|
||||
path: '/actor/:actorId/:actorSlug',
|
||||
name: 'actor',
|
||||
|
||||
@@ -89,6 +89,10 @@ module.exports = {
|
||||
'uksinners',
|
||||
// mindgeek
|
||||
'pornhub',
|
||||
// insex
|
||||
'paintoy',
|
||||
'aganmedon',
|
||||
'sensualpain',
|
||||
],
|
||||
networks: [
|
||||
// dummy network for testing
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE OR REPLACE FUNCTION entities_scene_total(entity entities) RETURNS integer AS $$
|
||||
CREATE OR REPLACE FUNCTION entities_scene_total(entity entities) RETURNS bigint AS $$
|
||||
SELECT COUNT(id)
|
||||
FROM releases
|
||||
WHERE releases.entity_id = entity.id;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE VIEW movies_tagged AS
|
||||
SELECT * FROM movies;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP VIEW IF EXISTS movies_tagged;
|
||||
`);
|
||||
23
migrations/20220227215315_entity_filters.js
Normal file
@@ -0,0 +1,23 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE FUNCTION entities_scene_tags(entity entities, selectable_tags text[]) RETURNS SETOF tags AS $$
|
||||
SELECT tags.*
|
||||
FROM releases
|
||||
LEFT JOIN
|
||||
releases_tags ON releases_tags.release_id = releases.id
|
||||
LEFT JOIN
|
||||
tags ON tags.id = releases_tags.tag_id
|
||||
WHERE
|
||||
releases.entity_id = entity.id
|
||||
AND
|
||||
CASE WHEN array_length(selectable_tags, 1) IS NOT NULL
|
||||
THEN tags.slug = ANY(selectable_tags)
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY tags.id
|
||||
ORDER BY tags.name;
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP FUNCTION IF EXISTS entities_scene_tags;
|
||||
`);
|
||||
215
migrations/20220304233846_series.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const config = require('config');
|
||||
|
||||
exports.up = async (knex) => Promise.resolve()
|
||||
.then(() => knex.schema.createTable('series', (table) => {
|
||||
table.increments('id', 16);
|
||||
|
||||
table.integer('entity_id', 12)
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.notNullable();
|
||||
|
||||
table.integer('studio_id', 12)
|
||||
.references('id')
|
||||
.inTable('entities');
|
||||
|
||||
table.text('entry_id');
|
||||
table.unique(['entity_id', 'entry_id']);
|
||||
|
||||
table.text('url', 1000);
|
||||
table.text('title');
|
||||
table.text('slug');
|
||||
|
||||
table.timestamp('date');
|
||||
table.index('date');
|
||||
|
||||
table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'])
|
||||
.defaultTo('day');
|
||||
|
||||
table.text('description');
|
||||
|
||||
table.boolean('deep');
|
||||
table.text('deep_url', 1000);
|
||||
|
||||
table.text('comment');
|
||||
|
||||
table.integer('created_batch_id', 12)
|
||||
.references('id')
|
||||
.inTable('batches')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('updated_batch_id', 12)
|
||||
.references('id')
|
||||
.inTable('batches')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.datetime('created_at')
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_scenes', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('scene_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('releases')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique(['serie_id', 'scene_id']);
|
||||
|
||||
table.datetime('created_at')
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_trailers', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.unique()
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_posters', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique('serie_id');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_covers', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['serie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_search', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('stashes_series', (table) => {
|
||||
table.integer('stash_id')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('stashes')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('serie_id')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique(['stash_id', 'serie_id']);
|
||||
|
||||
table.string('comment');
|
||||
|
||||
table.datetime('created_at')
|
||||
.notNullable()
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.raw(`
|
||||
ALTER TABLE series_search ADD COLUMN document tsvector;
|
||||
|
||||
CREATE UNIQUE INDEX series_search_unique ON series_search (serie_id);
|
||||
CREATE INDEX series_search_index ON series_search USING GIN (document);
|
||||
|
||||
CREATE FUNCTION series_actors(serie series) RETURNS SETOF actors AS $$
|
||||
SELECT actors.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
LEFT JOIN
|
||||
releases_actors ON releases_actors.release_id = releases.id
|
||||
LEFT JOIN
|
||||
actors ON actors.id = releases_actors.actor_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
AND actors.id IS NOT NULL
|
||||
GROUP BY actors.id
|
||||
ORDER BY actors.name, actors.gender
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
CREATE FUNCTION series_tags(serie series) RETURNS SETOF tags AS $$
|
||||
SELECT tags.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
LEFT JOIN
|
||||
releases_tags ON releases_tags.release_id = releases.id
|
||||
LEFT JOIN
|
||||
tags ON tags.id = releases_tags.tag_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
AND tags.id IS NOT NULL
|
||||
GROUP BY tags.id
|
||||
ORDER BY tags.priority DESC
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
CREATE FUNCTION series_photos(serie series) RETURNS SETOF media AS $$
|
||||
SELECT media.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
INNER JOIN
|
||||
releases_photos ON releases_photos.release_id = releases.id
|
||||
LEFT JOIN
|
||||
media ON media.id = releases_photos.media_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
GROUP BY media.id
|
||||
ORDER BY media.index ASC
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||
|
||||
ALTER TABLE stashes_series ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY stashes_policy ON stashes_series
|
||||
USING (EXISTS (
|
||||
SELECT *
|
||||
FROM stashes
|
||||
WHERE stashes.id = stashes_series.stash_id
|
||||
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||
));
|
||||
`, {
|
||||
visitor: knex.raw(config.database.query.user),
|
||||
}));
|
||||
|
||||
exports.down = async (knex) => Promise.resolve()
|
||||
.then(() => knex.raw(`
|
||||
DROP FUNCTION IF EXISTS series_actors;
|
||||
DROP FUNCTION IF EXISTS series_tags;
|
||||
DROP FUNCTION IF EXISTS series_photos;
|
||||
|
||||
DROP TABLE IF EXISTS stashes_series CASCADE;
|
||||
DROP TABLE IF EXISTS series_scenes CASCADE;
|
||||
DROP TABLE IF EXISTS series_trailers CASCADE;
|
||||
DROP TABLE IF EXISTS series_posters CASCADE;
|
||||
DROP TABLE IF EXISTS series_covers CASCADE;
|
||||
DROP TABLE IF EXISTS series_search CASCADE;
|
||||
DROP TABLE IF EXISTS series CASCADE;
|
||||
`));
|
||||
49
migrations/20220327230125_movie_photos.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const config = require('config');
|
||||
|
||||
exports.up = async (knex) => Promise.resolve()
|
||||
.then(() => knex.raw(`
|
||||
ALTER FUNCTION movies_photos(movie movies) RENAME TO movies_scenes_photos;
|
||||
ALTER FUNCTION series_photos(serie series) RENAME TO series_scenes_photos;
|
||||
`))
|
||||
.then(() => knex.schema.createTable('movies_photos', (table) => {
|
||||
table.integer('movie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('movies')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['movie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_photos', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['serie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.raw(`
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||
`, {
|
||||
visitor: knex.raw(config.database.query.user),
|
||||
}));
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP TABLE IF EXISTS movies_photos CASCADE;
|
||||
DROP TABLE IF EXISTS series_photos CASCADE;
|
||||
|
||||
ALTER FUNCTION movies_scenes_photos(movie movies) RENAME TO movies_photos;
|
||||
ALTER FUNCTION series_scenes_photos(serie series) RENAME TO series_photos;
|
||||
`);
|
||||
25
migrations/20220330230122_stats.js
Normal file
@@ -0,0 +1,25 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE MATERIALIZED VIEW entities_stats
|
||||
AS
|
||||
WITH RECURSIVE relations AS (
|
||||
SELECT entities.id, entities.parent_id, count(releases.id) AS releases_count, count(releases.id) AS total_count
|
||||
FROM entities
|
||||
LEFT JOIN releases ON releases.entity_id = entities.id
|
||||
GROUP BY entities.id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT entities.id AS entity_id, count(releases.id) AS releases_count, count(releases.id) + relations.total_count AS total_count
|
||||
FROM entities
|
||||
INNER JOIN relations ON relations.id = entities.parent_id
|
||||
LEFT JOIN releases ON releases.entity_id = entities.id
|
||||
GROUP BY entities.id
|
||||
)
|
||||
|
||||
SELECT relations.id AS entity_id, relations.releases_count
|
||||
FROM relations;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP MATERIALIZED VIEW entities_stats;
|
||||
`);
|
||||
7
migrations/20220331135618_qualities.js
Normal file
@@ -0,0 +1,7 @@
|
||||
exports.up = async (knex) => knex.schema.alterTable('releases', (table) => {
|
||||
table.specificType('qualities', 'text[]');
|
||||
});
|
||||
|
||||
exports.down = async (knex) => knex.schema.alterTable('releases', (table) => {
|
||||
table.dropColumn('qualities');
|
||||
});
|
||||
12
migrations/_20220330230122_stats.js
Normal file
@@ -0,0 +1,12 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE MATERIALIZED VIEW entities_stats
|
||||
AS
|
||||
SELECT entities.id AS entity_id, count(releases.id) AS releases_count
|
||||
FROM entities
|
||||
LEFT JOIN releases ON releases.entity_id = entities.id
|
||||
GROUP BY entities.id;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP MATERIALIZED VIEW entities_stats;
|
||||
`);
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.213.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.213.5",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@casl/ability": "^5.2.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.213.5",
|
||||
"description": "All the latest porn releases in one place",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
|
||||
BIN
public/img/logos/bang/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
public/img/logos/bang/lazy/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/logos/bang/lazy/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/img/logos/bang/lazy/favicon_light.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
public/img/logos/bang/thumbs/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/logos/bang/thumbs/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/img/logos/bang/thumbs/favicon_light.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
public/img/logos/pervcity/dpdiva.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
public/img/logos/pervcity/lazy/dpdiva.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/lazy/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/lazy/favicon_light.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
BIN
public/img/logos/pervcity/thumbs/dpdiva.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/thumbs/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/thumbs/favicon_light.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
public/img/tags/triple-penetration/lazy/lucky_bee_analvids.jpeg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/img/tags/triple-penetration/lucky_bee_analvids.jpeg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -225,6 +225,7 @@ const networks = [
|
||||
description: 'DigitalPlayground.com is the leader in high quality adult blockbuster movies and award winning sex parodies that feature the most exclusive pornstars online! Adult Film Database of adult movies.',
|
||||
parameters: {
|
||||
actorPath: 'modelprofile',
|
||||
forceDeep: true, // Digital Playground has movie and series information not available in the latest updates API
|
||||
},
|
||||
parent: 'mindgeek',
|
||||
},
|
||||
@@ -411,6 +412,9 @@ const networks = [
|
||||
url: 'https://www.milehighmedia.com',
|
||||
description: 'MileHighMedia.com is the only niche porn network you need! Watch lesbian sex, hardcore fucking and family porn stories with the hottest teens & MILFs!',
|
||||
parent: 'mindgeek',
|
||||
parameters: {
|
||||
forceDeep: true, // Mile High Media has movie and series information not available in the latest updates API
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'mofos',
|
||||
|
||||
@@ -810,6 +810,13 @@ const sites = [
|
||||
parameters: { siteId: 3261 },
|
||||
parent: 'bang',
|
||||
},
|
||||
{
|
||||
name: 'Bang! Podcast',
|
||||
slug: 'bangpodcast',
|
||||
url: 'https://www.bang.com/videos?in=bang!%20podcast',
|
||||
parameters: { siteId: 6305 },
|
||||
parent: 'bang',
|
||||
},
|
||||
// BANGBROS
|
||||
{
|
||||
name: 'Ass Parade',
|
||||
@@ -2646,7 +2653,7 @@ const sites = [
|
||||
{
|
||||
slug: 'digitalplayground',
|
||||
name: 'Digital Playground',
|
||||
url: 'https://www.digitalplayground.com/scenes',
|
||||
url: 'https://www.digitalplayground.com',
|
||||
description: '',
|
||||
parameters: { extract: true },
|
||||
parent: 'digitalplayground',
|
||||
@@ -2680,6 +2687,13 @@ const sites = [
|
||||
description: '',
|
||||
parent: 'digitalplayground',
|
||||
},
|
||||
{
|
||||
slug: 'dpstarsexchallenges',
|
||||
name: 'DP Star Sex Challenges',
|
||||
url: 'https://www.digitalplayground.com/scenes?site=210',
|
||||
parent: 'digitalplayground',
|
||||
hasLogo: false,
|
||||
},
|
||||
{
|
||||
slug: 'blockbuster',
|
||||
name: 'Blockbuster',
|
||||
@@ -4205,7 +4219,6 @@ const sites = [
|
||||
tags: ['bdsm'],
|
||||
parent: 'insex',
|
||||
parameters: {
|
||||
scraper: 'alt',
|
||||
latest: 'https://www.sexuallybroken.com/sb',
|
||||
},
|
||||
},
|
||||
@@ -4216,13 +4229,20 @@ const sites = [
|
||||
url: 'https://www.infernalrestraints.com',
|
||||
tags: ['bdsm'],
|
||||
parent: 'insex',
|
||||
parameters: {
|
||||
latest: 'https://www.infernalrestraints.com/ir',
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'hardtied',
|
||||
name: 'Hardtied',
|
||||
alias: ['ht'],
|
||||
url: 'https://www.hardtied.com',
|
||||
tags: ['bdsm'],
|
||||
parent: 'insex',
|
||||
parameters: {
|
||||
latest: 'https://www.hardtied.com/ht',
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'realtimebondage',
|
||||
@@ -4231,6 +4251,9 @@ const sites = [
|
||||
url: 'https://www.realtimebondage.com',
|
||||
tags: ['bdsm', 'live'],
|
||||
parent: 'insex',
|
||||
parameters: {
|
||||
latest: 'https://www.realtimebondage.com/rtb',
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'topgrl',
|
||||
@@ -4240,7 +4263,6 @@ const sites = [
|
||||
tags: ['bdsm', 'femdom'],
|
||||
parent: 'insex',
|
||||
parameters: {
|
||||
scraper: 'alt',
|
||||
latest: 'https://www.topgrl.com/tg',
|
||||
},
|
||||
},
|
||||
@@ -5264,7 +5286,7 @@ const sites = [
|
||||
{
|
||||
slug: 'bigdicksatschool',
|
||||
name: 'Big Dicks At School',
|
||||
url: 'https://www.bigdicksatschool.com',
|
||||
url: 'https://www.men.com/scenes?site=252',
|
||||
description: '',
|
||||
parameters: { siteId: 252 },
|
||||
tags: ['gay'],
|
||||
@@ -5273,7 +5295,7 @@ const sites = [
|
||||
{
|
||||
slug: 'drillmyhole',
|
||||
name: 'Drill My Hole',
|
||||
url: 'https://www.drillmyhole.com',
|
||||
url: 'https://www.men.com/scenes?site=253',
|
||||
description: '',
|
||||
parameters: { siteId: 253 },
|
||||
tags: ['gay'],
|
||||
@@ -5282,7 +5304,7 @@ const sites = [
|
||||
{
|
||||
slug: 'str8togay',
|
||||
name: 'Str8 to Gay',
|
||||
url: 'https://www.str8togay.com',
|
||||
url: 'https://www.men.com/scenes?site=254',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 254 },
|
||||
parent: 'men',
|
||||
@@ -5290,7 +5312,7 @@ const sites = [
|
||||
{
|
||||
slug: 'thegayoffice',
|
||||
name: 'The Gay Office',
|
||||
url: 'https://www.thegayoffice.com',
|
||||
url: 'https://www.men.com/scenes?site=255',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 255 },
|
||||
parent: 'men',
|
||||
@@ -5298,7 +5320,7 @@ const sites = [
|
||||
{
|
||||
slug: 'jizzorgy',
|
||||
name: 'Jizz Orgy',
|
||||
url: 'https://www.jizzorgy.com',
|
||||
url: 'https://www.men.com/scenes?site=256',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 256 },
|
||||
parent: 'men',
|
||||
@@ -5306,7 +5328,7 @@ const sites = [
|
||||
{
|
||||
slug: 'menofuk',
|
||||
name: 'Men of UK',
|
||||
url: 'https://www.menofuk.com',
|
||||
url: 'https://www.men.com/scenes?site=258',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 258 },
|
||||
parent: 'men',
|
||||
@@ -5314,7 +5336,7 @@ const sites = [
|
||||
{
|
||||
slug: 'toptobottom',
|
||||
name: 'Top to Bottom',
|
||||
url: 'https://www.toptobottom.com',
|
||||
url: 'https://www.men.com/scenes?site=259',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 259 },
|
||||
parent: 'men',
|
||||
@@ -5322,7 +5344,7 @@ const sites = [
|
||||
{
|
||||
slug: 'godsofmen',
|
||||
name: 'Gods of Men',
|
||||
url: 'https://www.godsofmen.com',
|
||||
url: 'https://www.men.com/scenes?site=260',
|
||||
tags: ['gay'],
|
||||
parameters: { siteId: 260 },
|
||||
parent: 'men',
|
||||
@@ -5413,7 +5435,10 @@ const sites = [
|
||||
name: 'Doghouse Digital',
|
||||
url: 'https://www.doghousedigital.com',
|
||||
alias: ['dhd'],
|
||||
parameters: { siteId: 321 },
|
||||
parameters: {
|
||||
siteId: 321,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
{
|
||||
@@ -5429,7 +5454,10 @@ const sites = [
|
||||
name: 'Reality Junkies',
|
||||
url: 'https://www.realityjunkies.com',
|
||||
alias: ['rj'],
|
||||
parameters: { siteId: 324 },
|
||||
parameters: {
|
||||
siteId: 324,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
{
|
||||
@@ -5437,7 +5465,10 @@ const sites = [
|
||||
name: 'Sweetheart Video',
|
||||
url: 'https://www.sweetheartvideo.com',
|
||||
alias: ['shv'],
|
||||
parameters: { siteId: 325 },
|
||||
parameters: {
|
||||
siteId: 325,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
{
|
||||
@@ -5445,7 +5476,10 @@ const sites = [
|
||||
name: 'Sweet Sinner',
|
||||
url: 'https://www.sweetsinner.com',
|
||||
alias: ['ss'],
|
||||
parameters: { siteId: 326 },
|
||||
parameters: {
|
||||
siteId: 326,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
{
|
||||
@@ -5454,7 +5488,10 @@ const sites = [
|
||||
alias: ['fs'],
|
||||
tags: ['family'],
|
||||
url: 'https://www.familysinners.com',
|
||||
parameters: { siteId: 317 },
|
||||
parameters: {
|
||||
siteId: 317,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
{
|
||||
@@ -5463,7 +5500,10 @@ const sites = [
|
||||
url: 'https://www.iconmale.com',
|
||||
alias: ['im'],
|
||||
tags: ['gay'],
|
||||
parameters: { native: true, siteId: 328 },
|
||||
parameters: {
|
||||
siteId: 328,
|
||||
native: true,
|
||||
},
|
||||
parent: 'milehighmedia',
|
||||
},
|
||||
// MOFOS
|
||||
@@ -6877,6 +6917,13 @@ const sites = [
|
||||
tourId: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'dpdiva',
|
||||
name: 'DP Diva',
|
||||
url: 'http://dpdiva.com',
|
||||
parent: 'pervcity',
|
||||
tags: ['dp', 'anal'],
|
||||
},
|
||||
// PIERRE WOODMAN
|
||||
{
|
||||
slug: 'woodmancastingx',
|
||||
@@ -8579,7 +8626,10 @@ const sites = [
|
||||
name: 'Dane Jones',
|
||||
alias: ['dnj'],
|
||||
url: 'https://www.danejones.com/',
|
||||
parameters: { siteId: 290 },
|
||||
parameters: {
|
||||
siteId: 290,
|
||||
native: true,
|
||||
},
|
||||
parent: 'sexyhub',
|
||||
},
|
||||
{
|
||||
@@ -8587,7 +8637,10 @@ const sites = [
|
||||
name: 'Lesbea',
|
||||
alias: ['lsb'],
|
||||
url: 'https://www.lesbea.com',
|
||||
parameters: { siteId: 291 },
|
||||
parameters: {
|
||||
siteId: 291,
|
||||
native: true,
|
||||
},
|
||||
tags: ['lesbian'],
|
||||
parent: 'sexyhub',
|
||||
},
|
||||
|
||||
@@ -727,8 +727,6 @@ const tagMedia = [
|
||||
['da-tp', 7, 'Polly Petrova in YE069', 'analvids'],
|
||||
['da-tp', 5, 'Venera Maxima in GIO1287'],
|
||||
['da-tp', 6, 'Adriana Chechik in "Gangbang Me"', 'hardx'],
|
||||
['da-tp', 0, 'Natasha Teen in SZ2164'],
|
||||
['da-tp', 1, 'Francys Belle in SZ1702', 'analvids'],
|
||||
['dap', 7, 'Adriana Chechik in "DP Masters 6"', 'julesjordan'],
|
||||
['dap', 10, 'Kira Noir', 'hardx'],
|
||||
['dap', 'emily_pink_legalporno', 'Emily Pink', 'analvids'],
|
||||
@@ -1049,6 +1047,7 @@ const tagMedia = [
|
||||
['toy-dp', 0, 'Marley Brinx, Ivy Lebelle and Lyra Law in "Marley Brinx First GGDP"', 'lesbianx'],
|
||||
['toys', 1, 'Chloe Lamour in "Curives In All The Right Places"', 'wetandpuffy'],
|
||||
['toys', 'shawna_lenee_sunrisekings', 'Shawna Lenee', 'sunrisekings'],
|
||||
['triple-penetration', 'lucky_bee_analvids', 'Lucky Bee', 'analvids'],
|
||||
['triple-penetration', 'angela_white_julesjordan', 'Angela White in "Her Biggest Gangbang Ever"', 'julesjordan'],
|
||||
['triple-penetration', 'ria_sunn_legalporno', 'Ria Sunn in SZ2082', 'analvids'],
|
||||
['tvp', 'september_reign_wefuckblackgirls', 'September Reign in "Second Appearance"', 'wefuckblackgirls'],
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"prefer-destructuring": "off",
|
||||
"template-curly-spacing": "off",
|
||||
"object-curly-newline": "off",
|
||||
"max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}],
|
||||
"max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ const scrapers = require('./scrapers/scrapers').actors;
|
||||
const argv = require('./argv');
|
||||
const include = require('./utils/argv-include')(argv);
|
||||
const bulkInsert = require('./utils/bulk-insert');
|
||||
const chunk = require('./utils/chunk');
|
||||
const logger = require('./logger')(__filename);
|
||||
|
||||
const { toBaseReleases } = require('./deep');
|
||||
@@ -1048,33 +1049,42 @@ async function flushProfiles(actorIdsOrNames) {
|
||||
logger.info(`Removed ${deleteCount} profiles`);
|
||||
}
|
||||
|
||||
async function deleteActors(actorIdsOrNames) {
|
||||
const actors = await knex('actors')
|
||||
.whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number'))
|
||||
.orWhere((builder) => {
|
||||
builder
|
||||
.whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string'))
|
||||
.whereNull('entity_id');
|
||||
});
|
||||
async function deleteActors(allActorIdsOrNames) {
|
||||
const deleteCounts = await Promise.map(chunk(allActorIdsOrNames), async (actorIdsOrNames) => {
|
||||
const actors = await knex('actors')
|
||||
.whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number'))
|
||||
.orWhere((builder) => {
|
||||
builder
|
||||
.whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string'))
|
||||
.whereNull('entity_id');
|
||||
});
|
||||
|
||||
const actorIds = actors.map((actor) => actor.id);
|
||||
const actorIds = actors.map((actor) => actor.id);
|
||||
|
||||
const sceneIds = await knex('releases_actors')
|
||||
.select('releases.id')
|
||||
.whereIn('actor_id', actorIds)
|
||||
.leftJoin('releases', 'releases.id', 'releases_actors.release_id')
|
||||
.pluck('id');
|
||||
const sceneIds = await knex('releases_actors')
|
||||
.select('releases.id')
|
||||
.whereIn('actor_id', actorIds)
|
||||
.leftJoin('releases', 'releases.id', 'releases_actors.release_id')
|
||||
.pluck('id');
|
||||
|
||||
const [deletedScenesCount, deletedActorsCount] = await Promise.all([
|
||||
deleteScenes(sceneIds),
|
||||
knex('actors')
|
||||
.whereIn('id', actorIds)
|
||||
.delete(),
|
||||
]);
|
||||
const [deletedScenesCount, deletedActorsCount] = await Promise.all([
|
||||
deleteScenes(sceneIds),
|
||||
knex('actors')
|
||||
.whereIn('id', actorIds)
|
||||
.delete(),
|
||||
]);
|
||||
|
||||
return { deletedScenesCount, deletedActorsCount };
|
||||
}, { concurrency: 10 });
|
||||
|
||||
const deletedActorsCount = deleteCounts.reduce((acc, count) => acc + count.deletedActorsCount, 0);
|
||||
const deletedScenesCount = deleteCounts.reduce((acc, count) => acc + count.deletedScenesCount, 0);
|
||||
|
||||
await flushOrphanedMedia();
|
||||
|
||||
logger.info(`Removed ${deletedActorsCount} actors with ${deletedScenesCount} scenes`);
|
||||
|
||||
return deletedActorsCount;
|
||||
}
|
||||
|
||||
async function flushActors() {
|
||||
|
||||
@@ -194,6 +194,7 @@ const { argv } = yargs
|
||||
alias: 'pics',
|
||||
})
|
||||
.option('videos', {
|
||||
alias: 'video',
|
||||
describe: 'Include any trailers or teasers',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
@@ -238,6 +239,7 @@ const { argv } = yargs
|
||||
default: false,
|
||||
})
|
||||
.option('level', {
|
||||
alias: 'log-level',
|
||||
describe: 'Log level',
|
||||
type: 'string',
|
||||
default: process.env.NODE_ENV === 'development' ? 'silly' : 'info',
|
||||
|
||||
@@ -6,7 +6,7 @@ const inquirer = require('inquirer');
|
||||
const logger = require('./logger')(__filename);
|
||||
const argv = require('./argv');
|
||||
const knex = require('./knex');
|
||||
const { deleteScenes, deleteMovies } = require('./releases');
|
||||
const { deleteScenes, deleteMovies, deleteSeries } = require('./releases');
|
||||
const { flushOrphanedMedia } = require('./media');
|
||||
const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve');
|
||||
|
||||
@@ -359,29 +359,39 @@ async function flushEntities(networkSlugs = [], channelSlugs = []) {
|
||||
.leftJoin('movies', 'movies.entity_id', 'selected_entities.id')
|
||||
.pluck('movies.id');
|
||||
|
||||
if (sceneIds.length === 0 && movieIds.length === 0) {
|
||||
logger.info(`No scenes or movies found to remove for ${entitySlugs}`);
|
||||
const serieIds = await entityQuery
|
||||
.clone()
|
||||
.select('series.id')
|
||||
.distinct('series.id')
|
||||
.whereNotNull('series.id')
|
||||
.from('selected_entities')
|
||||
.leftJoin('series', 'series.entity_id', 'selected_entities.id')
|
||||
.pluck('series.id');
|
||||
|
||||
if (sceneIds.length === 0 && movieIds.length === 0 && serieIds.length === 0) {
|
||||
logger.info(`No scenes, movies or series found to remove for ${entitySlugs}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'flushEntities',
|
||||
message: `You are about to remove ${sceneIds.length} scenes and ${movieIds.length} movies for ${entitySlugs}. Are you sure?`,
|
||||
message: `You are about to remove ${sceneIds.length} scenes, ${movieIds.length} movies and ${serieIds.length} series for ${entitySlugs}. Are you sure?`,
|
||||
default: false,
|
||||
}]);
|
||||
|
||||
if (!confirmed.flushEntities) {
|
||||
logger.warn(`Confirmation rejected, not flushing scenes or movies for: ${entitySlugs}`);
|
||||
logger.warn(`Confirmation rejected, not flushing scenes, movies or series for: ${entitySlugs}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const [deletedScenesCount, deletedMoviesCount] = await Promise.all([
|
||||
const [deletedScenesCount, deletedMoviesCount, deletedSeriesCount] = await Promise.all([
|
||||
deleteScenes(sceneIds),
|
||||
deleteMovies(movieIds),
|
||||
deleteSeries(serieIds),
|
||||
]);
|
||||
|
||||
logger.info(`Removed ${deletedScenesCount} scenes and ${deletedMoviesCount} movies for ${entitySlugs}`);
|
||||
logger.info(`Removed ${deletedScenesCount} scenes, ${deletedMoviesCount} movies and ${deletedSeriesCount} series for ${entitySlugs}`);
|
||||
|
||||
await flushOrphanedMedia();
|
||||
}
|
||||
|
||||
53
src/media.js
@@ -21,6 +21,7 @@ const argv = require('./argv');
|
||||
const knex = require('./knex');
|
||||
const http = require('./utils/http');
|
||||
const bulkInsert = require('./utils/bulk-insert');
|
||||
const chunk = require('./utils/chunk');
|
||||
const { get } = require('./utils/qu');
|
||||
|
||||
const pipeline = util.promisify(stream.pipeline);
|
||||
@@ -63,10 +64,10 @@ function sampleMedias(medias, limit = argv.mediaLimit, preferLast = true) {
|
||||
? chunks.slice(0, -1).concat(chunks.slice(-1).reverse())
|
||||
: chunks;
|
||||
|
||||
const groupedMedias = lastPreferredChunks.map((chunk) => {
|
||||
const groupedMedias = lastPreferredChunks.map((mediaChunk) => {
|
||||
// merge chunked medias into single media with grouped fallback priorities,
|
||||
// so the first sources of each media is preferred over all second sources, etc.
|
||||
const sources = chunk
|
||||
const sources = mediaChunk
|
||||
.reduce((accSources, media) => {
|
||||
media.sources.forEach((source, index) => {
|
||||
if (!accSources[index]) {
|
||||
@@ -82,8 +83,8 @@ function sampleMedias(medias, limit = argv.mediaLimit, preferLast = true) {
|
||||
.flat();
|
||||
|
||||
return {
|
||||
id: chunk[0].id,
|
||||
role: chunk[0].role,
|
||||
id: mediaChunk[0].id,
|
||||
role: mediaChunk[0].role,
|
||||
sources,
|
||||
};
|
||||
});
|
||||
@@ -235,22 +236,41 @@ async function findSourceDuplicates(baseMedias) {
|
||||
.filter(Boolean);
|
||||
|
||||
const [existingSourceMedia, existingExtractMedia] = await Promise.all([
|
||||
knex('media').whereIn('source', sourceUrls),
|
||||
knex('media').whereIn('source_page', extractUrls),
|
||||
// my try to check thousands of URLs at once, don't pass all of them to a single query
|
||||
chunk(sourceUrls).reduce(async (chain, sourceUrlsChunk) => {
|
||||
const accUrls = await chain;
|
||||
const existingUrls = await knex('media').whereIn('source', sourceUrlsChunk);
|
||||
|
||||
return [...accUrls, ...existingUrls];
|
||||
}, []),
|
||||
chunk(extractUrls).reduce(async (chain, extractUrlsChunk) => {
|
||||
const accUrls = await chain;
|
||||
const existingUrls = await knex('media').whereIn('source_page', extractUrlsChunk);
|
||||
|
||||
return [...accUrls, ...existingUrls];
|
||||
}, []),
|
||||
]);
|
||||
|
||||
const existingSourceMediaByUrl = itemsByKey(existingSourceMedia, 'source');
|
||||
const existingExtractMediaByUrl = itemsByKey(existingExtractMedia, 'source_page');
|
||||
|
||||
return { existingSourceMediaByUrl, existingExtractMediaByUrl };
|
||||
return {
|
||||
existingSourceMediaByUrl,
|
||||
existingExtractMediaByUrl,
|
||||
};
|
||||
}
|
||||
|
||||
async function findHashDuplicates(medias) {
|
||||
const hashes = medias.map((media) => media.meta?.hash || media.entry?.hash).filter(Boolean);
|
||||
|
||||
const existingHashMediaEntries = await knex('media').whereIn('hash', hashes);
|
||||
const existingHashMediaEntriesByHash = itemsByKey(existingHashMediaEntries, 'hash');
|
||||
const existingHashMediaEntries = await chunk(hashes, 2).reduce(async (chain, hashesChunk) => {
|
||||
const accHashes = await chain;
|
||||
const existingHashes = await knex('media').whereIn('hash', hashesChunk);
|
||||
|
||||
return [...accHashes, ...existingHashes];
|
||||
}, []);
|
||||
|
||||
const existingHashMediaEntriesByHash = itemsByKey(existingHashMediaEntries, 'hash');
|
||||
const uniqueHashMedias = medias.filter((media) => !media.entry && !existingHashMediaEntriesByHash[media.meta?.hash]);
|
||||
|
||||
const { selfDuplicateMedias, selfUniqueMediasByHash } = uniqueHashMedias.reduce((acc, media) => {
|
||||
@@ -600,11 +620,11 @@ async function fetchSource(source, baseMedia) {
|
||||
const hashStream = new stream.PassThrough();
|
||||
let size = 0;
|
||||
|
||||
hashStream.on('data', (chunk) => {
|
||||
size += chunk.length;
|
||||
hashStream.on('data', (streamChunk) => {
|
||||
size += streamChunk.length;
|
||||
|
||||
if (hasherReady) {
|
||||
hasher.write(chunk);
|
||||
hasher.write(streamChunk);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -961,9 +981,12 @@ async function flushOrphanedMedia() {
|
||||
await deleteS3Objects(orphanedMedia.filter((media) => media.is_s3));
|
||||
}
|
||||
|
||||
await fsPromises.rm(path.join(config.media.path, 'temp'), { recursive: true });
|
||||
|
||||
logger.info('Cleared temporary media directory');
|
||||
try {
|
||||
await fsPromises.rm(path.join(config.media.path, 'temp'), { recursive: true });
|
||||
logger.info('Cleared temporary media directory');
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to clear temporary media directory: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||