46 Commits

Author SHA1 Message Date
DebaucheryLibrarian
e202e887f9 1.213.5 2022-04-03 00:49:42 +02:00
DebaucheryLibrarian
574c117ab0 Refactored Dogfart scraper to use qu and return unextracted scenes. 2022-04-03 00:49:39 +02:00
DebaucheryLibrarian
d59a57f311 1.213.4 2022-04-02 00:32:29 +02:00
DebaucheryLibrarian
5e499c3685 Added chunking to media duplicate queries to prevent overloading parameters. Added DP Diva to Perv City (coming soon). 2022-04-02 00:32:23 +02:00
DebaucheryLibrarian
17e5ce71b2 1.213.3 2022-03-31 23:01:56 +02:00
DebaucheryLibrarian
5352186319 Insex not fetching video when not required. 2022-03-31 23:01:54 +02:00
DebaucheryLibrarian
e9ba02d65d 1.213.2 2022-03-31 22:46:56 +02:00
DebaucheryLibrarian
39813d4461 Updated Insex scraper. 2022-03-31 22:46:54 +02:00
DebaucheryLibrarian
829a285a2d 1.213.1 2022-03-31 14:34:12 +02:00
DebaucheryLibrarian
a19a77e165 Optionalized qualities. 2022-03-31 14:34:10 +02:00
DebaucheryLibrarian
122dd3eaee 1.213.0 2022-03-31 14:11:23 +02:00
DebaucheryLibrarian
18b219850e Storing scene qualities. Updated Perv City scraper. 2022-03-31 14:11:13 +02:00
DebaucheryLibrarian
33a327a04b Merge branch 'master' into experimental 2022-03-30 23:00:29 +02:00
DebaucheryLibrarian
a46061e247 1.212.9 2022-03-30 16:11:09 +02:00
DebaucheryLibrarian
94e07ff23d Added Bang! Podcast channel. 2022-03-30 16:11:07 +02:00
DebaucheryLibrarian
4811befcf6 1.212.8 2022-03-30 15:45:51 +02:00
DebaucheryLibrarian
c455f02c66 Updated Men URLs. 2022-03-30 15:45:41 +02:00
DebaucheryLibrarian
efc5620a28 1.212.7 2022-03-30 01:17:56 +02:00
DebaucheryLibrarian
61123fdb6a Added Accept-Language header to MindGeek requests, seems to help with acquiring sessions. 2022-03-30 01:17:54 +02:00
DebaucheryLibrarian
3ec6911d46 1.212.6 2022-03-29 23:24:58 +02:00
DebaucheryLibrarian
2021093645 Marked Dane Jones and Lesbea as native sites. 2022-03-29 23:24:55 +02:00
DebaucheryLibrarian
1c72dc202f 1.212.5 2022-03-28 23:44:44 +02:00
DebaucheryLibrarian
1ef946fa77 Marked Mile High sites as native. 2022-03-28 23:44:42 +02:00
DebaucheryLibrarian
3b6bbc39ff 1.212.4 2022-03-28 20:05:28 +02:00
DebaucheryLibrarian
481c9feada Fixed missing scenes photos breaking album. 2022-03-28 20:05:25 +02:00
DebaucheryLibrarian
953b3e9568 1.212.3 2022-03-28 00:36:05 +02:00
DebaucheryLibrarian
bdd2e68f49 Fixed centering logic in banner. 2022-03-28 00:36:04 +02:00
DebaucheryLibrarian
e4cc349302 1.212.2 2022-03-28 00:32:00 +02:00
DebaucheryLibrarian
6547b93e55 Fixed broken scene photo length check in release banner. 2022-03-28 00:31:59 +02:00
DebaucheryLibrarian
bb9649d23b 1.212.1 2022-03-28 00:22:59 +02:00
DebaucheryLibrarian
9e2eaef9d1 Added dedicated serie photos table, renamed serie scene photo function. Fixed covers and scene photos in banner and album. 2022-03-28 00:22:57 +02:00
DebaucheryLibrarian
1c3ee75d3b 1.212.0 2022-03-27 23:42:06 +02:00
DebaucheryLibrarian
15c9af8057 Added dedicated movie photo table, renamed scene photo function. 2022-03-27 23:42:03 +02:00
DebaucheryLibrarian
295573c1ef 1.211.2 2022-03-27 00:27:29 +01:00
DebaucheryLibrarian
e93e8ace5c Added deep scene force parameter to MindGeek scraper. 2022-03-27 00:27:26 +01:00
DebaucheryLibrarian
43af7ba777 1.211.1 2022-03-26 17:56:24 +01:00
DebaucheryLibrarian
0dad5b0d68 Added series removal utils. 2022-03-26 17:56:22 +01:00
DebaucheryLibrarian
ae9b793318 1.211.0 2022-03-26 17:40:23 +01:00
DebaucheryLibrarian
fd8170f223 Added series. 2022-03-26 17:40:20 +01:00
DebaucheryLibrarian
661b8b716b 1.210.1 2022-03-09 23:26:50 +01:00
DebaucheryLibrarian
5ff076cac3 Added DP Star Sex Challenges to Digital Playground. 2022-03-09 23:26:48 +01:00
DebaucheryLibrarian
41c100ac4e 1.210.0 2022-03-04 23:32:28 +01:00
DebaucheryLibrarian
c6e977f842 Added movie support to MindGeek scraper. 2022-03-04 23:32:09 +01:00
DebaucheryLibrarian
50b7f521b5 1.209.5 2022-02-27 21:15:07 +01:00
DebaucheryLibrarian
f0d0ee3acc Removed redundant scenes path from Digital Playground main channel. 2022-02-27 21:15:05 +01:00
DebaucheryLibrarian
ccb99e278c Added periodic memory logger. 2021-11-20 23:59:15 +01:00
107 changed files with 1149 additions and 448 deletions

