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;