diff --git a/assets/index.ejs b/assets/index.ejs
deleted file mode 100755
index 46f0b427..00000000
--- a/assets/index.ejs
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
- traxxx
-
-
-
-
-
-
-
-
-
-
-
-
-
- <% if (analytics.enabled) { %>
-
- <% } %>
-
-
-
-
-
-
-
-
diff --git a/config/default.js b/config/default.js
index a0fff475..639ab1a9 100755
--- a/config/default.js
+++ b/config/default.js
@@ -27,21 +27,6 @@ module.exports = {
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: {
host: 'localhost',
port: 6379,
diff --git a/package-lock.json b/package-lock.json
index 168d5258..ca101a71 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -94,7 +94,7 @@
"tunnel": "0.0.6",
"ua-parser-js": "^1.0.37",
"undici": "^5.28.1",
- "unprint": "^0.19.11",
+ "unprint": "^0.19.13",
"url-pattern": "^1.0.3",
"v-tooltip": "^2.1.3",
"video.js": "^8.6.1",
@@ -20668,9 +20668,9 @@
}
},
"node_modules/unprint": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.19.11.tgz",
- "integrity": "sha512-k+7zVUiviO8OpIvyrk/WGtzoEYRRLcru5wB+AChH7G8xlCDm46cE8LFqi5dGRt7+3iHbVfLSaN3i6ZbBoGuiUw==",
+ "version": "0.19.13",
+ "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.19.13.tgz",
+ "integrity": "sha512-HPNCQn2CziiGeK0JSZg/5E+G2prHme+8lDojxd16wUwSQ0mgW4nZq4LOuVMIRRAFm1M1nkju0oMIdsj4uRFASw==",
"dependencies": {
"bottleneck": "^2.19.5",
"cookie": "^1.1.1",
diff --git a/package.json b/package.json
index da890cac..8fd3c076 100755
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
"tunnel": "0.0.6",
"ua-parser-js": "^1.0.37",
"undici": "^5.28.1",
- "unprint": "^0.19.11",
+ "unprint": "^0.19.13",
"url-pattern": "^1.0.3",
"v-tooltip": "^2.1.3",
"video.js": "^8.6.1",
diff --git a/seeds/02_sites.js b/seeds/02_sites.js
index 294cb07d..fc980516 100755
--- a/seeds/02_sites.js
+++ b/seeds/02_sites.js
@@ -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.",
parent: 'cumlouder',
},
+ // DARKKO TV
+ {
+ name: 'Darkko TV',
+ slug: 'darkkotv',
+ url: 'https://darkkotv.com',
+ },
// PORN WORLD / DDF NETWORK
{
slug: 'pornworld',
diff --git a/src/app.js b/src/app.js
index 53bb8e95..0176c521 100755
--- a/src/app.js
+++ b/src/app.js
@@ -11,7 +11,6 @@ const fs = require('fs').promises;
const { format, intervalToDuration } = require('date-fns');
const argv = require('./argv');
-const initServer = require('./web/server');
const http = require('./utils/http');
const logger = require('./logger')(__filename);
@@ -130,11 +129,6 @@ async function init() {
try {
await redis.connect();
- if (argv.server) {
- await initServer();
- return;
- }
-
if (argv.sampleMemory) {
await startMemorySample(config.memorySampling.snapshotIntervals);
}
diff --git a/src/releases.js b/src/releases.js
index 22b8bd06..ff661fe7 100755
--- a/src/releases.js
+++ b/src/releases.js
@@ -8,117 +8,6 @@ const argv = require('./argv');
const { updateSceneSearch } = require('./update-search');
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) {
if (!release) {
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) {
if (sceneIds.length === 0) {
return 0;
@@ -483,13 +271,10 @@ async function flushBatches(batchIds) {
module.exports = {
curateRelease,
- fetchScene,
- fetchScenes,
flushBatches,
flushMovies,
flushSeries,
flushScenes,
- searchScenes,
deleteScenes,
deleteMovies,
deleteSeries,
diff --git a/src/scrapers/actors.js b/src/scrapers/actors.js
index 6fca5fd2..5974393e 100644
--- a/src/scrapers/actors.js
+++ b/src/scrapers/actors.js
@@ -11,6 +11,7 @@ const bradmontana = require('./bradmontana');
const cherrypimps = require('./cherrypimps');
const cumlouder = require('./cumlouder');
const modelmedia = require('./modelmedia');
+const darkkotv = require('./darkkotv');
const dorcel = require('./dorcel');
// const famedigital = require('./famedigital');
const firstanalquest = require('./firstanalquest');
@@ -223,6 +224,7 @@ module.exports = {
bradmontana,
cherrypimps,
cumlouder,
+ darkkotv,
dorcelclub: dorcel,
freeones,
hitzefrei,
diff --git a/src/scrapers/darkkotv.js b/src/scrapers/darkkotv.js
new file mode 100755
index 00000000..fe1e93fa
--- /dev/null
+++ b/src/scrapers/darkkotv.js
@@ -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,
+};
diff --git a/src/scrapers/releases.js b/src/scrapers/releases.js
index 887f552f..8d848cf6 100644
--- a/src/scrapers/releases.js
+++ b/src/scrapers/releases.js
@@ -16,6 +16,7 @@ const cherrypimps = require('./cherrypimps');
const cliffmedia = require('./cliffmedia');
const cumlouder = require('./cumlouder');
const czechav = require('./czechav');
+const darkkotv = require('./darkkotv');
const modelmedia = require('./modelmedia');
const dorcel = require('./dorcel');
const fabulouscash = require('./fabulouscash');
@@ -118,6 +119,7 @@ module.exports = {
cumlouder,
czechav,
pornworld,
+ darkkotv,
delphine: modelmedia,
dorcel,
elegantangel: adultempire,
diff --git a/src/scrapers/template.js b/src/scrapers/template.js
index 33c57c36..217bf0f8 100755
--- a/src/scrapers/template.js
+++ b/src/scrapers/template.js
@@ -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 }) {
const release = {};
@@ -62,17 +73,6 @@ function scrapeProfile({ query }) {
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) {
const url = `${entity.url}/actors/${slugify(actorName, '_')}`;
const res = await unprint.get(url);
diff --git a/src/store-releases.js b/src/store-releases.js
index ebdc400a..5eec5788 100755
--- a/src/store-releases.js
+++ b/src/store-releases.js
@@ -431,6 +431,7 @@ async function storeScenes(releases, useBatchId) {
const uniqueReleasesWithId = attachReleaseIds(uniqueReleases, storedReleaseEntries, batchId);
const duplicateReleasesWithId = attachReleaseIds(duplicateReleases, duplicateReleaseEntries, batchId);
const curatedDuplicateReleases = await Promise.all(duplicateReleasesWithId.map((release) => curateReleaseEntry(release, batchId)));
+
const releasesWithId = uniqueReleasesWithId.concat(duplicateReleasesWithId);
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)
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
+ RETURNING releases.*
`, {
scenes: JSON.stringify(chunkedReleases),
})));
diff --git a/src/tags.js b/src/tags.js
index ce226de0..d74dcb51 100755
--- a/src/tags.js
+++ b/src/tags.js
@@ -154,7 +154,7 @@ function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntit
async function associateReleaseTags(releases, type = 'release') {
if (releases.length === 0) {
- return;
+ return {};
}
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);
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) {
diff --git a/src/web/actors.js b/src/web/actors.js
deleted file mode 100755
index 8bfe40d1..00000000
--- a/src/web/actors.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/alerts.js b/src/web/alerts.js
deleted file mode 100755
index 1d0cfced..00000000
--- a/src/web/alerts.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/auth.js b/src/web/auth.js
deleted file mode 100755
index 4894c9c8..00000000
--- a/src/web/auth.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/entities.js b/src/web/entities.js
deleted file mode 100755
index 62b94a13..00000000
--- a/src/web/entities.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/error.js b/src/web/error.js
deleted file mode 100755
index b058768d..00000000
--- a/src/web/error.js
+++ /dev/null
@@ -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;
diff --git a/src/web/graphql.js b/src/web/graphql.js
deleted file mode 100755
index 32e5be19..00000000
--- a/src/web/graphql.js
+++ /dev/null
@@ -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 };
diff --git a/src/web/plugins/actors.js b/src/web/plugins/actors.js
deleted file mode 100755
index b4ff58fd..00000000
--- a/src/web/plugins/actors.js
+++ /dev/null
@@ -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];
diff --git a/src/web/plugins/media.js b/src/web/plugins/media.js
deleted file mode 100755
index b6f52719..00000000
--- a/src/web/plugins/media.js
+++ /dev/null
@@ -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];
diff --git a/src/web/plugins/plugins.js b/src/web/plugins/plugins.js
deleted file mode 100755
index 36e97fba..00000000
--- a/src/web/plugins/plugins.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/plugins/releases.js b/src/web/plugins/releases.js
deleted file mode 100755
index f0666a59..00000000
--- a/src/web/plugins/releases.js
+++ /dev/null
@@ -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];
diff --git a/src/web/plugins/sites.js b/src/web/plugins/sites.js
deleted file mode 100755
index e858bab8..00000000
--- a/src/web/plugins/sites.js
+++ /dev/null
@@ -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];
diff --git a/src/web/postgraphile.js b/src/web/postgraphile.js
deleted file mode 100755
index 52be0e82..00000000
--- a/src/web/postgraphile.js
+++ /dev/null
@@ -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;
diff --git a/src/web/releases.js b/src/web/releases.js
deleted file mode 100755
index df3a9873..00000000
--- a/src/web/releases.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/server.js b/src/web/server.js
deleted file mode 100755
index a0867abe..00000000
--- a/src/web/server.js
+++ /dev/null
@@ -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;
diff --git a/src/web/sites.js b/src/web/sites.js
deleted file mode 100755
index f5aebc8a..00000000
--- a/src/web/sites.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/stashes.js b/src/web/stashes.js
deleted file mode 100755
index 62640d90..00000000
--- a/src/web/stashes.js
+++ /dev/null
@@ -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,
-};
diff --git a/src/web/tags.js b/src/web/tags.js
deleted file mode 100755
index 0e3c797a..00000000
--- a/src/web/tags.js
+++ /dev/null
@@ -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,
-};
diff --git a/tests/profiles.js b/tests/profiles.js
index 34edaa57..8cfdf933 100644
--- a/tests/profiles.js
+++ b/tests/profiles.js
@@ -264,6 +264,7 @@ const actors = [
{ 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: '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;