View File

@@ -62,7 +62,7 @@ async function setConsent(consent, includeQueer) {
} }
if (includeQueer) { if (includeQueer) {
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter(tag => !['gay', 'bisexual', 'transsexual'].includes(tag))); this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)));
return; return;
} }

View File

@@ -108,6 +108,7 @@
:fetch-releases="fetchEntity" :fetch-releases="fetchEntity"
:items-total="totalCount" :items-total="totalCount"
:items-per-page="limit" :items-per-page="limit"
:available-tags="entity.tags"
/> />
<div class="releases"> <div class="releases">

View File

@@ -102,6 +102,8 @@ export default {
} }
.name { .name {
display: flex;
align-items: center;
color: var(--text-light); color: var(--text-light);
font-size: 1.25rem; font-size: 1.25rem;
font-weight: bold; font-weight: bold;

View File

@@ -28,7 +28,7 @@ export default {
}; };
}, },
beforeMount() { 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> </script>

View File

@@ -11,16 +11,31 @@
class="empty" class="empty"
>No results for "{{ $route.query.query }}"</span> >No results for "{{ $route.query.query }}"</span>
<div <template v-else>
v-else <h2 class="heading">Popular</h2>
class="entity-tiles"
> <div
<Entity class="entity-tiles"
v-for="entity in entities" >
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`" <Entity
:entity="entity" v-for="entity in popularEntities"
/> :key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
</div> :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> </div>
<Footer /> <Footer />
@@ -58,6 +73,45 @@ async function searchEntities() {
this.done = true; 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() { async function mounted() {
this.pageTitle = 'Channels'; this.pageTitle = 'Channels';
@@ -82,6 +136,7 @@ export default {
}, },
computed: { computed: {
channelCount, channelCount,
popularEntities,
}, },
watch: { watch: {
$route: fetchEntities, $route: fetchEntities,
@@ -130,6 +185,10 @@ export default {
font-weight: bold; font-weight: bold;
} }
.heading {
margin: 1rem 0 0 0;
}
@media(max-width: $breakpoint2) { @media(max-width: $breakpoint2) {
.entity-tiles { .entity-tiles {
grid-gap: .5rem; grid-gap: .5rem;

View File

@@ -2,7 +2,7 @@
<div class="media-container"> <div class="media-container">
<div <div
class="media" class="media"
:class="{ center: release.photos.length < 2, preview: !me }" :class="{ center: (release.photos?.length || 0) + (release.scenesPhotos?.length || 0) < 2, preview: !me }"
> >
<div <div
v-if="release.trailer || release.teaser" v-if="release.trailer || release.teaser"
@@ -71,24 +71,28 @@
</span> </span>
</div> </div>
<template v-if="release.covers && release.covers.length > 0"> <template v-if="release.covers?.length > 0">
<a <div
v-for="cover in release.covers" v-for="cover in release.covers"
:key="`cover-${cover.id}`" :key="`cover-${cover.id}`"
:href="getPath(cover)" class="item-container"
target="_blank"
rel="noopener noreferrer"
> >
<img <a
:src="getPath(cover, 'thumbnail')" :href="getPath(cover)"
:style="{ 'background-image': getBgPath(cover, 'lazy') }" target="_blank"
:width="cover.thumbnailWidth" rel="noopener noreferrer"
:height="cover.thumbnailHeight"
class="item cover"
loading="lazy"
@load="$emit('load', $event)"
> >
</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> </template>
<div <div
@@ -164,8 +168,8 @@ function poster() {
function photos() { function photos() {
const clips = this.release.clips || []; const clips = this.release.clips || [];
const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {}); 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 uniqueClipPosters = Array.from(new Set(clips.map((clip) => clip.poster.id) || [])).map((posterId) => clipPostersById[posterId]);
const photosWithClipPosters = (this.release.photos || []).concat(uniqueClipPosters); const photosWithClipPosters = (this.release.photos || []).concat(this.release.scenesPhotos || []).concat(uniqueClipPosters);
if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) { if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) {
// poster will be on trailer video // poster will be on trailer video

View File

@@ -3,12 +3,6 @@
<div class="content-inner"> <div class="content-inner">
<SearchBar :placeholder="`Search ${totalCount} movies`" /> <SearchBar :placeholder="`Search ${totalCount} movies`" />
<TagFilter
class="filters-filter"
:filter="filter"
:available-tags="availableTags"
/>
<div <div
ref="tiles" ref="tiles"
class="tiles" class="tiles"
@@ -36,7 +30,6 @@
import MovieTile from './movie-tile.vue'; import MovieTile from './movie-tile.vue';
import SearchBar from '../search/bar.vue'; import SearchBar from '../search/bar.vue';
import Pagination from '../pagination/pagination.vue'; import Pagination from '../pagination/pagination.vue';
import TagFilter from '../filters/tag-filter.vue';
async function fetchMovies() { async function fetchMovies() {
if (this.$route.query.query) { if (this.$route.query.query) {
@@ -80,7 +73,6 @@ export default {
MovieTile, MovieTile,
SearchBar, SearchBar,
Pagination, Pagination,
TagFilter,
}, },
data() { data() {
return { return {

View File

@@ -21,14 +21,14 @@
<Details :release="release" /> <Details :release="release" />
<button <button
v-if="release.photos.length > 0" v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0"
class="album-toggle" class="album-toggle"
@click="$router.push({ hash: '#album' })" @click="$router.push({ hash: '#album' })"
><Icon icon="grid3" />View album</button> ><Icon icon="grid3" />View album</button>
<Album <Album
v-if="showAlbum" v-if="showAlbum"
:items="[release.poster, ...release.photos]" :items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]"
:title="release.title" :title="release.title"
:path="config.media.mediaPath" :path="config.media.mediaPath"
@close="$router.replace({ hash: undefined })" @close="$router.replace({ hash: undefined })"
@@ -79,22 +79,23 @@
/> />
<div <div
v-if="release.movies && release.movies.length > 0" v-if="release.movies?.length > 0 || release.series?.length > 0"
class="row" class="row"
> >
<span class="row-label">Part of</span> <span class="row-label">Part of</span>
<div class="movies"> <div class="movies">
<router-link <router-link
v-for="movie in release.movies" v-for="movie in [...release.movies, ...release.series]"
:key="`movie-${movie.id}`" :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" class="movie"
> >
<span class="movie-title">{{ movie.title }}</span> <span class="movie-title">{{ movie.title }}</span>
<img <img
v-if="movie.covers.length > 0" v-if="movie.covers.length > 0 || movie.poster"
:src="getPath(movie.covers[0], 'thumbnail')" :src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
class="movie-cover" class="movie-cover"
> >
</router-link> </router-link>
@@ -202,6 +203,19 @@
</div> </div>
</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 <div
v-if="release.comment" v-if="release.comment"
class="row" class="row"
@@ -243,6 +257,10 @@ async function fetchRelease(scroll = true) {
this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId); 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) { if (scroll && this.$refs.content) {
this.$refs.content.scrollTop = 0; this.$refs.content.scrollTop = 0;
} }
@@ -282,7 +300,7 @@ function pageTitle() {
} }
function showAlbum() { 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 { export default {
@@ -465,6 +483,16 @@ export default {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.quality {
&::after {
content: 'p, ';
}
&:last-child::after {
content: 'p',
}
}
.releases { .releases {
margin: 0 0 .5rem 0; margin: 0 0 .5rem 0;
} }

View File

@@ -87,7 +87,6 @@ const tagSlugsByCategory = {
'titty-fucking', 'titty-fucking',
'fisting', 'fisting',
'anal-fisting', 'anal-fisting',
'fisting-dp',
], ],
group: [ group: [
'mfm', 'mfm',
@@ -108,16 +107,6 @@ const tagSlugsByCategory = {
'bukkake', 'bukkake',
'fake-cum', 'fake-cum',
], ],
toys: [
'toys',
'toy-anal',
'toy-dp',
'double-dildo',
'double-dildo-blowjob',
'double-dildo-kiss',
'double-dildo-anal',
'double-dildo-dp',
],
roleplay: [ roleplay: [
'family', 'family',
'parody', 'parody',
@@ -126,6 +115,15 @@ const tagSlugsByCategory = {
'maid', 'maid',
'nun', 'nun',
], ],
extreme: [
'dp',
'airtight',
'dap',
'dvp',
'triple-penetration',
'tap',
'tvp',
],
fetish: [ fetish: [
'bdsm', 'bdsm',
'femdom', 'femdom',
@@ -134,15 +132,15 @@ const tagSlugsByCategory = {
'latex', 'latex',
'blindfold', 'blindfold',
], ],
extreme: [ toys: [
'dp', 'toys',
'airtight', 'toy-anal',
'dap', 'toy-dp',
'dvp', 'double-dildo',
'da-tp', 'double-dildo-blowjob',
'dv-tp', 'double-dildo-kiss',
'tap', 'double-dildo-anal',
'tvp', 'double-dildo-dp',
], ],
misc: [ misc: [
'gaping', 'gaping',

View File

@@ -12,6 +12,7 @@ export default {
selectableTags: [ selectableTags: [
'airtight', 'airtight',
'anal', 'anal',
'bdsm',
'blowbang', 'blowbang',
'blowjob', 'blowjob',
'creampie', 'creampie',

View File

@@ -65,19 +65,23 @@ function curateActor(actor, release) {
return curatedActor; return curatedActor;
} }
function curateRelease(release) { function curateRelease(release, type = 'scene') {
const curatedRelease = { const curatedRelease = {
...release, ...release,
type: release.type || type,
actors: [], actors: [],
poster: release.poster && release.poster.media, poster: release.poster && release.poster.media,
tags: release.tags ? release.tags.map((tag) => tag.tag || tag) : [], tags: release.tags ? release.tags.map((tag) => tag.tag || tag) : [],
}; };
if (release.scenes) curatedRelease.scenes = release.scenes.filter(Boolean).map(({ scene }) => curateRelease(scene)); curatedRelease.scenes = release.scenes?.filter(Boolean).map(({ scene }) => curateRelease(scene, 'scene')) || [];
if (release.movies) curatedRelease.movies = release.movies.filter(Boolean).map(({ movie }) => curateRelease(movie)); curatedRelease.movies = release.movies?.filter(Boolean).map(({ movie }) => curateRelease(movie, 'movie')) || [];
if (release.chapters) curatedRelease.chapters = release.chapters.filter(Boolean).map((chapter) => curateRelease(chapter)); curatedRelease.series = release.series?.filter(Boolean).map(({ serie }) => curateRelease(serie, 'serie')) || [];
if (release.photos) curatedRelease.photos = release.photos.filter(Boolean).map((photo) => photo.media || photo); curatedRelease.chapters = release.chapters?.filter(Boolean).map((chapter) => curateRelease(chapter)) || [];
if (release.covers) curatedRelease.covers = release.covers.filter(Boolean).map(({ media }) => media); 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.trailer) curatedRelease.trailer = release.trailer.media;
if (release.teaser) curatedRelease.teaser = release.teaser.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)); 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.tags) curatedEntity.tags = entity.tags.map(({ tag }) => tag);
if (entity.sceneTags) curatedEntity.sceneTags = entity.sceneTags;
if (entity.children) { if (entity.children) {
if (entity.children.nodes) { if (entity.children.nodes) {

View File

@@ -41,6 +41,11 @@ function initEntitiesActions(store, router) {
slug slug
} }
} }
sceneTags {
id
name
slug
}
children: childEntitiesConnection( children: childEntitiesConnection(
orderBy: [PRIORITY_DESC, NAME_ASC], orderBy: [PRIORITY_DESC, NAME_ASC],
filter: { filter: {

View File

@@ -367,6 +367,7 @@ const releaseFields = `
date date
datePrecision datePrecision
slug slug
qualities
shootId shootId
productionDate productionDate
comment 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 = ` const releaseFragment = `
release(id: $releaseId) { release(id: $releaseId) {
id id
@@ -452,6 +476,7 @@ const releaseFragment = `
duration duration
createdAt createdAt
shootId shootId
qualities
productionDate productionDate
createdBatchId createdBatchId
productionLocation 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 isFavorited
isStashed(includeFavorites: false) isStashed(includeFavorites: false)
stashes: stashesScenesBySceneId( stashes: stashesScenesBySceneId(
@@ -624,6 +662,8 @@ export {
actorFields, actorFields,
actorStashesFields, actorStashesFields,
campaignsFragment, campaignsFragment,
mediaFields,
mediaFragment,
movieFields, movieFields,
releaseActorsFragment, releaseActorsFragment,
releaseFields, releaseFields,

View File

@@ -4,6 +4,8 @@ import {
releaseFragment, releaseFragment,
releaseFields, releaseFields,
movieFields, movieFields,
mediaFragment,
mediaFields,
} from '../fragments'; } from '../fragments';
import { curateRelease } from '../curate'; import { curateRelease } from '../curate';
import getDateRange from '../get-date-range'; 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 release = await get(`/releases/${releaseId}`);
const { movie } = await graphql(` const result = await graphql(`
query Movie( query Movie(
$movieId: Int! $movieId: Int!
$hasAuth: Boolean! $hasAuth: Boolean!
$userId: Int $userId: Int
) { ) {
movie(id: $movieId) { ${type}(id: $movieId) {
id id
title title
description description
@@ -182,7 +184,7 @@ function initReleasesActions(store, router) {
isS3 isS3
} }
} }
poster: moviesPoster { poster: ${type === 'series' ? 'seriesPosterBySerieId' : 'moviesPoster'} {
media { media {
id id
path path
@@ -195,7 +197,7 @@ function initReleasesActions(store, router) {
isS3 isS3
} }
} }
covers: moviesCovers(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) { covers: ${type === 'series' ? 'seriesCoversBySerieId' : 'moviesCovers'}(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
media { media {
id id
path path
@@ -208,14 +210,14 @@ function initReleasesActions(store, router) {
isS3 isS3
} }
} }
trailer: moviesTrailer { trailer: ${type === 'series' ? 'seriesTrailerBySerieId' : 'moviesTrailer'} {
media { media {
id id
path path
isS3 isS3
} }
} }
scenes: moviesScenes { scenes: ${type === 'series' ? 'seriesScenesBySerieId' : 'moviesScenes'} {
scene { scene {
${releaseFields} ${releaseFields}
} }
@@ -225,25 +227,11 @@ function initReleasesActions(store, router) {
slug slug
name name
} }
photos { photos: ${type === 'series' ? 'seriesPhotosBySerieId' : 'moviesPhotos'} {
id ${mediaFragment}
index }
path scenesPhotos {
thumbnail ${mediaFields}
lazy
width
height
thumbnailWidth
thumbnailHeight
isS3
comment
sfw: sfwMedia {
id
thumbnail
lazy
path
comment
}
} }
entity { entity {
id id
@@ -259,7 +247,7 @@ function initReleasesActions(store, router) {
hasLogo hasLogo
} }
} }
stashes: stashesMovies( stashes: ${type === 'series' ? 'stashesSeriesBySerieId' : 'stashesMovies'}(
filter: { filter: {
stash: { stash: {
userId: { userId: {
@@ -283,12 +271,20 @@ function initReleasesActions(store, router) {
userId: store.state.auth.user?.id, userId: store.state.auth.user?.id,
}); });
if (!movie) { if (!result[type]) {
router.replace('/not-found'); router.replace('/not-found');
return null; 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 { return {
@@ -296,6 +292,7 @@ function initReleasesActions(store, router) {
fetchReleaseById, fetchReleaseById,
fetchMovies, fetchMovies,
fetchMovieById, fetchMovieById,
fetchSerieById,
searchMovies, searchMovies,
}; };
} }

View File

@@ -71,6 +71,11 @@ const routes = [
component: Release, component: Release,
name: 'movie', name: 'movie',
}, },
{
path: '/serie/:releaseId/:releaseSlug?',
component: Release,
name: 'serie',
},
{ {
path: '/actor/:actorId/:actorSlug', path: '/actor/:actorId/:actorSlug',
name: 'actor', name: 'actor',

View File

@@ -89,6 +89,10 @@ module.exports = {
'uksinners', 'uksinners',
// mindgeek // mindgeek
'pornhub', 'pornhub',
// insex
'paintoy',
'aganmedon',
'sensualpain',
], ],
networks: [ networks: [
// dummy network for testing // dummy network for testing

View File

@@ -1,5 +1,5 @@
exports.up = async (knex) => knex.raw(` 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) SELECT COUNT(id)
FROM releases FROM releases
WHERE releases.entity_id = entity.id; WHERE releases.entity_id = entity.id;

View File

@@ -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;
`);

View 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;
`);

View 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;
`));

View 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;
`);

View 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;
`);

View 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');
});

View 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
View File

@@ -1,12 +1,12 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.209.4", "version": "1.213.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "traxxx", "name": "traxxx",
"version": "1.209.4", "version": "1.213.5",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@casl/ability": "^5.2.2", "@casl/ability": "^5.2.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.209.4", "version": "1.213.5",
"description": "All the latest porn releases in one place", "description": "All the latest porn releases in one place",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -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.', 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: { parameters: {
actorPath: 'modelprofile', actorPath: 'modelprofile',
forceDeep: true, // Digital Playground has movie and series information not available in the latest updates API
}, },
parent: 'mindgeek', parent: 'mindgeek',
}, },
@@ -411,6 +412,9 @@ const networks = [
url: 'https://www.milehighmedia.com', 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!', 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', parent: 'mindgeek',
parameters: {
forceDeep: true, // Mile High Media has movie and series information not available in the latest updates API
},
}, },
{ {
slug: 'mofos', slug: 'mofos',

View File

@@ -810,6 +810,13 @@ const sites = [
parameters: { siteId: 3261 }, parameters: { siteId: 3261 },
parent: 'bang', parent: 'bang',
}, },
{
name: 'Bang! Podcast',
slug: 'bangpodcast',
url: 'https://www.bang.com/videos?in=bang!%20podcast',
parameters: { siteId: 6305 },
parent: 'bang',
},
// BANGBROS // BANGBROS
{ {
name: 'Ass Parade', name: 'Ass Parade',
@@ -2646,7 +2653,7 @@ const sites = [
{ {
slug: 'digitalplayground', slug: 'digitalplayground',
name: 'Digital Playground', name: 'Digital Playground',
url: 'https://www.digitalplayground.com/scenes', url: 'https://www.digitalplayground.com',
description: '', description: '',
parameters: { extract: true }, parameters: { extract: true },
parent: 'digitalplayground', parent: 'digitalplayground',
@@ -2680,6 +2687,13 @@ const sites = [
description: '', description: '',
parent: 'digitalplayground', parent: 'digitalplayground',
}, },
{
slug: 'dpstarsexchallenges',
name: 'DP Star Sex Challenges',
url: 'https://www.digitalplayground.com/scenes?site=210',
parent: 'digitalplayground',
hasLogo: false,
},
{ {
slug: 'blockbuster', slug: 'blockbuster',
name: 'Blockbuster', name: 'Blockbuster',
@@ -4205,7 +4219,6 @@ const sites = [
tags: ['bdsm'], tags: ['bdsm'],
parent: 'insex', parent: 'insex',
parameters: { parameters: {
scraper: 'alt',
latest: 'https://www.sexuallybroken.com/sb', latest: 'https://www.sexuallybroken.com/sb',
}, },
}, },
@@ -4216,13 +4229,20 @@ const sites = [
url: 'https://www.infernalrestraints.com', url: 'https://www.infernalrestraints.com',
tags: ['bdsm'], tags: ['bdsm'],
parent: 'insex', parent: 'insex',
parameters: {
latest: 'https://www.infernalrestraints.com/ir',
},
}, },
{ {
slug: 'hardtied', slug: 'hardtied',
name: 'Hardtied', name: 'Hardtied',
alias: ['ht'],
url: 'https://www.hardtied.com', url: 'https://www.hardtied.com',
tags: ['bdsm'], tags: ['bdsm'],
parent: 'insex', parent: 'insex',
parameters: {
latest: 'https://www.hardtied.com/ht',
},
}, },
{ {
slug: 'realtimebondage', slug: 'realtimebondage',
@@ -4231,6 +4251,9 @@ const sites = [
url: 'https://www.realtimebondage.com', url: 'https://www.realtimebondage.com',
tags: ['bdsm', 'live'], tags: ['bdsm', 'live'],
parent: 'insex', parent: 'insex',
parameters: {
latest: 'https://www.realtimebondage.com/rtb',
},
}, },
{ {
slug: 'topgrl', slug: 'topgrl',
@@ -4240,7 +4263,6 @@ const sites = [
tags: ['bdsm', 'femdom'], tags: ['bdsm', 'femdom'],
parent: 'insex', parent: 'insex',
parameters: { parameters: {
scraper: 'alt',
latest: 'https://www.topgrl.com/tg', latest: 'https://www.topgrl.com/tg',
}, },
}, },
@@ -5264,7 +5286,7 @@ const sites = [
{ {
slug: 'bigdicksatschool', slug: 'bigdicksatschool',
name: 'Big Dicks At School', name: 'Big Dicks At School',
url: 'https://www.bigdicksatschool.com', url: 'https://www.men.com/scenes?site=252',
description: '', description: '',
parameters: { siteId: 252 }, parameters: { siteId: 252 },
tags: ['gay'], tags: ['gay'],
@@ -5273,7 +5295,7 @@ const sites = [
{ {
slug: 'drillmyhole', slug: 'drillmyhole',
name: 'Drill My Hole', name: 'Drill My Hole',
url: 'https://www.drillmyhole.com', url: 'https://www.men.com/scenes?site=253',
description: '', description: '',
parameters: { siteId: 253 }, parameters: { siteId: 253 },
tags: ['gay'], tags: ['gay'],
@@ -5282,7 +5304,7 @@ const sites = [
{ {
slug: 'str8togay', slug: 'str8togay',
name: 'Str8 to Gay', name: 'Str8 to Gay',
url: 'https://www.str8togay.com', url: 'https://www.men.com/scenes?site=254',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 254 }, parameters: { siteId: 254 },
parent: 'men', parent: 'men',
@@ -5290,7 +5312,7 @@ const sites = [
{ {
slug: 'thegayoffice', slug: 'thegayoffice',
name: 'The Gay Office', name: 'The Gay Office',
url: 'https://www.thegayoffice.com', url: 'https://www.men.com/scenes?site=255',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 255 }, parameters: { siteId: 255 },
parent: 'men', parent: 'men',
@@ -5298,7 +5320,7 @@ const sites = [
{ {
slug: 'jizzorgy', slug: 'jizzorgy',
name: 'Jizz Orgy', name: 'Jizz Orgy',
url: 'https://www.jizzorgy.com', url: 'https://www.men.com/scenes?site=256',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 256 }, parameters: { siteId: 256 },
parent: 'men', parent: 'men',
@@ -5306,7 +5328,7 @@ const sites = [
{ {
slug: 'menofuk', slug: 'menofuk',
name: 'Men of UK', name: 'Men of UK',
url: 'https://www.menofuk.com', url: 'https://www.men.com/scenes?site=258',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 258 }, parameters: { siteId: 258 },
parent: 'men', parent: 'men',
@@ -5314,7 +5336,7 @@ const sites = [
{ {
slug: 'toptobottom', slug: 'toptobottom',
name: 'Top to Bottom', name: 'Top to Bottom',
url: 'https://www.toptobottom.com', url: 'https://www.men.com/scenes?site=259',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 259 }, parameters: { siteId: 259 },
parent: 'men', parent: 'men',
@@ -5322,7 +5344,7 @@ const sites = [
{ {
slug: 'godsofmen', slug: 'godsofmen',
name: 'Gods of Men', name: 'Gods of Men',
url: 'https://www.godsofmen.com', url: 'https://www.men.com/scenes?site=260',
tags: ['gay'], tags: ['gay'],
parameters: { siteId: 260 }, parameters: { siteId: 260 },
parent: 'men', parent: 'men',
@@ -5413,7 +5435,10 @@ const sites = [
name: 'Doghouse Digital', name: 'Doghouse Digital',
url: 'https://www.doghousedigital.com', url: 'https://www.doghousedigital.com',
alias: ['dhd'], alias: ['dhd'],
parameters: { siteId: 321 }, parameters: {
siteId: 321,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
{ {
@@ -5429,7 +5454,10 @@ const sites = [
name: 'Reality Junkies', name: 'Reality Junkies',
url: 'https://www.realityjunkies.com', url: 'https://www.realityjunkies.com',
alias: ['rj'], alias: ['rj'],
parameters: { siteId: 324 }, parameters: {
siteId: 324,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
{ {
@@ -5437,7 +5465,10 @@ const sites = [
name: 'Sweetheart Video', name: 'Sweetheart Video',
url: 'https://www.sweetheartvideo.com', url: 'https://www.sweetheartvideo.com',
alias: ['shv'], alias: ['shv'],
parameters: { siteId: 325 }, parameters: {
siteId: 325,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
{ {
@@ -5445,7 +5476,10 @@ const sites = [
name: 'Sweet Sinner', name: 'Sweet Sinner',
url: 'https://www.sweetsinner.com', url: 'https://www.sweetsinner.com',
alias: ['ss'], alias: ['ss'],
parameters: { siteId: 326 }, parameters: {
siteId: 326,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
{ {
@@ -5454,7 +5488,10 @@ const sites = [
alias: ['fs'], alias: ['fs'],
tags: ['family'], tags: ['family'],
url: 'https://www.familysinners.com', url: 'https://www.familysinners.com',
parameters: { siteId: 317 }, parameters: {
siteId: 317,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
{ {
@@ -5463,7 +5500,10 @@ const sites = [
url: 'https://www.iconmale.com', url: 'https://www.iconmale.com',
alias: ['im'], alias: ['im'],
tags: ['gay'], tags: ['gay'],
parameters: { native: true, siteId: 328 }, parameters: {
siteId: 328,
native: true,
},
parent: 'milehighmedia', parent: 'milehighmedia',
}, },
// MOFOS // MOFOS
@@ -6877,6 +6917,13 @@ const sites = [
tourId: 9, tourId: 9,
}, },
}, },
{
slug: 'dpdiva',
name: 'DP Diva',
url: 'http://dpdiva.com',
parent: 'pervcity',
tags: ['dp', 'anal'],
},
// PIERRE WOODMAN // PIERRE WOODMAN
{ {
slug: 'woodmancastingx', slug: 'woodmancastingx',
@@ -8579,7 +8626,10 @@ const sites = [
name: 'Dane Jones', name: 'Dane Jones',
alias: ['dnj'], alias: ['dnj'],
url: 'https://www.danejones.com/', url: 'https://www.danejones.com/',
parameters: { siteId: 290 }, parameters: {
siteId: 290,
native: true,
},
parent: 'sexyhub', parent: 'sexyhub',
}, },
{ {
@@ -8587,7 +8637,10 @@ const sites = [
name: 'Lesbea', name: 'Lesbea',
alias: ['lsb'], alias: ['lsb'],
url: 'https://www.lesbea.com', url: 'https://www.lesbea.com',
parameters: { siteId: 291 }, parameters: {
siteId: 291,
native: true,
},
tags: ['lesbian'], tags: ['lesbian'],
parent: 'sexyhub', parent: 'sexyhub',
}, },

View File

@@ -727,8 +727,6 @@ const tagMedia = [
['da-tp', 7, 'Polly Petrova in YE069', 'analvids'], ['da-tp', 7, 'Polly Petrova in YE069', 'analvids'],
['da-tp', 5, 'Venera Maxima in GIO1287'], ['da-tp', 5, 'Venera Maxima in GIO1287'],
['da-tp', 6, 'Adriana Chechik in "Gangbang Me"', 'hardx'], ['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', 7, 'Adriana Chechik in "DP Masters 6"', 'julesjordan'],
['dap', 10, 'Kira Noir', 'hardx'], ['dap', 10, 'Kira Noir', 'hardx'],
['dap', 'emily_pink_legalporno', 'Emily Pink', 'analvids'], ['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'], ['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', 1, 'Chloe Lamour in "Curives In All The Right Places"', 'wetandpuffy'],
['toys', 'shawna_lenee_sunrisekings', 'Shawna Lenee', 'sunrisekings'], ['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', 'angela_white_julesjordan', 'Angela White in "Her Biggest Gangbang Ever"', 'julesjordan'],
['triple-penetration', 'ria_sunn_legalporno', 'Ria Sunn in SZ2082', 'analvids'], ['triple-penetration', 'ria_sunn_legalporno', 'Ria Sunn in SZ2082', 'analvids'],
['tvp', 'september_reign_wefuckblackgirls', 'September Reign in "Second Appearance"', 'wefuckblackgirls'], ['tvp', 'september_reign_wefuckblackgirls', 'September Reign in "Second Appearance"', 'wefuckblackgirls'],

View File

@@ -14,6 +14,6 @@
"prefer-destructuring": "off", "prefer-destructuring": "off",
"template-curly-spacing": "off", "template-curly-spacing": "off",
"object-curly-newline": "off", "object-curly-newline": "off",
"max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}], "max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}]
} }
} }

View File

@@ -20,6 +20,7 @@ const scrapers = require('./scrapers/scrapers').actors;
const argv = require('./argv'); const argv = require('./argv');
const include = require('./utils/argv-include')(argv); const include = require('./utils/argv-include')(argv);
const bulkInsert = require('./utils/bulk-insert'); const bulkInsert = require('./utils/bulk-insert');
const chunk = require('./utils/chunk');
const logger = require('./logger')(__filename); const logger = require('./logger')(__filename);
const { toBaseReleases } = require('./deep'); const { toBaseReleases } = require('./deep');
@@ -1048,33 +1049,42 @@ async function flushProfiles(actorIdsOrNames) {
logger.info(`Removed ${deleteCount} profiles`); logger.info(`Removed ${deleteCount} profiles`);
} }
async function deleteActors(actorIdsOrNames) { async function deleteActors(allActorIdsOrNames) {
const actors = await knex('actors') const deleteCounts = await Promise.map(chunk(allActorIdsOrNames), async (actorIdsOrNames) => {
.whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number')) const actors = await knex('actors')
.orWhere((builder) => { .whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number'))
builder .orWhere((builder) => {
.whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string')) builder
.whereNull('entity_id'); .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') const sceneIds = await knex('releases_actors')
.select('releases.id') .select('releases.id')
.whereIn('actor_id', actorIds) .whereIn('actor_id', actorIds)
.leftJoin('releases', 'releases.id', 'releases_actors.release_id') .leftJoin('releases', 'releases.id', 'releases_actors.release_id')
.pluck('id'); .pluck('id');
const [deletedScenesCount, deletedActorsCount] = await Promise.all([ const [deletedScenesCount, deletedActorsCount] = await Promise.all([
deleteScenes(sceneIds), deleteScenes(sceneIds),
knex('actors') knex('actors')
.whereIn('id', actorIds) .whereIn('id', actorIds)
.delete(), .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(); await flushOrphanedMedia();
logger.info(`Removed ${deletedActorsCount} actors with ${deletedScenesCount} scenes`); logger.info(`Removed ${deletedActorsCount} actors with ${deletedScenesCount} scenes`);
return deletedActorsCount;
} }
async function flushActors() { async function flushActors() {

View File

@@ -194,6 +194,7 @@ const { argv } = yargs
alias: 'pics', alias: 'pics',
}) })
.option('videos', { .option('videos', {
alias: 'video',
describe: 'Include any trailers or teasers', describe: 'Include any trailers or teasers',
type: 'boolean', type: 'boolean',
default: true, default: true,
@@ -238,6 +239,7 @@ const { argv } = yargs
default: false, default: false,
}) })
.option('level', { .option('level', {
alias: 'log-level',
describe: 'Log level', describe: 'Log level',
type: 'string', type: 'string',
default: process.env.NODE_ENV === 'development' ? 'silly' : 'info', default: process.env.NODE_ENV === 'development' ? 'silly' : 'info',

View File

@@ -6,7 +6,7 @@ const inquirer = require('inquirer');
const logger = require('./logger')(__filename); const logger = require('./logger')(__filename);
const argv = require('./argv'); const argv = require('./argv');
const knex = require('./knex'); const knex = require('./knex');
const { deleteScenes, deleteMovies } = require('./releases'); const { deleteScenes, deleteMovies, deleteSeries } = require('./releases');
const { flushOrphanedMedia } = require('./media'); const { flushOrphanedMedia } = require('./media');
const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve'); const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve');
@@ -359,29 +359,39 @@ async function flushEntities(networkSlugs = [], channelSlugs = []) {
.leftJoin('movies', 'movies.entity_id', 'selected_entities.id') .leftJoin('movies', 'movies.entity_id', 'selected_entities.id')
.pluck('movies.id'); .pluck('movies.id');
if (sceneIds.length === 0 && movieIds.length === 0) { const serieIds = await entityQuery
logger.info(`No scenes or movies found to remove for ${entitySlugs}`); .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; return;
} }
const confirmed = await inquirer.prompt([{ const confirmed = await inquirer.prompt([{
type: 'confirm', type: 'confirm',
name: 'flushEntities', 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, default: false,
}]); }]);
if (!confirmed.flushEntities) { 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; return;
} }
const [deletedScenesCount, deletedMoviesCount] = await Promise.all([ const [deletedScenesCount, deletedMoviesCount, deletedSeriesCount] = await Promise.all([
deleteScenes(sceneIds), deleteScenes(sceneIds),
deleteMovies(movieIds), 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(); await flushOrphanedMedia();
} }

View File

@@ -21,6 +21,7 @@ const argv = require('./argv');
const knex = require('./knex'); const knex = require('./knex');
const http = require('./utils/http'); const http = require('./utils/http');
const bulkInsert = require('./utils/bulk-insert'); const bulkInsert = require('./utils/bulk-insert');
const chunk = require('./utils/chunk');
const { get } = require('./utils/qu'); const { get } = require('./utils/qu');
const pipeline = util.promisify(stream.pipeline); 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.slice(0, -1).concat(chunks.slice(-1).reverse())
: chunks; : chunks;
const groupedMedias = lastPreferredChunks.map((chunk) => { const groupedMedias = lastPreferredChunks.map((mediaChunk) => {
// merge chunked medias into single media with grouped fallback priorities, // merge chunked medias into single media with grouped fallback priorities,
// so the first sources of each media is preferred over all second sources, etc. // so the first sources of each media is preferred over all second sources, etc.
const sources = chunk const sources = mediaChunk
.reduce((accSources, media) => { .reduce((accSources, media) => {
media.sources.forEach((source, index) => { media.sources.forEach((source, index) => {
if (!accSources[index]) { if (!accSources[index]) {
@@ -82,8 +83,8 @@ function sampleMedias(medias, limit = argv.mediaLimit, preferLast = true) {
.flat(); .flat();
return { return {
id: chunk[0].id, id: mediaChunk[0].id,
role: chunk[0].role, role: mediaChunk[0].role,
sources, sources,
}; };
}); });
@@ -235,22 +236,41 @@ async function findSourceDuplicates(baseMedias) {
.filter(Boolean); .filter(Boolean);
const [existingSourceMedia, existingExtractMedia] = await Promise.all([ const [existingSourceMedia, existingExtractMedia] = await Promise.all([
knex('media').whereIn('source', sourceUrls), // my try to check thousands of URLs at once, don't pass all of them to a single query
knex('media').whereIn('source_page', extractUrls), 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 existingSourceMediaByUrl = itemsByKey(existingSourceMedia, 'source');
const existingExtractMediaByUrl = itemsByKey(existingExtractMedia, 'source_page'); const existingExtractMediaByUrl = itemsByKey(existingExtractMedia, 'source_page');
return { existingSourceMediaByUrl, existingExtractMediaByUrl }; return {
existingSourceMediaByUrl,
existingExtractMediaByUrl,
};
} }
async function findHashDuplicates(medias) { async function findHashDuplicates(medias) {
const hashes = medias.map((media) => media.meta?.hash || media.entry?.hash).filter(Boolean); const hashes = medias.map((media) => media.meta?.hash || media.entry?.hash).filter(Boolean);
const existingHashMediaEntries = await knex('media').whereIn('hash', hashes); const existingHashMediaEntries = await chunk(hashes, 2).reduce(async (chain, hashesChunk) => {
const existingHashMediaEntriesByHash = itemsByKey(existingHashMediaEntries, 'hash'); 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 uniqueHashMedias = medias.filter((media) => !media.entry && !existingHashMediaEntriesByHash[media.meta?.hash]);
const { selfDuplicateMedias, selfUniqueMediasByHash } = uniqueHashMedias.reduce((acc, media) => { const { selfDuplicateMedias, selfUniqueMediasByHash } = uniqueHashMedias.reduce((acc, media) => {
@@ -600,11 +620,11 @@ async function fetchSource(source, baseMedia) {
const hashStream = new stream.PassThrough(); const hashStream = new stream.PassThrough();
let size = 0; let size = 0;
hashStream.on('data', (chunk) => { hashStream.on('data', (streamChunk) => {
size += chunk.length; size += streamChunk.length;
if (hasherReady) { if (hasherReady) {
hasher.write(chunk); hasher.write(streamChunk);
} }
}); });
@@ -961,9 +981,12 @@ async function flushOrphanedMedia() {
await deleteS3Objects(orphanedMedia.filter((media) => media.is_s3)); await deleteS3Objects(orphanedMedia.filter((media) => media.is_s3));
} }
await fsPromises.rm(path.join(config.media.path, 'temp'), { recursive: true }); try {
await fsPromises.rm(path.join(config.media.path, 'temp'), { recursive: true });
logger.info('Cleared temporary media directory'); logger.info('Cleared temporary media directory');
} catch (error) {
logger.warn(`Failed to clear temporary media directory: ${error.message}`);
}
} }
module.exports = { module.exports = {

View File

@@ -325,6 +325,24 @@ async function deleteMovies(movieIds) {
return deleteCount; return deleteCount;
} }
async function deleteSeries(serieIds) {
if (serieIds.length === 0) {
return 0;
}
await knex('series_scenes')
.whereIn('serie_id', serieIds)
.delete();
const deleteCount = await knex('series')
.whereIn('id', serieIds)
.delete();
logger.info(`Removed ${deleteCount}/${serieIds.length} series`);
return deleteCount;
}
async function flushScenes() { async function flushScenes() {
const sceneIds = await knex('releases').select('id').pluck('id'); const sceneIds = await knex('releases').select('id').pluck('id');
@@ -367,6 +385,27 @@ async function flushMovies() {
logger.info(`Removed ${deleteCount}/${movieIds.length} movies`); logger.info(`Removed ${deleteCount}/${movieIds.length} movies`);
} }
async function flushSeries() {
const serieIds = await knex('series').select('id').pluck('id');
const confirmed = await inquirer.prompt([{
type: 'confirm',
name: 'flushSeries',
message: `You are about to remove ${serieIds.length} series. Are you sure?`,
default: false,
}]);
if (!confirmed.flushSeries) {
logger.warn('Confirmation rejected, not flushing series');
return;
}
const deleteCount = await deleteSeries(serieIds);
await flushOrphanedMedia();
logger.info(`Removed ${deleteCount}/${serieIds.length} series`);
}
async function flushBatches(batchIds) { async function flushBatches(batchIds) {
const [sceneIds, movieIds] = await Promise.all([ const [sceneIds, movieIds] = await Promise.all([
knex('releases') knex('releases')
@@ -407,8 +446,10 @@ module.exports = {
fetchScenes, fetchScenes,
flushBatches, flushBatches,
flushMovies, flushMovies,
flushSeries,
flushScenes, flushScenes,
searchScenes, searchScenes,
deleteScenes, deleteScenes,
deleteMovies, deleteMovies,
deleteSeries,
}; };

Some files were not shown because too many files have changed in this diff Show More