Added DarkkoTV scraper. Removed some obsolete web components.
This commit is contained in:
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,maximum-scale=1,user-scalable=no">
|
|
||||||
<meta name="theme-color" content="#ff2288">
|
|
||||||
|
|
||||||
<title>traxxx</title>
|
|
||||||
|
|
||||||
<link rel="icon" href="/img/favicon/favicon-32.ico">
|
|
||||||
<link rel="icon" href="/img/favicon/favicon.svg" type="image/svg+xml">
|
|
||||||
<link rel="apple-touch-icon" href="/img/favicon/favicon-180.png">
|
|
||||||
<link rel="manifest" href="/img/favicon/manifest.webmanifest">
|
|
||||||
|
|
||||||
<meta name="msapplication-TileColor" content="#aa2c66">
|
|
||||||
<meta name="msapplication-config" content="/img/favicon/browserconfig.xml">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
|
|
||||||
<script src="/js/bundle.js" defer></script>
|
|
||||||
|
|
||||||
<% if (analytics.enabled) { %>
|
|
||||||
<script async src="<%- analytics.address %>" data-website-id="<%- analytics.siteId %>"></script>
|
|
||||||
<% } %>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container"></div>
|
|
||||||
|
|
||||||
<script>window.env = <%- env %>;</script>
|
|
||||||
<!-- flag icons by https://www.flaticon.com/authors/freepik -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -27,21 +27,6 @@ module.exports = {
|
|||||||
destroyTimeoutMillis: 300000,
|
destroyTimeoutMillis: 300000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
web: {
|
|
||||||
host: '0.0.0.0',
|
|
||||||
port: 5000,
|
|
||||||
sfwHost: '0.0.0.0',
|
|
||||||
sfwPort: 5001,
|
|
||||||
session: {
|
|
||||||
secret: '12345678abcdefghij',
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
cookie: {
|
|
||||||
secure: true,
|
|
||||||
maxAge: 2629800000, // 1 month
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
redis: {
|
redis: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 6379,
|
port: 6379,
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -94,7 +94,7 @@
|
|||||||
"tunnel": "0.0.6",
|
"tunnel": "0.0.6",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"undici": "^5.28.1",
|
"undici": "^5.28.1",
|
||||||
"unprint": "^0.19.11",
|
"unprint": "^0.19.13",
|
||||||
"url-pattern": "^1.0.3",
|
"url-pattern": "^1.0.3",
|
||||||
"v-tooltip": "^2.1.3",
|
"v-tooltip": "^2.1.3",
|
||||||
"video.js": "^8.6.1",
|
"video.js": "^8.6.1",
|
||||||
@@ -20668,9 +20668,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unprint": {
|
"node_modules/unprint": {
|
||||||
"version": "0.19.11",
|
"version": "0.19.13",
|
||||||
"resolved": "https://registry.npmjs.org/unprint/-/unprint-0.19.11.tgz",
|
"resolved": "https://registry.npmjs.org/unprint/-/unprint-0.19.13.tgz",
|
||||||
"integrity": "sha512-k+7zVUiviO8OpIvyrk/WGtzoEYRRLcru5wB+AChH7G8xlCDm46cE8LFqi5dGRt7+3iHbVfLSaN3i6ZbBoGuiUw==",
|
"integrity": "sha512-HPNCQn2CziiGeK0JSZg/5E+G2prHme+8lDojxd16wUwSQ0mgW4nZq4LOuVMIRRAFm1M1nkju0oMIdsj4uRFASw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bottleneck": "^2.19.5",
|
"bottleneck": "^2.19.5",
|
||||||
"cookie": "^1.1.1",
|
"cookie": "^1.1.1",
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
"tunnel": "0.0.6",
|
"tunnel": "0.0.6",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"undici": "^5.28.1",
|
"undici": "^5.28.1",
|
||||||
"unprint": "^0.19.11",
|
"unprint": "^0.19.13",
|
||||||
"url-pattern": "^1.0.3",
|
"url-pattern": "^1.0.3",
|
||||||
"v-tooltip": "^2.1.3",
|
"v-tooltip": "^2.1.3",
|
||||||
"video.js": "^8.6.1",
|
"video.js": "^8.6.1",
|
||||||
|
|||||||
@@ -3748,6 +3748,12 @@ const sites = [
|
|||||||
description: "Do you love blowjob scenes? Welcome to the place with the best outdoors cock-eating techniques you've ever seen. The randiest females sucking cocks right there.",
|
description: "Do you love blowjob scenes? Welcome to the place with the best outdoors cock-eating techniques you've ever seen. The randiest females sucking cocks right there.",
|
||||||
parent: 'cumlouder',
|
parent: 'cumlouder',
|
||||||
},
|
},
|
||||||
|
// DARKKO TV
|
||||||
|
{
|
||||||
|
name: 'Darkko TV',
|
||||||
|
slug: 'darkkotv',
|
||||||
|
url: 'https://darkkotv.com',
|
||||||
|
},
|
||||||
// PORN WORLD / DDF NETWORK
|
// PORN WORLD / DDF NETWORK
|
||||||
{
|
{
|
||||||
slug: 'pornworld',
|
slug: 'pornworld',
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const fs = require('fs').promises;
|
|||||||
const { format, intervalToDuration } = require('date-fns');
|
const { format, intervalToDuration } = require('date-fns');
|
||||||
|
|
||||||
const argv = require('./argv');
|
const argv = require('./argv');
|
||||||
const initServer = require('./web/server');
|
|
||||||
const http = require('./utils/http');
|
const http = require('./utils/http');
|
||||||
|
|
||||||
const logger = require('./logger')(__filename);
|
const logger = require('./logger')(__filename);
|
||||||
@@ -130,11 +129,6 @@ async function init() {
|
|||||||
try {
|
try {
|
||||||
await redis.connect();
|
await redis.connect();
|
||||||
|
|
||||||
if (argv.server) {
|
|
||||||
await initServer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv.sampleMemory) {
|
if (argv.sampleMemory) {
|
||||||
await startMemorySample(config.memorySampling.snapshotIntervals);
|
await startMemorySample(config.memorySampling.snapshotIntervals);
|
||||||
}
|
}
|
||||||
|
|||||||
215
src/releases.js
215
src/releases.js
@@ -8,117 +8,6 @@ const argv = require('./argv');
|
|||||||
const { updateSceneSearch } = require('./update-search');
|
const { updateSceneSearch } = require('./update-search');
|
||||||
const { flushOrphanedMedia } = require('./media');
|
const { flushOrphanedMedia } = require('./media');
|
||||||
|
|
||||||
const { graphql } = require('./web/graphql');
|
|
||||||
|
|
||||||
const releaseFields = `
|
|
||||||
id
|
|
||||||
entryId
|
|
||||||
shootId
|
|
||||||
title
|
|
||||||
url
|
|
||||||
date
|
|
||||||
description
|
|
||||||
duration
|
|
||||||
entity {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
parent {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actors: releasesActors {
|
|
||||||
actor {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
gender
|
|
||||||
aliasFor
|
|
||||||
entityId
|
|
||||||
entryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tags: releasesTags {
|
|
||||||
tag {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chapters(orderBy: TIME_ASC) @include(if: $full) {
|
|
||||||
id
|
|
||||||
index
|
|
||||||
time
|
|
||||||
duration
|
|
||||||
title
|
|
||||||
description
|
|
||||||
tags: chaptersTags {
|
|
||||||
tag {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
poster: chaptersPoster {
|
|
||||||
media {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
thumbnail
|
|
||||||
s3: isS3
|
|
||||||
width
|
|
||||||
height
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
photos: chaptersPhotos {
|
|
||||||
media {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
thumbnail
|
|
||||||
s3: isS3
|
|
||||||
width
|
|
||||||
height
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
poster: releasesPoster {
|
|
||||||
media {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
thumbnail
|
|
||||||
s3: isS3
|
|
||||||
width
|
|
||||||
height
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
photos: releasesPhotos @include (if: $full) {
|
|
||||||
media {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
thumbnail
|
|
||||||
s3: isS3
|
|
||||||
width
|
|
||||||
height
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trailer: releasesTrailer @include (if: $full) {
|
|
||||||
media {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
s3: isS3
|
|
||||||
vr: isVr
|
|
||||||
quality
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
`;
|
|
||||||
|
|
||||||
function curateRelease(release, withMedia = false, withPoster = true) {
|
function curateRelease(release, withMedia = false, withPoster = true) {
|
||||||
if (!release) {
|
if (!release) {
|
||||||
return null;
|
return null;
|
||||||
@@ -193,107 +82,6 @@ function curateRelease(release, withMedia = false, withPoster = true) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function curateGraphqlRelease(release) {
|
|
||||||
if (!release) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: release.id,
|
|
||||||
...(release.relevance && { relevance: release.relevance }),
|
|
||||||
entryId: release.entryId,
|
|
||||||
shootId: release.shootId,
|
|
||||||
title: release.title || null,
|
|
||||||
url: release.url || null,
|
|
||||||
date: release.date,
|
|
||||||
description: release.description || null,
|
|
||||||
duration: release.duration,
|
|
||||||
entity: release.entity,
|
|
||||||
actors: release.actors.map((actor) => actor.actor),
|
|
||||||
tags: release.tags.map((tag) => tag.tag),
|
|
||||||
...(release.chapters && { chapters: release.chapters.map((chapter) => ({
|
|
||||||
...chapter,
|
|
||||||
tags: chapter.tags.map((tag) => tag.tag),
|
|
||||||
poster: chapter.poster?.media || null,
|
|
||||||
photos: chapter.photos.map((photo) => photo.media),
|
|
||||||
})) }),
|
|
||||||
poster: release.poster?.media || null,
|
|
||||||
...(release.photos && { photos: release.photos.map((photo) => photo.media) }),
|
|
||||||
trailer: release.trailer?.media || null,
|
|
||||||
createdAt: release.createdAt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchScene(releaseId) {
|
|
||||||
const { release } = await graphql(`
|
|
||||||
query Release(
|
|
||||||
$releaseId: Int!
|
|
||||||
$full: Boolean = true
|
|
||||||
) {
|
|
||||||
release(id: $releaseId) {
|
|
||||||
${releaseFields}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, {
|
|
||||||
releaseId: Number(releaseId),
|
|
||||||
});
|
|
||||||
|
|
||||||
return curateGraphqlRelease(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchScenes(limit = 100) {
|
|
||||||
const { releases } = await graphql(`
|
|
||||||
query SearchReleases(
|
|
||||||
$limit: Int = 20
|
|
||||||
$full: Boolean = false
|
|
||||||
) {
|
|
||||||
releases(
|
|
||||||
first: $limit
|
|
||||||
orderBy: DATE_DESC
|
|
||||||
) {
|
|
||||||
${releaseFields}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, {
|
|
||||||
limit: Math.min(limit, 10000),
|
|
||||||
});
|
|
||||||
|
|
||||||
return releases.map((release) => curateGraphqlRelease(release));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function searchScenes(query, limit = 100, relevance = 0) {
|
|
||||||
const { releases } = await graphql(`
|
|
||||||
query SearchReleases(
|
|
||||||
$query: String!
|
|
||||||
$limit: Int = 20
|
|
||||||
$relevance: Float = 0.025
|
|
||||||
$full: Boolean = false
|
|
||||||
) {
|
|
||||||
releases: searchReleases(
|
|
||||||
query: $query
|
|
||||||
first: $limit
|
|
||||||
orderBy: RANK_DESC
|
|
||||||
filter: {
|
|
||||||
rank: {
|
|
||||||
greaterThan: $relevance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
rank
|
|
||||||
release {
|
|
||||||
${releaseFields}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, {
|
|
||||||
query,
|
|
||||||
limit,
|
|
||||||
relevance,
|
|
||||||
});
|
|
||||||
|
|
||||||
return releases.map((release) => curateGraphqlRelease({ ...release.release, relevance: release.rank }));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteScenes(sceneIds) {
|
async function deleteScenes(sceneIds) {
|
||||||
if (sceneIds.length === 0) {
|
if (sceneIds.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -483,13 +271,10 @@ async function flushBatches(batchIds) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
curateRelease,
|
curateRelease,
|
||||||
fetchScene,
|
|
||||||
fetchScenes,
|
|
||||||
flushBatches,
|
flushBatches,
|
||||||
flushMovies,
|
flushMovies,
|
||||||
flushSeries,
|
flushSeries,
|
||||||
flushScenes,
|
flushScenes,
|
||||||
searchScenes,
|
|
||||||
deleteScenes,
|
deleteScenes,
|
||||||
deleteMovies,
|
deleteMovies,
|
||||||
deleteSeries,
|
deleteSeries,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const bradmontana = require('./bradmontana');
|
|||||||
const cherrypimps = require('./cherrypimps');
|
const cherrypimps = require('./cherrypimps');
|
||||||
const cumlouder = require('./cumlouder');
|
const cumlouder = require('./cumlouder');
|
||||||
const modelmedia = require('./modelmedia');
|
const modelmedia = require('./modelmedia');
|
||||||
|
const darkkotv = require('./darkkotv');
|
||||||
const dorcel = require('./dorcel');
|
const dorcel = require('./dorcel');
|
||||||
// const famedigital = require('./famedigital');
|
// const famedigital = require('./famedigital');
|
||||||
const firstanalquest = require('./firstanalquest');
|
const firstanalquest = require('./firstanalquest');
|
||||||
@@ -223,6 +224,7 @@ module.exports = {
|
|||||||
bradmontana,
|
bradmontana,
|
||||||
cherrypimps,
|
cherrypimps,
|
||||||
cumlouder,
|
cumlouder,
|
||||||
|
darkkotv,
|
||||||
dorcelclub: dorcel,
|
dorcelclub: dorcel,
|
||||||
freeones,
|
freeones,
|
||||||
hitzefrei,
|
hitzefrei,
|
||||||
|
|||||||
157
src/scrapers/darkkotv.js
Executable file
157
src/scrapers/darkkotv.js
Executable file
@@ -0,0 +1,157 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const unprint = require('unprint');
|
||||||
|
|
||||||
|
const slugify = require('../utils/slugify');
|
||||||
|
const tryUrls = require('../utils/try-urls');
|
||||||
|
const { convert } = require('../utils/convert');
|
||||||
|
|
||||||
|
function getEntryId(url) {
|
||||||
|
return slugify(new URL(url).pathname.match(/\/scenes\/(.*?)(_vids)?.html/)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeAll(scenes, channel) {
|
||||||
|
return scenes.map(({ query }) => {
|
||||||
|
const release = {};
|
||||||
|
|
||||||
|
release.url = query.url('.videoPic a, h4 a');
|
||||||
|
release.entryId = getEntryId(release.url);
|
||||||
|
|
||||||
|
release.title = query.content('h4 a');
|
||||||
|
|
||||||
|
release.date = query.date('.videoInfo li:first-child ', 'MM-DD-YYYY');
|
||||||
|
release.duration = query.number('.videoInfo li:nth-child(2)') * 60 || null;
|
||||||
|
|
||||||
|
release.actors = query.all('a[href*="models/"]').map((actorEl) => ({
|
||||||
|
name: unprint.query.content(actorEl),
|
||||||
|
url: unprint.query.url(actorEl, null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
release.poster = Array.from({ length: 4 }, (_value, index) => query.img('.videoPic img', { attribute: `src0_${4 - index}x`, origin: channel.origin }));
|
||||||
|
|
||||||
|
return release;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const url = `${channel.url}/categories/movies_${page}.html`;
|
||||||
|
const res = await unprint.get(url, { selectAll: '.latestUpdateB' });
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeAll(res.context, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCaps(url) {
|
||||||
|
if (!url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unprint.get(url, { select: '.photoDetailsArea' });
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return res.context.query.imgs('.photoDPic img');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeScene({ query: pageQuery, html }, { url, entity, include }) {
|
||||||
|
const release = {};
|
||||||
|
const { query } = unprint.init(pageQuery.element('.latestUpdateBinfo'));
|
||||||
|
|
||||||
|
release.entryId = getEntryId(url);
|
||||||
|
|
||||||
|
release.title = pageQuery.content('.vidImgTitle h4');
|
||||||
|
release.description = query.content('.vidImgContent p');
|
||||||
|
|
||||||
|
release.date = query.date('.videoInfo li:first-child ', 'MM-DD-YYYY');
|
||||||
|
release.duration = query.number('.videoInfo li:nth-child(2)') * 60 || null;
|
||||||
|
|
||||||
|
release.actors = query.all('a[href*="models/"]').map((actorEl) => ({
|
||||||
|
name: unprint.query.content(actorEl),
|
||||||
|
url: unprint.query.url(actorEl, null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
release.tags = query.contents('.blogTags a');
|
||||||
|
|
||||||
|
const posterPath = html.match(/useimage\s*=\s*"(.*?)"/i)?.[1];
|
||||||
|
const capsUrl = pageQuery.url('a[href*="_caps"]');
|
||||||
|
|
||||||
|
if (posterPath) {
|
||||||
|
release.poster = Array.from({ length: 4 }, (_value, index) => unprint.prefixUrl(posterPath.replace('-4x', `-${4 - index}x`), entity.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.photos && capsUrl) {
|
||||||
|
release.caps = await fetchCaps(capsUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
release.trailer = pageQuery.video('#download_select option[value*=".mp4"]', { attribute: 'value' });
|
||||||
|
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrapeProfile({ query }, { url, actorName }) {
|
||||||
|
const profile = { url };
|
||||||
|
|
||||||
|
const bio = Object.fromEntries(query.contents('.vitalStats li').map((entry) => {
|
||||||
|
const [key, value] = entry.split(':');
|
||||||
|
|
||||||
|
if (!key || !value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [slugify(key, '_'), value?.trim()];
|
||||||
|
}).filter(Boolean));
|
||||||
|
|
||||||
|
profile.description = `${query.content('.modelBioInfo')?.replace(new RegExp(`professional bio of ${actorName}`, 'i'), '')}${bio.awards ? ` Awards: ${bio.awards}` : ''}`;
|
||||||
|
|
||||||
|
profile.dateOfBirth = unprint.extractDate(bio.date_of_birth, 'MMMM D, YYYY');
|
||||||
|
profile.birthPlace = bio.birthplace;
|
||||||
|
profile.ethnicity = bio.ethnicity;
|
||||||
|
|
||||||
|
profile.height = unprint.extractNumber(bio.height, { match: /(\d+)\s*cm/i, matchIndex: 1 })
|
||||||
|
|| convert(bio.height?.match(/\d+\s*ft \d+\s*in/)?.[0], 'cm');
|
||||||
|
|
||||||
|
profile.weight = unprint.extractNumber(bio.weight, { match: /(\d+)\s*kg/i, matchIndex: 1 })
|
||||||
|
|| convert(bio.weight?.match(/\d+\s*lbs/)[0], 'lb', 'kg');
|
||||||
|
|
||||||
|
profile.measurements = bio.measurements;
|
||||||
|
|
||||||
|
if (/yes/i.test(bio.natural_breasts)) profile.naturalBoobs = true;
|
||||||
|
if (/no/i.test(bio.natural_breasts)) profile.naturalBoobs = false;
|
||||||
|
|
||||||
|
if (/yes/i.test(bio.tattoos)) profile.hasTattoos = true;
|
||||||
|
if (/no/i.test(bio.tattoos)) profile.hasTattoos = false;
|
||||||
|
|
||||||
|
if (/yes/i.test(bio.piercings)) profile.hasPiercings = true;
|
||||||
|
if (/no/i.test(bio.piercings)) profile.hasPiercings = false;
|
||||||
|
|
||||||
|
profile.socials = query.urls('.vitalStats a[href*="onlyfans"], .vitalStats a[href*="twitter"], .vitalStats a[href*="instagram"]');
|
||||||
|
profile.avatar = Array.from({ length: 4 }, (_value, index) => query.img('.modelBioPic img', { attribute: `src0_${4 - index}x` }));
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile({ name: actorName, url: actorUrl }, entity) {
|
||||||
|
const { res, url } = await tryUrls([
|
||||||
|
actorUrl,
|
||||||
|
`${entity.url}/models/${slugify(actorName, '-')}.html`,
|
||||||
|
`${entity.url}/models/${slugify(actorName, '')}.html`,
|
||||||
|
`${entity.url}/models/${slugify(actorName, '_')}.html`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeProfile(res.context, { url, entity, actorName });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchLatest,
|
||||||
|
fetchProfile,
|
||||||
|
scrapeScene,
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ const cherrypimps = require('./cherrypimps');
|
|||||||
const cliffmedia = require('./cliffmedia');
|
const cliffmedia = require('./cliffmedia');
|
||||||
const cumlouder = require('./cumlouder');
|
const cumlouder = require('./cumlouder');
|
||||||
const czechav = require('./czechav');
|
const czechav = require('./czechav');
|
||||||
|
const darkkotv = require('./darkkotv');
|
||||||
const modelmedia = require('./modelmedia');
|
const modelmedia = require('./modelmedia');
|
||||||
const dorcel = require('./dorcel');
|
const dorcel = require('./dorcel');
|
||||||
const fabulouscash = require('./fabulouscash');
|
const fabulouscash = require('./fabulouscash');
|
||||||
@@ -118,6 +119,7 @@ module.exports = {
|
|||||||
cumlouder,
|
cumlouder,
|
||||||
czechav,
|
czechav,
|
||||||
pornworld,
|
pornworld,
|
||||||
|
darkkotv,
|
||||||
delphine: modelmedia,
|
delphine: modelmedia,
|
||||||
dorcel,
|
dorcel,
|
||||||
elegantangel: adultempire,
|
elegantangel: adultempire,
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ function scrapeAll(scenes) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchLatest(channel, page = 1) {
|
||||||
|
const url = `${channel.url}/${page}`;
|
||||||
|
const res = await unprint.get(url, { selectAll: '.scene' });
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return scrapeAll(res.context, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status;
|
||||||
|
}
|
||||||
|
|
||||||
function scrapeScene({ query }, { url }) {
|
function scrapeScene({ query }, { url }) {
|
||||||
const release = {};
|
const release = {};
|
||||||
|
|
||||||
@@ -62,17 +73,6 @@ function scrapeProfile({ query }) {
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLatest(channel, page = 1) {
|
|
||||||
const url = `${channel.url}/${page}`;
|
|
||||||
const res = await unprint.get(url, { selectAll: '.scene' });
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return scrapeAll(res.context, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchProfile({ name: actorName }, entity) {
|
async function fetchProfile({ name: actorName }, entity) {
|
||||||
const url = `${entity.url}/actors/${slugify(actorName, '_')}`;
|
const url = `${entity.url}/actors/${slugify(actorName, '_')}`;
|
||||||
const res = await unprint.get(url);
|
const res = await unprint.get(url);
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ async function storeScenes(releases, useBatchId) {
|
|||||||
const uniqueReleasesWithId = attachReleaseIds(uniqueReleases, storedReleaseEntries, batchId);
|
const uniqueReleasesWithId = attachReleaseIds(uniqueReleases, storedReleaseEntries, batchId);
|
||||||
const duplicateReleasesWithId = attachReleaseIds(duplicateReleases, duplicateReleaseEntries, batchId);
|
const duplicateReleasesWithId = attachReleaseIds(duplicateReleases, duplicateReleaseEntries, batchId);
|
||||||
const curatedDuplicateReleases = await Promise.all(duplicateReleasesWithId.map((release) => curateReleaseEntry(release, batchId)));
|
const curatedDuplicateReleases = await Promise.all(duplicateReleasesWithId.map((release) => curateReleaseEntry(release, batchId)));
|
||||||
|
|
||||||
const releasesWithId = uniqueReleasesWithId.concat(duplicateReleasesWithId);
|
const releasesWithId = uniqueReleasesWithId.concat(duplicateReleasesWithId);
|
||||||
|
|
||||||
const updatedChunks = await Promise.all(chunk(curatedDuplicateReleases, 500).map(async (chunkedReleases) => knex.raw(`
|
const updatedChunks = await Promise.all(chunk(curatedDuplicateReleases, 500).map(async (chunkedReleases) => knex.raw(`
|
||||||
@@ -449,6 +450,7 @@ async function storeScenes(releases, useBatchId) {
|
|||||||
FROM json_to_recordset(:scenes)
|
FROM json_to_recordset(:scenes)
|
||||||
AS new(id int, url text, date timestamptz, entity json, title text, description text, shoot_id text, duration integer, comment text, attributes json, deep boolean)
|
AS new(id int, url text, date timestamptz, entity json, title text, description text, shoot_id text, duration integer, comment text, attributes json, deep boolean)
|
||||||
WHERE releases.id = new.id
|
WHERE releases.id = new.id
|
||||||
|
RETURNING releases.*
|
||||||
`, {
|
`, {
|
||||||
scenes: JSON.stringify(chunkedReleases),
|
scenes: JSON.stringify(chunkedReleases),
|
||||||
})));
|
})));
|
||||||
|
|||||||
14
src/tags.js
14
src/tags.js
@@ -154,7 +154,7 @@ function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntit
|
|||||||
|
|
||||||
async function associateReleaseTags(releases, type = 'release') {
|
async function associateReleaseTags(releases, type = 'release') {
|
||||||
if (releases.length === 0) {
|
if (releases.length === 0) {
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagIdsBySlug = await matchTags(releases.flatMap((release) => release.tags));
|
const tagIdsBySlug = await matchTags(releases.flatMap((release) => release.tags));
|
||||||
@@ -163,6 +163,18 @@ async function associateReleaseTags(releases, type = 'release') {
|
|||||||
const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type);
|
const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type);
|
||||||
|
|
||||||
await batchInsert(`${type}s_tags`, tagAssociations, { conflict: false });
|
await batchInsert(`${type}s_tags`, tagAssociations, { conflict: false });
|
||||||
|
|
||||||
|
return tagAssociations.reduce((acc, association) => {
|
||||||
|
if (!acc[association.release_id]) {
|
||||||
|
acc[association.release_id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (association.tag_id) {
|
||||||
|
acc[association.release_id].push(association.tag_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTag(tagId) {
|
async function fetchTag(tagId) {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { fetchActor, searchActors } = require('../actors');
|
|
||||||
|
|
||||||
async function fetchActorApi(req, res) {
|
|
||||||
const actor = await fetchActor(req.params.actorId);
|
|
||||||
|
|
||||||
if (actor) {
|
|
||||||
res.send({ actor });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).send({ actor: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchActorsApi(req, res) {
|
|
||||||
const query = req.query.query || req.query.q;
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
const actors = await searchActors(query, req.query.limit);
|
|
||||||
|
|
||||||
res.send({ actors });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send({ hint: 'specify a query or ID', actors: [] });
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fetchActor: fetchActorApi,
|
|
||||||
fetchActors: fetchActorsApi,
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { addAlert, removeAlert, updateNotifications, updateNotification } = require('../alerts');
|
|
||||||
|
|
||||||
async function addAlertApi(req, res) {
|
|
||||||
const alertId = await addAlert(req.body, req.session.user);
|
|
||||||
|
|
||||||
res.send({ id: alertId });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeAlertApi(req, res) {
|
|
||||||
await removeAlert(req.params.alertId);
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNotificationsApi(req, res) {
|
|
||||||
await updateNotifications(req.body, req.session.user);
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNotificationApi(req, res) {
|
|
||||||
await updateNotification(req.params.notificationId, req.body, req.session.user);
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
addAlert: addAlertApi,
|
|
||||||
removeAlert: removeAlertApi,
|
|
||||||
updateNotifications: updateNotificationsApi,
|
|
||||||
updateNotification: updateNotificationApi,
|
|
||||||
};
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { login, signup } = require('../auth');
|
|
||||||
const { fetchUser } = require('../users');
|
|
||||||
|
|
||||||
async function loginApi(req, res) {
|
|
||||||
const user = await login(req.body);
|
|
||||||
|
|
||||||
req.session.user = user;
|
|
||||||
res.send(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logoutApi(req, res) {
|
|
||||||
req.session.destroy((error) => {
|
|
||||||
if (error) {
|
|
||||||
res.status(500).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMeApi(req, res) {
|
|
||||||
if (req.session.user) {
|
|
||||||
req.session.user = await fetchUser(req.session.user.id, false, req.session.user);
|
|
||||||
|
|
||||||
res.send(req.session.user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(401).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function signupApi(req, res) {
|
|
||||||
const user = await signup(req.body);
|
|
||||||
|
|
||||||
req.session.user = user;
|
|
||||||
res.send(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
login: loginApi,
|
|
||||||
logout: logoutApi,
|
|
||||||
fetchMe: fetchMeApi,
|
|
||||||
signup: signupApi,
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { fetchEntity, fetchEntities, searchEntities } = require('../entities');
|
|
||||||
|
|
||||||
async function fetchEntityApi(req, res, type) {
|
|
||||||
const entity = await fetchEntity(req.params.entityId, type || req.query.type);
|
|
||||||
|
|
||||||
if (entity) {
|
|
||||||
res.send({ entity });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).send({ entity: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchEntitiesApi(req, res, type) {
|
|
||||||
const query = req.query.query || req.query.q;
|
|
||||||
|
|
||||||
const entities = query
|
|
||||||
? await searchEntities(query, type || req.query.type, req.query.limit)
|
|
||||||
: await fetchEntities(type || req.query.type, req.query.limit);
|
|
||||||
|
|
||||||
res.send({ entities });
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fetchEntity: fetchEntityApi,
|
|
||||||
fetchEntities: fetchEntitiesApi,
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const argv = require('../argv');
|
|
||||||
const logger = require('../logger')(__filename);
|
|
||||||
|
|
||||||
function errorHandler(error, req, res, _next) {
|
|
||||||
logger.warn(`Failed to fulfill request to ${req.path}: ${error.message}`);
|
|
||||||
|
|
||||||
if (argv.debug) {
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.httpCode) {
|
|
||||||
res.status(error.httpCode).send(error.message);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(500).send('Oops... our server messed up. We will be investigating this incident, our apologies for the inconvenience.');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = errorHandler;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
const { withPostGraphileContext } = require('postgraphile');
|
|
||||||
const { graphql } = require('graphql');
|
|
||||||
|
|
||||||
const initPg = require('./postgraphile');
|
|
||||||
const logger = require('../logger')(__filename);
|
|
||||||
|
|
||||||
async function query(graphqlQuery, params, role = 'query') {
|
|
||||||
const pg = initPg(config.database[role]);
|
|
||||||
|
|
||||||
return withPostGraphileContext(pg, async (context) => {
|
|
||||||
const schema = await pg.getGraphQLSchema();
|
|
||||||
const result = await graphql(schema, graphqlQuery, null, context, params);
|
|
||||||
|
|
||||||
if (result.errors?.length > 0) {
|
|
||||||
logger.error(result.errors);
|
|
||||||
|
|
||||||
throw result.errors[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { graphql: query };
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { makeExtendSchemaPlugin, gql } = require('graphile-utils');
|
|
||||||
const moment = require('moment');
|
|
||||||
const { cmToFeetInches, cmToInches, kgToLbs } = require('../../utils/convert');
|
|
||||||
|
|
||||||
const schemaExtender = makeExtendSchemaPlugin((_build) => ({
|
|
||||||
typeDefs: gql`
|
|
||||||
enum Units {
|
|
||||||
METRIC
|
|
||||||
IMPERIAL
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Actor {
|
|
||||||
isFavorited: Boolean @requires(columns: ["stashesActors"])
|
|
||||||
isStashed(includeFavorites: Boolean = false): Boolean @requires(columns: ["stashesActors"])
|
|
||||||
ageFromBirth: Int @requires(columns: ["dateOfBirth"])
|
|
||||||
ageAtDeath: Int @requires(columns: ["dateOfBirth", "dateOfDeath"])
|
|
||||||
height(units: Units): String @requires(columns: ["height"])
|
|
||||||
weight(units: Units): String @requires(columns: ["weight"])
|
|
||||||
penisLength(units: Units): String @requires(columns: ["penis_length"])
|
|
||||||
penisGirth(units: Units): String @requires(columns: ["penis_girth"])
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
resolvers: {
|
|
||||||
Actor: {
|
|
||||||
isFavorited(parent) {
|
|
||||||
if (!parent['@stashes'] || (parent['@stashes'].length > 0 && typeof parent['@stashes'][0]['@stash'].primary === 'undefined')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent['@stashes'].some(({ '@stash': stash }) => stash.primary);
|
|
||||||
},
|
|
||||||
isStashed(parent, args) {
|
|
||||||
if (!parent['@stashes'] || (parent['@stashes'].length > 0 && typeof parent['@stashes'][0]['@stash'].primary === 'undefined')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.includeFavorites) {
|
|
||||||
return parent['@stashes'].length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent['@stashes'].some(({ '@stash': stash }) => !stash.primary);
|
|
||||||
},
|
|
||||||
ageFromBirth(parent, _args, _context, _info) {
|
|
||||||
if (!parent.dateOfBirth) return null;
|
|
||||||
|
|
||||||
return moment().diff(parent.dateOfBirth, 'years');
|
|
||||||
},
|
|
||||||
ageAtDeath(parent, _args, _context, _info) {
|
|
||||||
if (!parent.dateOfDeath) return null;
|
|
||||||
|
|
||||||
return moment(parent.dateOfDeath).diff(parent.dateOfBirth, 'years');
|
|
||||||
},
|
|
||||||
height(parent, args, _context, _info) {
|
|
||||||
if (!parent.height) return null;
|
|
||||||
|
|
||||||
if (args.units === 'IMPERIAL') {
|
|
||||||
const { feet, inches } = cmToFeetInches(parent.height);
|
|
||||||
return `${feet}' ${inches}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent.height.toString();
|
|
||||||
},
|
|
||||||
weight(parent, args, _context, _info) {
|
|
||||||
if (!parent.weight) return null;
|
|
||||||
|
|
||||||
return args.units === 'IMPERIAL'
|
|
||||||
? kgToLbs(parent.weight).toString()
|
|
||||||
: parent.weight.toString();
|
|
||||||
},
|
|
||||||
penisLength(parent, args, _context, _info) {
|
|
||||||
if (!parent.penisLength) return null;
|
|
||||||
|
|
||||||
return args.units === 'IMPERIAL'
|
|
||||||
? (Math.round(cmToInches(parent.penisLength) * 4) / 4).toString() // round to nearest quarter inch
|
|
||||||
: parent.penisLength.toString();
|
|
||||||
},
|
|
||||||
penisGirth(parent, args, _context, _info) {
|
|
||||||
if (!parent.penisGirth) return null;
|
|
||||||
|
|
||||||
return args.units === 'IMPERIAL'
|
|
||||||
? (Math.round(cmToInches(parent.penisGirth) * 4) / 4).toString() // round to nearest quarter inch
|
|
||||||
: parent.penisGirth.toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
module.exports = [schemaExtender];
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
const { makeExtendSchemaPlugin, gql } = require('graphile-utils');
|
|
||||||
|
|
||||||
const schemaExtender = makeExtendSchemaPlugin((_build) => ({
|
|
||||||
typeDefs: gql`
|
|
||||||
extend type Media {
|
|
||||||
thumbnailWidth: Int @requires(columns: ["width", "height"])
|
|
||||||
thumbnailHeight: Int @requires(columns: ["height", "width"])
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
resolvers: {
|
|
||||||
Media: {
|
|
||||||
thumbnailWidth(parent, _args, _context, _info) {
|
|
||||||
if (!parent.width || !parent.height) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.height <= config.media.thumbnailSize) {
|
|
||||||
// thumbnails aren't upscaled
|
|
||||||
return parent.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(parent.width / (parent.height / config.media.thumbnailSize));
|
|
||||||
},
|
|
||||||
thumbnailHeight(parent, _args, _context, _info) {
|
|
||||||
if (!parent.width || !parent.height) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.height <= config.media.thumbnailSize) {
|
|
||||||
// thumbnails aren't upscaled
|
|
||||||
return parent.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.media.thumbnailSize;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
module.exports = [schemaExtender];
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const ActorPlugins = require('./actors');
|
|
||||||
const SitePlugins = require('./sites');
|
|
||||||
const ReleasePlugins = require('./releases');
|
|
||||||
const MediaPlugins = require('./media');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
ActorPlugins,
|
|
||||||
SitePlugins,
|
|
||||||
ReleasePlugins,
|
|
||||||
MediaPlugins,
|
|
||||||
};
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { makeExtendSchemaPlugin, gql } = require('graphile-utils');
|
|
||||||
|
|
||||||
function isFavorited(parent) {
|
|
||||||
if (!parent['@stashes'] || (parent['@stashes'].length > 0 && typeof parent['@stashes'][0]['@stash'].primary === 'undefined')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent['@stashes'].some(({ '@stash': stash }) => stash.primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStashed(parent, args) {
|
|
||||||
if (!parent['@stashes'] || (parent['@stashes'].length > 0 && typeof parent['@stashes'][0]['@stash'].primary === 'undefined')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.includeFavorites) {
|
|
||||||
return parent['@stashes'].length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent['@stashes'].some(({ '@stash': stash }) => !stash.primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemaExtender = makeExtendSchemaPlugin((_build) => ({
|
|
||||||
typeDefs: gql`
|
|
||||||
extend type Release {
|
|
||||||
isFavorited: Boolean @requires(columns: ["stashesScenesBySceneId"])
|
|
||||||
isStashed(includeFavorites: Boolean = false): Boolean @requires(columns: ["stashesScenesBySceneId"])
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Movie {
|
|
||||||
isFavorited: Boolean @requires(columns: ["stashesMovies"])
|
|
||||||
isStashed(includeFavorites: Boolean = false): Boolean @requires(columns: ["stashesMovies"])
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
resolvers: {
|
|
||||||
Release: {
|
|
||||||
isFavorited,
|
|
||||||
isStashed,
|
|
||||||
},
|
|
||||||
Movie: {
|
|
||||||
isFavorited,
|
|
||||||
isStashed,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
module.exports = [schemaExtender];
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { makeExtendSchemaPlugin, gql } = require('graphile-utils');
|
|
||||||
|
|
||||||
const schemaExtender = makeExtendSchemaPlugin((_build) => ({
|
|
||||||
typeDefs: gql`
|
|
||||||
extend type Site {
|
|
||||||
independent: Boolean @requires(columns: ["parameters"])
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
resolvers: {
|
|
||||||
Site: {
|
|
||||||
independent(parent, _args, _context, _info) {
|
|
||||||
return !!parent.parameters?.independent;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
module.exports = [schemaExtender];
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* eslint-disable arrow-body-style */
|
|
||||||
const config = require('config');
|
|
||||||
const { postgraphile } = require('postgraphile');
|
|
||||||
|
|
||||||
const PgConnectionFilterPlugin = require('postgraphile-plugin-connection-filter');
|
|
||||||
const PgSimplifyInflectorPlugin = require('@graphile-contrib/pg-simplify-inflector');
|
|
||||||
const PgOrderByRelatedPlugin = require('@graphile-contrib/pg-order-by-related');
|
|
||||||
|
|
||||||
const { ActorPlugins, SitePlugins, ReleasePlugins, MediaPlugins } = require('./plugins/plugins');
|
|
||||||
|
|
||||||
async function pgSettings(req) {
|
|
||||||
return {
|
|
||||||
'user.id': req.session.user?.id || null, // undefined is passed as an empty string, avoid
|
|
||||||
statement_timeout: config.database.timeout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPostgraphile(credentials) {
|
|
||||||
const connectionString = `postgres://${credentials.user}:${credentials.password}@${credentials.host}:5432/${credentials.database}`;
|
|
||||||
|
|
||||||
return postgraphile(
|
|
||||||
connectionString,
|
|
||||||
'public',
|
|
||||||
{
|
|
||||||
// watchPg: true,
|
|
||||||
disableDefaultMutations: true,
|
|
||||||
dynamicJson: true,
|
|
||||||
graphiql: config.database.graphiql,
|
|
||||||
enhanceGraphiql: true,
|
|
||||||
allowExplain: () => true,
|
|
||||||
// simpleCollections: 'only',
|
|
||||||
simpleCollections: 'both',
|
|
||||||
graphileBuildOptions: {
|
|
||||||
pgOmitListSuffix: true,
|
|
||||||
// connectionFilterUseListInflectors: true,
|
|
||||||
connectionFilterRelations: true,
|
|
||||||
connectionFilterAllowNullInput: true,
|
|
||||||
},
|
|
||||||
appendPlugins: [
|
|
||||||
PgSimplifyInflectorPlugin,
|
|
||||||
PgConnectionFilterPlugin,
|
|
||||||
PgOrderByRelatedPlugin,
|
|
||||||
...ActorPlugins,
|
|
||||||
...SitePlugins,
|
|
||||||
...ReleasePlugins,
|
|
||||||
...MediaPlugins,
|
|
||||||
],
|
|
||||||
pgSettings,
|
|
||||||
},
|
|
||||||
pgSettings,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = initPostgraphile;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const { fetchScene, fetchScenes, searchScenes } = require('../releases');
|
|
||||||
|
|
||||||
async function fetchSceneApi(req, res) {
|
|
||||||
const release = await fetchScene(req.params.releaseId);
|
|
||||||
|
|
||||||
if (release) {
|
|
||||||
res.send({ scene: release });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).send({ scene: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchScenesApi(req, res) {
|
|
||||||
const query = req.query.query || req.query.q;
|
|
||||||
const limit = req.query.limit && Number(req.query.limit);
|
|
||||||
const relevance = req.query.relevance && Number(req.query.relevance);
|
|
||||||
|
|
||||||
const releases = query
|
|
||||||
? await searchScenes(query, limit, relevance)
|
|
||||||
: await fetchScenes(req.query.limit);
|
|
||||||
|
|
||||||
res.send({ scenes: releases });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchScenePosterApi(req, res) {
|
|
||||||
const scene = await fetchScene(req.params.releaseId);
|
|
||||||
const posterPath = scene?.poster?.path;
|
|
||||||
|
|
||||||
if (posterPath) {
|
|
||||||
res.sendFile(path.resolve(config.media.path, posterPath));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fetchScene: fetchSceneApi,
|
|
||||||
fetchScenes: fetchScenesApi,
|
|
||||||
fetchScenePoster: fetchScenePosterApi,
|
|
||||||
};
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('config');
|
|
||||||
const express = require('express');
|
|
||||||
const Router = require('express-promise-router');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const session = require('express-session');
|
|
||||||
const KnexSessionStore = require('connect-session-knex')(session);
|
|
||||||
const { nanoid } = require('nanoid');
|
|
||||||
|
|
||||||
const logger = require('../logger')(__filename);
|
|
||||||
const knex = require('../knex');
|
|
||||||
const errorHandler = require('./error');
|
|
||||||
|
|
||||||
const initPg = require('./postgraphile');
|
|
||||||
|
|
||||||
const {
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
signup,
|
|
||||||
fetchMe,
|
|
||||||
} = require('./auth');
|
|
||||||
|
|
||||||
const {
|
|
||||||
fetchScene,
|
|
||||||
fetchScenes,
|
|
||||||
fetchScenePoster,
|
|
||||||
} = require('./releases');
|
|
||||||
|
|
||||||
const {
|
|
||||||
fetchActor,
|
|
||||||
fetchActors,
|
|
||||||
} = require('./actors');
|
|
||||||
|
|
||||||
const {
|
|
||||||
fetchEntity,
|
|
||||||
fetchEntities,
|
|
||||||
} = require('./entities');
|
|
||||||
|
|
||||||
const {
|
|
||||||
fetchTag,
|
|
||||||
fetchTags,
|
|
||||||
} = require('./tags');
|
|
||||||
|
|
||||||
const {
|
|
||||||
createStash,
|
|
||||||
removeStash,
|
|
||||||
stashActor,
|
|
||||||
stashScene,
|
|
||||||
stashMovie,
|
|
||||||
unstashActor,
|
|
||||||
unstashScene,
|
|
||||||
unstashMovie,
|
|
||||||
updateStash,
|
|
||||||
} = require('./stashes');
|
|
||||||
|
|
||||||
const {
|
|
||||||
addAlert,
|
|
||||||
removeAlert,
|
|
||||||
updateNotifications,
|
|
||||||
updateNotification,
|
|
||||||
} = require('./alerts');
|
|
||||||
|
|
||||||
function getIp(req) {
|
|
||||||
return req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'].split(',')[0] : req.connection.remoteAddress; // See src/ws
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initServer() {
|
|
||||||
const app = express();
|
|
||||||
const router = Router();
|
|
||||||
const store = new KnexSessionStore({ knex });
|
|
||||||
|
|
||||||
app.set('view engine', 'ejs');
|
|
||||||
app.disable('x-powered-by');
|
|
||||||
|
|
||||||
router.use('/media', express.static(config.media.path));
|
|
||||||
router.use(express.static('public'));
|
|
||||||
|
|
||||||
router.use('/img', (_req, res) => {
|
|
||||||
res.status(404).send();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use(bodyParser.json({ strict: false }));
|
|
||||||
router.use(session({ ...config.web.session, store }));
|
|
||||||
|
|
||||||
router.use(initPg(config.database.query));
|
|
||||||
|
|
||||||
router.use((req, _res, next) => {
|
|
||||||
req.session.safeId = req.session.safeId || nanoid();
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use((req, _res, next) => {
|
|
||||||
const ip = getIp(req);
|
|
||||||
|
|
||||||
logger.silly(`${ip} (${req.headers['CF-IPCountry'] || 'country N/A'}) requested ${req.originalUrl} as ${req.session.user ? `${req.session.user.username} (${req.session.user.id})` : 'guest'}`);
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/api/session', fetchMe);
|
|
||||||
router.post('/api/session', login);
|
|
||||||
router.delete('/api/session', logout);
|
|
||||||
|
|
||||||
router.post('/api/users', signup);
|
|
||||||
|
|
||||||
router.patch('/api/users/:userId/notifications', updateNotifications);
|
|
||||||
router.patch('/api/users/:userId/notifications/:notificationId', updateNotification);
|
|
||||||
|
|
||||||
router.post('/api/stashes', createStash);
|
|
||||||
router.patch('/api/stashes/:stashId', updateStash);
|
|
||||||
router.delete('/api/stashes/:stashId', removeStash);
|
|
||||||
|
|
||||||
router.post('/api/stashes/:stashId/actors', stashActor);
|
|
||||||
router.post('/api/stashes/:stashId/scenes', stashScene);
|
|
||||||
router.post('/api/stashes/:stashId/movies', stashMovie);
|
|
||||||
|
|
||||||
router.delete('/api/stashes/:stashId/actors/:actorId', unstashActor);
|
|
||||||
router.delete('/api/stashes/:stashId/scenes/:sceneId', unstashScene);
|
|
||||||
router.delete('/api/stashes/:stashId/movies/:movieId', unstashMovie);
|
|
||||||
|
|
||||||
router.post('/api/alerts', addAlert);
|
|
||||||
router.delete('/api/alerts/:alertId', removeAlert);
|
|
||||||
|
|
||||||
router.get('/api/scenes', fetchScenes);
|
|
||||||
router.get('/api/scenes/:releaseId', fetchScene);
|
|
||||||
router.get('/api/scenes/:releaseId/poster', fetchScenePoster);
|
|
||||||
|
|
||||||
// router.get('/api/movies', fetchMovies);
|
|
||||||
// router.get('/api/movies/:releaseId', fetchMovie);
|
|
||||||
|
|
||||||
router.get('/api/actors', fetchActors);
|
|
||||||
router.get('/api/actors/:actorId', fetchActor);
|
|
||||||
|
|
||||||
router.get('/api/entities', async (req, res) => fetchEntities(req, res, null));
|
|
||||||
router.get('/api/entities/:entityId', async (req, res) => fetchEntity(req, res, null));
|
|
||||||
|
|
||||||
router.get('/api/channels', async (req, res) => fetchEntities(req, res, 'channel'));
|
|
||||||
router.get('/api/channels/:entityId', async (req, res) => fetchEntity(req, res, 'channel'));
|
|
||||||
|
|
||||||
router.get('/api/networks', async (req, res) => fetchEntities(req, res, 'network'));
|
|
||||||
router.get('/api/networks/:entityId', async (req, res) => fetchEntity(req, res, 'network'));
|
|
||||||
|
|
||||||
router.get('/api/studios', async (req, res) => fetchEntities(req, res, 'studio'));
|
|
||||||
router.get('/api/studios/:entityId', async (req, res) => fetchEntity(req, res, 'studio'));
|
|
||||||
|
|
||||||
router.get('/api/tags', fetchTags);
|
|
||||||
router.get('/api/tags/:tagId', fetchTag);
|
|
||||||
|
|
||||||
router.get('*', (req, res) => {
|
|
||||||
res.render(path.join(__dirname, '../../assets/index.ejs'), {
|
|
||||||
analytics: config.analytics,
|
|
||||||
env: JSON.stringify({
|
|
||||||
sfw: !!req.headers.sfw || Object.prototype.hasOwnProperty.call(req.query, 'sfw'),
|
|
||||||
login: config.auth.login,
|
|
||||||
signup: config.auth.signup,
|
|
||||||
sessionId: req.session.safeId,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use(errorHandler);
|
|
||||||
app.use(router);
|
|
||||||
|
|
||||||
const server = app.listen(config.web.port, config.web.host, () => {
|
|
||||||
const { address, port } = server.address();
|
|
||||||
|
|
||||||
logger.info(`Web server listening on ${address}:${port}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = initServer;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { fetchSites, fetchSitesFromReleases } = require('../sites');
|
|
||||||
|
|
||||||
async function fetchSitesApi(req, res) {
|
|
||||||
const siteId = typeof req.params.siteId === 'number' ? req.params.siteId : undefined;
|
|
||||||
const siteSlug = typeof req.params.siteId === 'string' ? req.params.siteId : undefined;
|
|
||||||
|
|
||||||
const sites = await fetchSites({
|
|
||||||
id: siteId,
|
|
||||||
slug: siteSlug,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send(sites);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSitesFromReleasesApi(req, res) {
|
|
||||||
const sites = await fetchSitesFromReleases();
|
|
||||||
|
|
||||||
res.send(sites);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fetchSites: fetchSitesApi,
|
|
||||||
fetchSitesFromReleases: fetchSitesFromReleasesApi,
|
|
||||||
};
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const {
|
|
||||||
createStash,
|
|
||||||
removeStash,
|
|
||||||
stashActor,
|
|
||||||
stashScene,
|
|
||||||
stashMovie,
|
|
||||||
unstashActor,
|
|
||||||
unstashScene,
|
|
||||||
unstashMovie,
|
|
||||||
updateStash,
|
|
||||||
} = require('../stashes');
|
|
||||||
|
|
||||||
async function createStashApi(req, res) {
|
|
||||||
const stash = await createStash(req.body, req.session.user);
|
|
||||||
|
|
||||||
res.send(stash);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateStashApi(req, res) {
|
|
||||||
const stash = await updateStash(req.params.stashId, req.body, req.session.user);
|
|
||||||
|
|
||||||
res.send(stash);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeStashApi(req, res) {
|
|
||||||
await removeStash(req.params.stashId, req.session.user);
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stashActorApi(req, res) {
|
|
||||||
const stashes = await stashActor(req.body.actorId, Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stashSceneApi(req, res) {
|
|
||||||
const stashes = await stashScene(req.body.sceneId, Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stashMovieApi(req, res) {
|
|
||||||
const stashes = await stashMovie(req.body.movieId, Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unstashActorApi(req, res) {
|
|
||||||
const stashes = await unstashActor(Number(req.params.actorId), Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unstashSceneApi(req, res) {
|
|
||||||
const stashes = await unstashScene(Number(req.params.sceneId), Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unstashMovieApi(req, res) {
|
|
||||||
const stashes = await unstashMovie(Number(req.params.movieId), Number(req.params.stashId), req.session.user);
|
|
||||||
|
|
||||||
res.send(stashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createStash: createStashApi,
|
|
||||||
removeStash: removeStashApi,
|
|
||||||
stashActor: stashActorApi,
|
|
||||||
stashScene: stashSceneApi,
|
|
||||||
stashMovie: stashMovieApi,
|
|
||||||
unstashActor: unstashActorApi,
|
|
||||||
unstashScene: unstashSceneApi,
|
|
||||||
unstashMovie: unstashMovieApi,
|
|
||||||
updateStash: updateStashApi,
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { fetchTag, fetchTags } = require('../tags');
|
|
||||||
|
|
||||||
async function fetchTagApi(req, res) {
|
|
||||||
const tag = await fetchTag(req.params.tagId);
|
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
res.send({ tag });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).send({ tag: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchTagsApi(req, res) {
|
|
||||||
const tags = await fetchTags(req.query.limit);
|
|
||||||
|
|
||||||
res.send({ tags });
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fetchTag: fetchTagApi,
|
|
||||||
fetchTags: fetchTagsApi,
|
|
||||||
};
|
|
||||||
@@ -264,6 +264,7 @@ const actors = [
|
|||||||
{ entity: 'theflourishxxx', name: 'XWifeKaren', fields: ['avatar', 'description'] },
|
{ entity: 'theflourishxxx', name: 'XWifeKaren', fields: ['avatar', 'description'] },
|
||||||
{ entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] },
|
{ entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] },
|
||||||
{ entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] },
|
{ entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] },
|
||||||
|
{ entity: 'darkkotv', name: 'Aidra Fox', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'measurements', 'naturalBoobs', 'hasTattoos', 'hasPiercings'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const actorScrapers = scrapers.actors;
|
const actorScrapers = scrapers.actors;
|
||||||
|
|||||||
Reference in New Issue
Block a user