Added georestriction with SFW mode.
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
"no-console": 0,
|
||||
"no-param-reassign": ["error", {
|
||||
"props": true,
|
||||
"ignorePropertyModificationsFor": ["state", "acc"]
|
||||
"ignorePropertyModificationsFor": ["state", "acc", "req"]
|
||||
}],
|
||||
"vue/multi-word-component-names": 0,
|
||||
"vue/no-reserved-component-names": 0,
|
||||
@@ -32,7 +32,8 @@
|
||||
"vue/html-indent": ["error", "tab"],
|
||||
"vue/multiline-html-element-content-newline": 0,
|
||||
"vue/no-v-html": 0,
|
||||
"vue/singleline-html-element-content-newline": 0
|
||||
"vue/singleline-html-element-content-newline": 0,
|
||||
"vue/comment-directive": 0,
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ config/*
|
||||
log/
|
||||
/media
|
||||
data/
|
||||
assets/*.mmdb
|
||||
assets/.geoipupdate.lock
|
||||
|
||||
125
assets/sfw.ejs
Normal file
125
assets/sfw.ejs
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="/img/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="/img/favicon/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="/img/favicon/favicon.ico">
|
||||
|
||||
<meta name="msapplication-TileColor" content="#b91d47">
|
||||
<meta name="msapplication-config" content="/img/favicon/browserconfig.xml">
|
||||
<meta name="theme-color" content="#f65596">
|
||||
|
||||
<meta property="og:title" content="traxxx" />
|
||||
<meta property="og:image" content="https://traxxx.me/img/og_logo.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,interactive-widget=resizes-content" />
|
||||
|
||||
<title>traxxx - None shall pass</title>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-dark-10: #e54485;
|
||||
--primary: #f65596;
|
||||
--primary-light-10: #f075a6;
|
||||
--primary-light-20: #f2a6c4;
|
||||
--primary-light-30: #f7c9dc;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.explainer {
|
||||
margin-bottom: 3rem;
|
||||
font-size: 1.25rem;
|
||||
text-align: justify;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.useful {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
.links li {
|
||||
padding: .25rem 0;
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.links a {
|
||||
padding: .5rem 1rem;
|
||||
border-radius: .5rem;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
background: var(--primary-dark-10);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h2 class="heading">Not so fast, rascal.</h2>
|
||||
|
||||
<p class="explainer">The content offered by traxxx is restricted in your jurisdiction.</p>
|
||||
|
||||
<% if (!noVpn) { %>
|
||||
<div class="useful">
|
||||
Useful links:
|
||||
<ul class="links">
|
||||
<li>
|
||||
<a
|
||||
href="https://mullvad.net/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="link"
|
||||
>Mullvad VPN</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="https://protonvpn.com/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="link"
|
||||
>Proton VPN</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,7 +11,7 @@
|
||||
class="avatar-link no-link"
|
||||
>
|
||||
<img
|
||||
v-if="actor.avatar"
|
||||
v-if="actor.avatar && !restriction"
|
||||
:src="getPath(actor.avatar, 'thumbnail')"
|
||||
:style="{ 'background-image': `url(${getPath(actor.avatar, 'lazy')})` }"
|
||||
loading="lazy"
|
||||
@@ -103,7 +103,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { user } = pageContext;
|
||||
const { user, restriction } = pageContext;
|
||||
const pageStash = pageContext.pageProps.stash;
|
||||
const currentStash = pageStash || pageContext.assets?.primaryStash;
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="restriction"
|
||||
class="restricted"
|
||||
>
|
||||
<div>Traxxx is restricted in your region</div>
|
||||
<a
|
||||
href="/sfw"
|
||||
class="link"
|
||||
>Learn more</a>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
v-if="campaign?.banner?.type === 'html'"
|
||||
v-else-if="campaign?.banner?.type === 'html'"
|
||||
ref="iframe"
|
||||
:width="campaign.banner.width"
|
||||
:height="campaign.banner.height"
|
||||
@@ -31,6 +42,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { restriction } = pageContext;
|
||||
|
||||
const props = defineProps({
|
||||
campaign: {
|
||||
type: Object,
|
||||
@@ -75,4 +91,15 @@ const bannerSrc = (() => {
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.restricted {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: .5rem;
|
||||
font-weight: bold;
|
||||
padding: .5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -130,7 +130,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const user = pageContext.user;
|
||||
const { user, restriction } = pageContext;
|
||||
const pageStash = pageContext.pageProps.stash;
|
||||
const currentStash = pageStash || pageContext.assets?.primaryStash;
|
||||
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
v-for="photo in photos"
|
||||
:key="`photo-${photo.id}`"
|
||||
:title="photo.comment"
|
||||
:href="`/img/${photo.path}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="photo-container"
|
||||
>
|
||||
<img
|
||||
:src="`/${photo.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/${photo.lazy})` }"
|
||||
:src="getPath(photo, 'thumbnail')"
|
||||
:style="{ 'background-image': `url(${getPath(photo, 'lazy')})` }"
|
||||
:alt="photo.comment"
|
||||
:width="photo.width"
|
||||
:height="photo.height"
|
||||
@@ -47,6 +46,8 @@ import { computed, inject } from 'vue';
|
||||
import Logo from '#/components/tags/logo.vue';
|
||||
import Campaign from '#/components/campaigns/campaign.vue';
|
||||
|
||||
import getPath from '#/src/get-path.js';
|
||||
|
||||
const props = defineProps({
|
||||
tag: {
|
||||
type: Object,
|
||||
|
||||
@@ -58,6 +58,91 @@ module.exports = {
|
||||
address: 'http://localhost:3000/script.js',
|
||||
siteId: '1b28ac3b-d229-43bf-aec9-75cf0a72a466',
|
||||
},
|
||||
restrictions: {
|
||||
enabled: false,
|
||||
modes: [
|
||||
null, // easier for 0 to mean disabled
|
||||
'block', // 1
|
||||
'censor', // 2
|
||||
],
|
||||
regions: {
|
||||
// Europe
|
||||
DE: 1, // Germany
|
||||
FR: 1, // France
|
||||
GB: 1, // Great Britain / United Kingdom
|
||||
IT: 1, // Italy
|
||||
// Asia & Oceania
|
||||
AU: 1, // Australia
|
||||
CN: 1, // China
|
||||
// Americas
|
||||
US: {
|
||||
AL: 1, // Alabama
|
||||
AR: 1, // Arkansas
|
||||
AZ: 1, // Arizona
|
||||
FL: 1, // Florida
|
||||
GA: 1, // Georgia
|
||||
ID: 1, // Idaho
|
||||
IN: 1, // Indiana
|
||||
KS: 1, // Kansas
|
||||
KY: 1, // Kentucky
|
||||
LA: 1, // Louisiana
|
||||
MO: 1, // Missouri
|
||||
MS: 1, // Mississippi
|
||||
MT: 1, // Montana
|
||||
NC: 1, // North Carolina
|
||||
ND: 1, // North Dakota
|
||||
NE: 1, // Nebraska
|
||||
OH: 1, // Ohio
|
||||
OK: 1, // Oklahoma
|
||||
SC: 1, // South Carolina
|
||||
SD: 1, // South Dakota
|
||||
TN: 1, // Tennessee
|
||||
TX: 1, // Texas
|
||||
UT: 1, // Utah
|
||||
VA: 1, // Virginia
|
||||
WY: 1, // Wyoming
|
||||
}, // only Florida
|
||||
},
|
||||
noVpn: [
|
||||
'AE', // United Arab Emirates
|
||||
'BY', // Belarus
|
||||
'CN', // China
|
||||
'IQ', // Iraq
|
||||
'IR', // Iran
|
||||
'KP', // North Korea
|
||||
'OM', // Oman
|
||||
'RU', // Russia
|
||||
'TM', // Turkmenistan
|
||||
'TR', // Turkey
|
||||
],
|
||||
censors: [ // additional to default filter
|
||||
'ball',
|
||||
'bisexual',
|
||||
'blow',
|
||||
'blowbang',
|
||||
'condom',
|
||||
'cowgirl',
|
||||
'creampie',
|
||||
'doggy',
|
||||
'facial',
|
||||
'finger',
|
||||
'gay',
|
||||
'hole',
|
||||
'horny',
|
||||
'lesbian',
|
||||
'masturbation',
|
||||
'milf',
|
||||
'missionary',
|
||||
'prolapse',
|
||||
'nymph',
|
||||
'sex',
|
||||
'swallowing',
|
||||
'squirt',
|
||||
'sucking',
|
||||
'threesome',
|
||||
'trans',
|
||||
],
|
||||
},
|
||||
auth: {
|
||||
login: true,
|
||||
signup: true,
|
||||
@@ -101,7 +186,7 @@ module.exports = {
|
||||
},
|
||||
media: {
|
||||
path: './media',
|
||||
assetPath: '/img',
|
||||
assetPath: '',
|
||||
mediaPath: '/media',
|
||||
s3Path: 'https://s3.wasabisys.com',
|
||||
videoRestrictions: [], // entity slugs, _ prefix for networks, hides trailer and teaser videos
|
||||
|
||||
205
package-lock.json
generated
205
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||
"@maxmind/geoip2-node": "^6.3.4",
|
||||
"@resvg/resvg-js": "^2.6.0",
|
||||
"@toycode/markdown-it-class": "^1.2.4",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
@@ -29,6 +30,7 @@
|
||||
"cron": "^3.1.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^3.0.0",
|
||||
"ejs": "^4.0.1",
|
||||
"error-stack-parser": "^2.1.4",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"express": "^4.18.2",
|
||||
@@ -52,6 +54,7 @@
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"object.omit": "^3.0.0",
|
||||
"obscenity": "^0.4.6",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.12",
|
||||
@@ -3224,6 +3227,15 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@maxmind/geoip2-node": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.3.4.tgz",
|
||||
"integrity": "sha512-BTRFHCX7Uie4wVSPXsWQfg0EVl4eGZgLCts0BTKAP+Eiyt1zmF2UPyuUZkaj0R59XSDYO+84o1THAtaenUoQYg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"maxmind": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modyfi/vite-plugin-yaml": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@modyfi/vite-plugin-yaml/-/vite-plugin-yaml-1.1.0.tgz",
|
||||
@@ -4527,9 +4539,10 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
@@ -5577,6 +5590,21 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-4.0.1.tgz",
|
||||
"integrity": "sha512-krvQtxc0btwSm/nvnt1UpnaFDFVJpJ0fdckmALpCgShsr/iGYHTnJiUliZTgmzq/UxTX33TtOQVKaNigMQp/6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jake": "^10.9.1"
|
||||
},
|
||||
"bin": {
|
||||
"ejs": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.18"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.616",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz",
|
||||
@@ -6790,6 +6818,36 @@
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -8045,6 +8103,23 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.4",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
"filelist": "^1.0.4",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"jake": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
@@ -8510,6 +8585,20 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/maxmind": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.5.tgz",
|
||||
"integrity": "sha512-1lcH2kMjbBpCFhuHaMU32vz8CuOsKttRcWMQyXvtlklopCzN7NNHSVR/h9RYa8JPuFTGmkn2vYARm+7cIGuqDw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mmdb-lib": "3.0.2",
|
||||
"tiny-lru": "11.4.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
@@ -8653,6 +8742,16 @@
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"node_modules/mmdb-lib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz",
|
||||
"integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
@@ -8991,6 +9090,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/obscenity": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/obscenity/-/obscenity-0.4.6.tgz",
|
||||
"integrity": "sha512-pHk7kNN7j3L3zGhhGnwxjvXIGsPpLrcZl2r58fqWh/V/rH6b/dafscj2sMmAY+A/9/wPsocLmimgGk2DKeKsFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -10734,6 +10842,15 @@
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"node_modules/tiny-lru": {
|
||||
"version": "11.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz",
|
||||
"integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.2.tgz",
|
||||
@@ -13920,6 +14037,14 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"@maxmind/geoip2-node": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.3.4.tgz",
|
||||
"integrity": "sha512-BTRFHCX7Uie4wVSPXsWQfg0EVl4eGZgLCts0BTKAP+Eiyt1zmF2UPyuUZkaj0R59XSDYO+84o1THAtaenUoQYg==",
|
||||
"requires": {
|
||||
"maxmind": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"@modyfi/vite-plugin-yaml": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@modyfi/vite-plugin-yaml/-/vite-plugin-yaml-1.1.0.tgz",
|
||||
@@ -14759,9 +14884,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
@@ -15518,6 +15643,14 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-4.0.1.tgz",
|
||||
"integrity": "sha512-krvQtxc0btwSm/nvnt1UpnaFDFVJpJ0fdckmALpCgShsr/iGYHTnJiUliZTgmzq/UxTX33TtOQVKaNigMQp/6Q==",
|
||||
"requires": {
|
||||
"jake": "^10.9.1"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.616",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz",
|
||||
@@ -16434,6 +16567,32 @@
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"requires": {
|
||||
"minimatch": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -17277,6 +17436,16 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"jake": {
|
||||
"version": "10.9.4",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
|
||||
"requires": {
|
||||
"async": "^3.2.6",
|
||||
"filelist": "^1.0.4",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
@@ -17637,6 +17806,15 @@
|
||||
"typed-function": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"maxmind": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.5.tgz",
|
||||
"integrity": "sha512-1lcH2kMjbBpCFhuHaMU32vz8CuOsKttRcWMQyXvtlklopCzN7NNHSVR/h9RYa8JPuFTGmkn2vYARm+7cIGuqDw==",
|
||||
"requires": {
|
||||
"mmdb-lib": "3.0.2",
|
||||
"tiny-lru": "11.4.7"
|
||||
}
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
@@ -17738,6 +17916,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"mmdb-lib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz",
|
||||
"integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
@@ -17989,6 +18172,11 @@
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"obscenity": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/obscenity/-/obscenity-0.4.6.tgz",
|
||||
"integrity": "sha512-pHk7kNN7j3L3zGhhGnwxjvXIGsPpLrcZl2r58fqWh/V/rH6b/dafscj2sMmAY+A/9/wPsocLmimgGk2DKeKsFQ=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -19227,6 +19415,11 @@
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"tiny-lru": {
|
||||
"version": "11.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz",
|
||||
"integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.2.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "npm run server:dev",
|
||||
"dev": "node ./src/app",
|
||||
"prod": "npm run build && npm run server:prod",
|
||||
"build": "vite build",
|
||||
"server:dev": "node ./src/app",
|
||||
@@ -14,6 +14,7 @@
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||
"@maxmind/geoip2-node": "^6.3.4",
|
||||
"@resvg/resvg-js": "^2.6.0",
|
||||
"@toycode/markdown-it-class": "^1.2.4",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
@@ -29,6 +30,7 @@
|
||||
"cron": "^3.1.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^3.0.0",
|
||||
"ejs": "^4.0.1",
|
||||
"error-stack-parser": "^2.1.4",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"express": "^4.18.2",
|
||||
@@ -52,6 +54,7 @@
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"object.omit": "^3.0.0",
|
||||
"obscenity": "^0.4.6",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.12",
|
||||
|
||||
@@ -3,7 +3,9 @@ import { fetchEntities } from '#/src/entities.js';
|
||||
export async function onBeforeRender(pageContext) {
|
||||
const networks = await fetchEntities(pageContext.urlParsed.search.q
|
||||
? { query: pageContext.urlParsed.search.q }
|
||||
: { type: 'primary' });
|
||||
: { type: 'primary' }, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
|
||||
@@ -20,7 +20,9 @@ async function fetchReleases(pageContext, entityId) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
aggregate: true,
|
||||
}, pageContext.user);
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
}
|
||||
|
||||
return fetchScenes(await curateScenesQuery({
|
||||
@@ -32,7 +34,9 @@ async function fetchReleases(pageContext, entityId) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
aggregate: true,
|
||||
}, pageContext.user);
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
}
|
||||
|
||||
export async function onBeforeRender(pageContext) {
|
||||
@@ -47,7 +51,9 @@ export async function onBeforeRender(pageContext) {
|
||||
[entity],
|
||||
entityReleases,
|
||||
] = await Promise.all([
|
||||
fetchEntitiesById([Number(entityId)], { includeChildren: true }, pageContext.user),
|
||||
fetchEntitiesById([Number(entityId)], { includeChildren: true }, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
}),
|
||||
fetchReleases(pageContext, entityId),
|
||||
]);
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ export async function onBeforeRender(pageContext) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 50,
|
||||
dedupe: true,
|
||||
}, pageContext.user);
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
|
||||
@@ -18,7 +18,7 @@ function getTitle(movie) {
|
||||
|
||||
export async function onBeforeRender(pageContext) {
|
||||
const [[movie], movieScenes] = await Promise.all([
|
||||
fetchMoviesById([Number(pageContext.routeParams.movieId)], pageContext.user),
|
||||
fetchMoviesById([Number(pageContext.routeParams.movieId)], pageContext.user, { restriction: pageContext.restriction }),
|
||||
fetchScenes(await curateScenesQuery({
|
||||
...pageContext.urlQuery,
|
||||
scope: 'oldest',
|
||||
@@ -27,7 +27,9 @@ export async function onBeforeRender(pageContext) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
aggregate: true,
|
||||
}, pageContext.user),
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!movie) {
|
||||
|
||||
@@ -25,6 +25,7 @@ export async function onBeforeRender(pageContext) {
|
||||
includeAssets: true,
|
||||
includePartOf: true,
|
||||
actorStashes: true,
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
|
||||
const [campaigns, tagIds] = await Promise.all([
|
||||
|
||||
@@ -17,7 +17,9 @@ export async function onBeforeRender(pageContext) {
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 29,
|
||||
aggregate: true,
|
||||
dedupe: true,
|
||||
}, pageContext.user),
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
}),
|
||||
getRandomCampaigns([
|
||||
{ minRatio: 0.75, maxRatio: 1.25 },
|
||||
{ minRatio: 1.5 },
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
>
|
||||
<img
|
||||
v-if="tag.poster"
|
||||
:src="`/${tag.poster.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/${tag.poster.lazy})` }"
|
||||
:src="getPath(tag.poster, 'thumbnail')"
|
||||
:style="{ 'background-image': `url(/${getPath(tag.poster, 'lazy')})` }"
|
||||
:title="tag.poster.comment"
|
||||
class="thumb"
|
||||
loading="lazy"
|
||||
@@ -111,6 +111,7 @@ import { ref, onMounted, inject } from 'vue';
|
||||
|
||||
import navigate from '#/src/navigate.js';
|
||||
import events from '#/src/events.js';
|
||||
import getPath from '#/src/get-path.js';
|
||||
|
||||
import Logo from '#/components/tags/logo.vue';
|
||||
|
||||
@@ -324,6 +325,7 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 5/3;
|
||||
border-radius: .25rem;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fetchTags, fetchTagsById } from '#/src/tags.js';
|
||||
import { censor } from '#/src/censor.js';
|
||||
|
||||
const tagSlugs = {
|
||||
popular: [
|
||||
@@ -136,13 +137,13 @@ export async function onBeforeRender(pageContext) {
|
||||
return searchTags(pageContext);
|
||||
}
|
||||
|
||||
const tags = await fetchTagsById(Object.values(tagSlugs).flat());
|
||||
const tags = await fetchTagsById(Object.values(tagSlugs).flat(), {}, pageContext.user, { restriction: pageContext.restriction });
|
||||
|
||||
const filteredTags = tags.filter((tag) => !pageContext.tagFilter.includes(tag.name) && !pageContext.tagFilter.includes(tag.slug));
|
||||
const tagsBySlug = Object.fromEntries(filteredTags.map((tag) => [tag.slug, tag]));
|
||||
|
||||
const tagShowcase = Object.fromEntries(Object.entries(tagSlugs).map(([category, categorySlugs]) => [
|
||||
category,
|
||||
censor(category, pageContext.restriction),
|
||||
categorySlugs.map((slug) => tagsBySlug[slug]).filter(Boolean),
|
||||
]));
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ async function fetchReleases(pageContext) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
aggregate: true,
|
||||
}, pageContext.user);
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
}
|
||||
|
||||
return fetchScenes(await curateScenesQuery({
|
||||
@@ -33,14 +35,16 @@ async function fetchReleases(pageContext) {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
aggregate: true,
|
||||
}, pageContext.user);
|
||||
}, pageContext.user, {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
}
|
||||
|
||||
export async function onBeforeRender(pageContext) {
|
||||
const tagSlug = pageContext.routeParams.tagSlug;
|
||||
|
||||
const [[tag], tagReleases, campaigns] = await Promise.all([
|
||||
fetchTagsById([tagSlug], {}, pageContext.user),
|
||||
fetchTagsById([tagSlug], {}, pageContext.user, { restriction: pageContext.restriction }),
|
||||
fetchReleases(pageContext),
|
||||
getRandomCampaigns([
|
||||
{ tagSlugs: [tagSlug], minRatio: 0.75, maxRatio: 1.25 },
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function onBeforeRender(pageContext) {
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 29,
|
||||
aggregate: withQuery,
|
||||
dedupe: true,
|
||||
}, pageContext.user),
|
||||
}, pageContext.user, { restriction: pageContext.restriction }),
|
||||
getRandomCampaigns([
|
||||
{ minRatio: 2.0, maxRatio: 5 },
|
||||
{ minRatio: 0.75, maxRatio: 1.25 },
|
||||
|
||||
@@ -9,5 +9,6 @@ export default {
|
||||
'assets',
|
||||
'campaigns',
|
||||
'meta',
|
||||
'restriction',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -122,7 +122,10 @@ export function curateActor(actor, context = {}) {
|
||||
state: actor.residence_state,
|
||||
},
|
||||
agency: actor.agency,
|
||||
avatar: curateMedia(actor.avatar),
|
||||
avatar: actor.avatar && curateMedia({
|
||||
...actor.avatar,
|
||||
sfw_media: actor.sfw_avatar,
|
||||
}),
|
||||
socials: context.socials?.map((social) => ({
|
||||
id: social.id,
|
||||
url: social.url,
|
||||
@@ -214,18 +217,21 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
|
||||
'residence_countries.alpha2 as residence_country_alpha2',
|
||||
knex.raw('COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name'),
|
||||
knex.raw('row_to_json(entities) as entity'),
|
||||
knex.raw('row_to_json(sfw_media) as sfw_avatar'),
|
||||
)
|
||||
.leftJoin('actors_meta', 'actors_meta.actor_id', 'actors.id')
|
||||
.leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors.birth_country_alpha2')
|
||||
.leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors.residence_country_alpha2')
|
||||
.leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'avatars.sfw_media_id')
|
||||
.leftJoin('entities', 'entities.id', 'actors.entity_id')
|
||||
.whereIn('actors.id', actorIds)
|
||||
.modify((builder) => {
|
||||
if (options.order) {
|
||||
builder.orderBy(...options.order);
|
||||
}
|
||||
}),
|
||||
})
|
||||
.groupBy('actors.id', 'avatars.id', 'sfw_media.id', 'entities.id', 'actors_meta.stashed', 'birth_countries.alpha2', 'residence_countries.alpha2'),
|
||||
knex('actors_profiles')
|
||||
.select(
|
||||
'actors_profiles.*',
|
||||
@@ -245,10 +251,12 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
|
||||
'media.*',
|
||||
'actors_avatars.actor_id',
|
||||
knex.raw('json_agg(actors_avatars.profile_id) as profile_ids'),
|
||||
knex.raw('row_to_json(sfw_media) as sfw_media'),
|
||||
)
|
||||
.whereIn('actor_id', actorIds)
|
||||
.leftJoin('media', 'media.id', 'actors_avatars.media_id')
|
||||
.groupBy('media.id', 'actors_avatars.actor_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.groupBy('media.id', 'sfw_media.id', 'actors_avatars.actor_id')
|
||||
.orderBy(knex.raw('max(actors_avatars.created_at)'), 'desc'),
|
||||
knex('actors_socials')
|
||||
.whereIn('actor_id', actorIds),
|
||||
|
||||
10
src/argv.js
10
src/argv.js
@@ -1,11 +1,15 @@
|
||||
import yargs from 'yargs';
|
||||
|
||||
const { argv } = yargs()
|
||||
.command('npm start')
|
||||
const { argv } = yargs(process.argv.slice(2))
|
||||
.option('debug', {
|
||||
describe: 'Show error stack traces',
|
||||
describe: 'Show error stack traces and inputs',
|
||||
type: 'boolean',
|
||||
default: process.env.NODE_ENV === 'development',
|
||||
})
|
||||
.option('ip', {
|
||||
describe: 'Mock IP address',
|
||||
type: 'string',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export default argv;
|
||||
|
||||
55
src/censor.js
Normal file
55
src/censor.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import config from 'config';
|
||||
|
||||
import {
|
||||
TextCensor,
|
||||
RegExpMatcher,
|
||||
englishDataset,
|
||||
englishRecommendedTransformers,
|
||||
DataSet,
|
||||
pattern,
|
||||
// asteriskCensorStrategy,
|
||||
} from 'obscenity';
|
||||
|
||||
const textCensor = new TextCensor();
|
||||
|
||||
// built-in asterisk strategy replaces entire word
|
||||
textCensor.setStrategy(({
|
||||
input,
|
||||
startIndex,
|
||||
endIndex,
|
||||
matchLength,
|
||||
}) => {
|
||||
if (matchLength <= 2) {
|
||||
return '*'.repeat(matchLength);
|
||||
}
|
||||
|
||||
return `${input.at(startIndex)}${'*'.repeat(matchLength - 2)}${input.at(endIndex)}`;
|
||||
});
|
||||
|
||||
const dataset = new DataSet().addAll(englishDataset);
|
||||
|
||||
config.restrictions.censors.forEach((word) => {
|
||||
dataset.addPhrase((phrase) => phrase
|
||||
.setMetadata({ originalWord: word })
|
||||
.addPattern(pattern`${word}`));
|
||||
});
|
||||
|
||||
const matcher = new RegExpMatcher({
|
||||
...dataset.build(),
|
||||
...englishRecommendedTransformers,
|
||||
});
|
||||
|
||||
export function censor(text, restriction) {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!restriction) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const censorMatches = matcher.getAllMatches(text);
|
||||
const censoredText = textCensor.applyTo(text, censorMatches);
|
||||
|
||||
return censoredText;
|
||||
}
|
||||
@@ -3,29 +3,36 @@ import redis from './redis.js';
|
||||
import initLogger from './logger.js';
|
||||
import entityPrefixes from './entities-prefixes.js';
|
||||
import { getAffiliateEntityUrl } from './affiliates.js';
|
||||
import { censor } from './censor.js';
|
||||
|
||||
const logger = initLogger();
|
||||
|
||||
export function curateEntity(entity, context) {
|
||||
export function curateEntity(entity, context = {}) {
|
||||
if (!entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const curatedEntity = {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
name: censor(entity.name, context.restriction),
|
||||
slug: entity.slug,
|
||||
type: entity.type,
|
||||
url: entity.url,
|
||||
isIndependent: entity.independent,
|
||||
hasLogo: entity.has_logo,
|
||||
hasLogo: context.restriction ? false : entity.has_logo,
|
||||
parent: curateEntity(entity.parent, context),
|
||||
tags: context?.tags?.map((tag) => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
slug: tag.slug,
|
||||
})),
|
||||
children: context?.children?.filter((child) => child.parent_id === entity.id).map((child) => curateEntity({ ...child, parent: entity }, { parent: entity })) || [],
|
||||
children: context?.children?.filter((child) => child.parent_id === entity.id).map((child) => curateEntity({
|
||||
...child,
|
||||
parent: entity,
|
||||
}, {
|
||||
parent: entity,
|
||||
restriction: context.restriction,
|
||||
})) || [],
|
||||
affiliate: entity.affiliate ? {
|
||||
id: entity.affiliate.id,
|
||||
entityId: entity.affiliate.entity_id,
|
||||
@@ -44,7 +51,7 @@ export function curateEntity(entity, context) {
|
||||
return curatedEntity;
|
||||
}
|
||||
|
||||
export async function fetchEntities(options = {}) {
|
||||
export async function fetchEntities(options = {}, context) {
|
||||
const entities = await knex('entities')
|
||||
.select('entities.*', knex.raw('row_to_json(parents) as parent'))
|
||||
.modify((builder) => {
|
||||
@@ -93,11 +100,12 @@ export async function fetchEntities(options = {}) {
|
||||
.whereIn('entity_id', entities.map((entity) => entity.id));
|
||||
|
||||
return entities.map((entityEntry) => curateEntity(entityEntry, {
|
||||
...context,
|
||||
tags: entitiesTags.filter((tag) => tag.entity_id === entityEntry.id),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function fetchEntitiesById(entityIds, options = {}, reqUser) {
|
||||
export async function fetchEntitiesById(entityIds, options = {}, reqUser, context) {
|
||||
const [entities, children, tags, alerts] = await Promise.all([
|
||||
knex('entities')
|
||||
.select(
|
||||
@@ -136,6 +144,7 @@ export async function fetchEntitiesById(entityIds, options = {}, reqUser) {
|
||||
|
||||
if (options.order) {
|
||||
return entities.map((entityEntry) => curateEntity(entityEntry, {
|
||||
...context,
|
||||
append: options.append,
|
||||
children: children.filter((channel) => channel.parent_id === entityEntry.id),
|
||||
alerts: alerts.filter((alert) => alert.entity_id === entityEntry.id),
|
||||
@@ -151,6 +160,7 @@ export async function fetchEntitiesById(entityIds, options = {}, reqUser) {
|
||||
}
|
||||
|
||||
return curateEntity(entity, {
|
||||
...context,
|
||||
append: options.append,
|
||||
children: children.filter((channel) => channel.parent_id === entity.id),
|
||||
tags: tags.filter((tag) => tag.entity_id === entity.id),
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// import config from 'config';
|
||||
import { pageContext } from '../renderer/usePageContext.js';
|
||||
|
||||
function getBasePath(media, type, options) {
|
||||
/*
|
||||
if (store.state.ui.sfw) {
|
||||
return config.media.assetPath;
|
||||
function getBasePath(media, options) {
|
||||
if (pageContext.restriction) {
|
||||
return pageContext.env.media.assetPath;
|
||||
}
|
||||
*/
|
||||
|
||||
if (media.isS3) {
|
||||
return options.s3Path;
|
||||
@@ -20,15 +17,13 @@ function getBasePath(media, type, options) {
|
||||
}
|
||||
|
||||
function getFilename(media, type, options) {
|
||||
/*
|
||||
if (store.state.ui.sfw && type && !options?.original) {
|
||||
return media.sfw[type];
|
||||
if (pageContext.restriction && type && !options?.original) {
|
||||
return media.sfw?.[type];
|
||||
}
|
||||
|
||||
if (store.state.ui.sfw) {
|
||||
return media.sfw.path;
|
||||
if (pageContext.restriction) {
|
||||
return media.sfw?.path;
|
||||
}
|
||||
*/
|
||||
|
||||
if (type && !options?.original) {
|
||||
return media[type];
|
||||
@@ -42,7 +37,7 @@ export default function getPath(media, type, options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const path = getBasePath(media, type, { ...pageContext.env.media, ...options });
|
||||
const path = getBasePath(media, { ...pageContext.env.media, ...options });
|
||||
const filename = getFilename(media, type, { ...pageContext.env.media, ...options });
|
||||
|
||||
return `${path}/${filename}`;
|
||||
|
||||
@@ -30,6 +30,7 @@ export function curateMedia(media, context = {}) {
|
||||
parent: media.entity_parent,
|
||||
}),
|
||||
type: context.type || null,
|
||||
sfw: curateMedia(media.sfw_media),
|
||||
isRestricted: context.isRestricted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,29 +8,30 @@ import { curateMedia } from './media.js';
|
||||
import { fetchTagsById } from './tags.js';
|
||||
import { fetchEntitiesById } from './entities.js';
|
||||
import { curateStash } from './stashes.js';
|
||||
import { censor } from './censor.js';
|
||||
import escape from '../utils/escape-manticore.js';
|
||||
import promiseProps from '../utils/promise-props.js';
|
||||
|
||||
function curateMovie(rawMovie, assets) {
|
||||
function curateMovie(rawMovie, assets, context = {}) {
|
||||
if (!rawMovie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: rawMovie.id,
|
||||
title: rawMovie.title,
|
||||
title: censor(rawMovie.title, context.restriction),
|
||||
slug: rawMovie.slug,
|
||||
url: rawMovie.url,
|
||||
date: rawMovie.date,
|
||||
datePrecision: rawMovie.date_precision,
|
||||
createdAt: rawMovie.created_at,
|
||||
effectiveDate: rawMovie.effective_date,
|
||||
description: rawMovie.description,
|
||||
description: censor(rawMovie.description, context.restriction),
|
||||
duration: rawMovie.duration,
|
||||
channel: {
|
||||
id: assets.channel.id,
|
||||
slug: assets.channel.slug,
|
||||
name: assets.channel.name,
|
||||
name: censor(assets.channel.name, context.restriction),
|
||||
type: assets.channel.type,
|
||||
isIndependent: assets.channel.independent,
|
||||
hasLogo: assets.channel.has_logo,
|
||||
@@ -38,7 +39,7 @@ function curateMovie(rawMovie, assets) {
|
||||
network: assets.channel.network_id ? {
|
||||
id: assets.channel.network_id,
|
||||
slug: assets.channel.network_slug,
|
||||
name: assets.channel.network_name,
|
||||
name: censor(assets.channel.network_name, context.restriction),
|
||||
type: assets.channel.network_type,
|
||||
hasLogo: assets.channel.has_logo,
|
||||
} : null,
|
||||
@@ -51,7 +52,7 @@ function curateMovie(rawMovie, assets) {
|
||||
tags: assets.tags.map((tag) => ({
|
||||
id: tag.id,
|
||||
slug: tag.slug,
|
||||
name: tag.name,
|
||||
name: censor(tag.name, context.restriction),
|
||||
})),
|
||||
// poster: curateMedia(assets.poster),
|
||||
covers: assets.covers.map((cover) => curateMedia(cover, { type: 'cover' })),
|
||||
@@ -64,7 +65,7 @@ function curateMovie(rawMovie, assets) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchMoviesById(movieIds, reqUser) {
|
||||
export async function fetchMoviesById(movieIds, reqUser, context) {
|
||||
const {
|
||||
movies,
|
||||
channels,
|
||||
@@ -123,20 +124,25 @@ export async function fetchMoviesById(movieIds, reqUser) {
|
||||
.leftJoin('tags', 'tags.id', 'releases_tags.tag_id')
|
||||
.orderBy('priority', 'desc'),
|
||||
covers: knex('movies_covers')
|
||||
.select('media.*', 'movies_covers.movie_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.whereIn('movie_id', movieIds)
|
||||
.leftJoin('media', 'media.id', 'movies_covers.media_id')
|
||||
.orderBy('media.index'),
|
||||
photos: knex.transaction(async (trx) => {
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.orderBy('media.index')
|
||||
.groupBy('media.id', 'movies_covers.movie_id', 'sfw_media.id'),
|
||||
photos: context.restriction ? [] : knex.transaction(async (trx) => {
|
||||
if (reqUser) {
|
||||
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
|
||||
}
|
||||
|
||||
return trx('movies_scenes')
|
||||
.select('media.*', 'movies_scenes.movie_id')
|
||||
.select('media.*', 'movies_scenes.movie_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.whereIn('movies_scenes.movie_id', movieIds)
|
||||
.whereNotNull('media.id')
|
||||
.leftJoin('releases_photos', 'releases_photos.release_id', 'movies_scenes.scene_id')
|
||||
.leftJoin('media', 'media.id', 'releases_photos.media_id');
|
||||
.leftJoin('media', 'media.id', 'releases_photos.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.groupBy('media.id', 'movies_scenes.movie_id', 'sfw_media.id');
|
||||
}),
|
||||
caps: knex.transaction(async (trx) => {
|
||||
if (reqUser) {
|
||||
@@ -144,11 +150,13 @@ export async function fetchMoviesById(movieIds, reqUser) {
|
||||
}
|
||||
|
||||
return trx('movies_scenes')
|
||||
.select('media.*', 'movies_scenes.movie_id')
|
||||
.select('media.*', 'movies_scenes.movie_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.whereIn('movies_scenes.movie_id', movieIds)
|
||||
.whereNotNull('media.id')
|
||||
.leftJoin('releases_caps', 'releases_caps.release_id', 'movies_scenes.scene_id')
|
||||
.leftJoin('media', 'media.id', 'releases_caps.media_id');
|
||||
.leftJoin('media', 'media.id', 'releases_caps.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.groupBy('media.id', 'movies_scenes.movie_id', 'sfw_media.id');
|
||||
}),
|
||||
trailers: knex('movies_trailers')
|
||||
.whereIn('movie_id', movieIds)
|
||||
@@ -192,7 +200,7 @@ export async function fetchMoviesById(movieIds, reqUser) {
|
||||
caps: movieCaps,
|
||||
trailer: movieTrailer,
|
||||
stashes: movieStashes,
|
||||
});
|
||||
}, context);
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -398,7 +406,7 @@ function countAggregations(buckets) {
|
||||
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
}
|
||||
|
||||
export async function fetchMovies(filters, rawOptions, reqUser) {
|
||||
export async function fetchMovies(filters, rawOptions, reqUser, context) {
|
||||
const options = curateOptions(rawOptions);
|
||||
|
||||
console.log(options);
|
||||
@@ -413,13 +421,13 @@ export async function fetchMovies(filters, rawOptions, reqUser) {
|
||||
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds);
|
||||
|
||||
const [aggActors, aggTags, aggChannels] = await Promise.all([
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: actorCounts }) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.map((bucket) => bucket.key), { order: [knex.raw('lower(name)'), 'asc'], append: tagCounts }) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(result.aggregations.channelIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: channelCounts }) : [],
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: actorCounts }, reqUser) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.map((bucket) => bucket.key), { order: [knex.raw('lower(name)'), 'asc'], append: tagCounts }, reqUser, context) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(result.aggregations.channelIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: channelCounts }, reqUser, context) : [],
|
||||
]);
|
||||
|
||||
const movieIds = result.movies.map((movie) => Number(movie.id));
|
||||
const movies = await fetchMoviesById(movieIds, reqUser);
|
||||
const movies = await fetchMoviesById(movieIds, reqUser, context);
|
||||
|
||||
return {
|
||||
movies,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import config from 'config';
|
||||
import { MerkleJson } from 'merkle-json';
|
||||
|
||||
import argv from './argv.js';
|
||||
import { knexQuery as knex, knexOwner, knexManticore } from './knex.js';
|
||||
import { utilsApi } from './manticore.js';
|
||||
import { HttpError } from './errors.js';
|
||||
@@ -15,18 +16,19 @@ import promiseProps from '../utils/promise-props.js';
|
||||
import initLogger from './logger.js';
|
||||
import { curateRevision } from './revisions.js';
|
||||
import { getAffiliateSceneUrl } from './affiliates.js';
|
||||
import { censor } from './censor.js';
|
||||
|
||||
const logger = initLogger();
|
||||
const mj = new MerkleJson();
|
||||
|
||||
function curateScene(rawScene, assets, reqUser) {
|
||||
function curateScene(rawScene, assets, reqUser, context) {
|
||||
if (!rawScene) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const curatedScene = {
|
||||
id: rawScene.id,
|
||||
title: rawScene.title,
|
||||
title: censor(rawScene.title, context.restriction),
|
||||
slug: rawScene.slug,
|
||||
url: rawScene.url,
|
||||
entryId: rawScene.entry_id,
|
||||
@@ -34,14 +36,14 @@ function curateScene(rawScene, assets, reqUser) {
|
||||
datePrecision: rawScene.date_precision,
|
||||
createdAt: rawScene.created_at,
|
||||
effectiveDate: rawScene.effective_date,
|
||||
description: rawScene.description,
|
||||
description: censor(rawScene.description, context.restriction),
|
||||
duration: rawScene.duration,
|
||||
shootId: rawScene.shoot_id,
|
||||
productionDate: rawScene.production_date,
|
||||
channel: {
|
||||
id: assets.channel.id,
|
||||
slug: assets.channel.slug,
|
||||
name: assets.channel.name,
|
||||
name: censor(assets.channel.name, context.restriction),
|
||||
type: assets.channel.type,
|
||||
isIndependent: assets.channel.independent,
|
||||
hasLogo: assets.channel.has_logo,
|
||||
@@ -49,7 +51,7 @@ function curateScene(rawScene, assets, reqUser) {
|
||||
network: assets.channel.network_id ? {
|
||||
id: assets.channel.network_id,
|
||||
slug: assets.channel.network_slug,
|
||||
name: assets.channel.network_name,
|
||||
name: censor(assets.channel.network_name, context.restriction),
|
||||
type: assets.channel.network_type,
|
||||
hasLogo: assets.channel.network_has_logo,
|
||||
} : null,
|
||||
@@ -78,7 +80,7 @@ function curateScene(rawScene, assets, reqUser) {
|
||||
tags: assets.tags.map((tag) => ({
|
||||
id: tag.id,
|
||||
slug: tag.slug,
|
||||
name: tag.name,
|
||||
name: censor(tag.name, context.restriction),
|
||||
priority: tag.priority,
|
||||
})),
|
||||
chapters: assets.chapters.map((chapter) => ({
|
||||
@@ -86,7 +88,9 @@ function curateScene(rawScene, assets, reqUser) {
|
||||
title: chapter.title,
|
||||
time: chapter.time,
|
||||
duration: chapter.duration,
|
||||
poster: curateMedia(chapter.chapter_poster),
|
||||
poster: context.restriction
|
||||
? null
|
||||
: (chapter.chapter_poster),
|
||||
tags: chapter.chapter_tags.map((tag) => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
@@ -98,14 +102,18 @@ function curateScene(rawScene, assets, reqUser) {
|
||||
movies: assets.movies.map((movie) => ({
|
||||
id: movie.id,
|
||||
slug: movie.slug,
|
||||
title: movie.title,
|
||||
covers: movie.movie_covers?.map((cover) => curateMedia(cover, { type: 'cover' })).toSorted((coverA, coverB) => coverA.index - coverB.index) || [],
|
||||
title: censor(movie.title, context.restriction),
|
||||
covers: movie.movie_covers && !context.restriction
|
||||
? movie.movie_covers.map((cover) => curateMedia(cover, { type: 'cover' })).toSorted((coverA, coverB) => coverA.index - coverB.index)
|
||||
: [],
|
||||
})),
|
||||
series: assets.series.map((serie) => ({
|
||||
id: serie.id,
|
||||
slug: serie.slug,
|
||||
title: serie.title,
|
||||
poster: curateMedia(serie.serie_poster, { type: 'poster' }),
|
||||
poster: context.restriction
|
||||
? null
|
||||
: (serie.serie_poster, { type: 'poster' }),
|
||||
})),
|
||||
poster: curateMedia(assets.poster, { type: 'poster' }),
|
||||
photos: assets.photos?.map((photo) => curateMedia(photo, { type: 'photo' })) || [],
|
||||
@@ -189,14 +197,17 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||
.select(
|
||||
'actors.*',
|
||||
knex.raw('row_to_json(avatars) as avatar'),
|
||||
knex.raw('row_to_json(sfw_media) as sfw_avatar'),
|
||||
'countries.name as birth_country_name',
|
||||
'countries.alias as birth_country_alias',
|
||||
'releases_actors.release_id',
|
||||
)
|
||||
.leftJoin('actors', 'actors.id', 'releases_actors.actor_id')
|
||||
.leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'avatars.sfw_media_id')
|
||||
.leftJoin('countries', 'countries.alpha2', 'actors.birth_country_alpha2')
|
||||
.whereIn('release_id', sceneIds),
|
||||
.whereIn('release_id', sceneIds)
|
||||
.groupBy('actors.id', 'releases_actors.release_id', 'avatars.id', 'countries.name', 'countries.alias', 'sfw_media.id'),
|
||||
directors: knex('releases_directors')
|
||||
.whereIn('release_id', sceneIds)
|
||||
.leftJoin('actors as directors', 'directors.id', 'releases_directors.director_id'),
|
||||
@@ -234,17 +245,23 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||
.whereIn('scene_id', sceneIds)
|
||||
.groupBy('series.id', 'series_scenes.scene_id', 'media.*') : [],
|
||||
posters: knex('releases_posters')
|
||||
.select('media.*', 'releases_posters.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.whereIn('release_id', sceneIds)
|
||||
.leftJoin('media', 'media.id', 'releases_posters.media_id'),
|
||||
photos: context.includeAssets ? knex.transaction(async (trx) => {
|
||||
.leftJoin('media', 'media.id', 'releases_posters.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.groupBy('media.id', 'releases_posters.release_id', 'sfw_media.id'),
|
||||
photos: context.includeAssets && !context.restriction ? knex.transaction(async (trx) => {
|
||||
if (reqUser) {
|
||||
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
|
||||
}
|
||||
|
||||
return trx('releases_photos')
|
||||
.select('media.*', 'releases_photos.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.leftJoin('media', 'media.id', 'releases_photos.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.whereIn('release_id', sceneIds)
|
||||
.orderBy('index');
|
||||
.orderBy('index')
|
||||
.groupBy('media.id', 'releases_photos.release_id', 'sfw_media.id');
|
||||
}) : [],
|
||||
caps: context.includeAssets ? knex.transaction(async (trx) => {
|
||||
if (reqUser) {
|
||||
@@ -252,9 +269,12 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||
}
|
||||
|
||||
return trx('releases_caps')
|
||||
.select('media.*', 'releases_caps.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.leftJoin('media', 'media.id', 'releases_caps.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.whereIn('release_id', sceneIds)
|
||||
.orderBy('index');
|
||||
.orderBy('index')
|
||||
.groupBy('media.id', 'releases_caps.release_id', 'sfw_media.id');
|
||||
}) : [],
|
||||
trailers: context.includeAssets ? knex.transaction(async (trx) => {
|
||||
if (reqUser) {
|
||||
@@ -350,7 +370,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
|
||||
stashes: sceneStashes,
|
||||
actorStashes: sceneActorStashes,
|
||||
lastBatchId: lastBatch?.id,
|
||||
}, reqUser);
|
||||
}, reqUser, context);
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -545,7 +565,7 @@ async function queryManticoreSql(filters, options, _reqUser) {
|
||||
? sqlQuery
|
||||
: sqlQuery.replace(/scenes\./g, '');
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (process.env.NODE_ENV === 'development' && argv.debug) {
|
||||
console.log(curatedSqlQuery);
|
||||
}
|
||||
|
||||
@@ -603,14 +623,18 @@ function countAggregations(buckets) {
|
||||
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
}
|
||||
|
||||
export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||
export async function fetchScenes(filters, rawOptions, reqUser, context) {
|
||||
const options = curateOptions(rawOptions);
|
||||
|
||||
console.log('filters', filters);
|
||||
console.log('options', options);
|
||||
if (argv.debug) {
|
||||
console.log('filters', filters);
|
||||
console.log('options', options);
|
||||
}
|
||||
|
||||
console.time('manticore sql');
|
||||
|
||||
const result = await queryManticoreSql(filters, options, reqUser);
|
||||
|
||||
console.timeEnd('manticore sql');
|
||||
|
||||
const aggYears = options.aggregateYears && result.aggregations.years.map((bucket) => ({ year: bucket.key, count: bucket.doc_count }));
|
||||
@@ -623,16 +647,16 @@ export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||
console.time('fetch aggregations');
|
||||
|
||||
const [aggActors, aggTags, aggChannels] = await Promise.all([
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: actorCounts }) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.map((bucket) => bucket.key), { order: [knex.raw('lower(name)'), 'asc'], append: tagCounts }) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(entityIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: channelCounts }) : [],
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: actorCounts }, reqUser) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.map((bucket) => bucket.key), { order: [knex.raw('lower(name)'), 'asc'], append: tagCounts }, reqUser, context) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(entityIds.map((bucket) => bucket.key), { order: ['slug', 'asc'], append: channelCounts }, reqUser, context) : [],
|
||||
]);
|
||||
|
||||
console.timeEnd('fetch aggregations');
|
||||
|
||||
console.time('fetch full');
|
||||
const sceneIds = result.scenes.map((scene) => Number(scene.id));
|
||||
const scenes = await fetchScenesById(sceneIds, { reqUser });
|
||||
const scenes = await fetchScenesById(sceneIds, { reqUser, ...context });
|
||||
console.timeEnd('fetch full');
|
||||
|
||||
return {
|
||||
|
||||
21
src/tags.js
21
src/tags.js
@@ -1,6 +1,7 @@
|
||||
import knex from './knex.js';
|
||||
import redis from './redis.js';
|
||||
import initLogger from './logger.js';
|
||||
import { censor } from './censor.js';
|
||||
|
||||
import { curateMedia } from './media.js';
|
||||
|
||||
@@ -9,9 +10,9 @@ const logger = initLogger();
|
||||
function curateTag(tag, context) {
|
||||
return {
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
name: censor(tag.name, context.restriction),
|
||||
slug: tag.slug,
|
||||
description: tag.description,
|
||||
description: context.restriction ? null : tag.description, // censor interferes with markdown
|
||||
priority: tag.priority,
|
||||
poster: tag.poster && curateMedia(tag.poster),
|
||||
photos: tag.photos?.map((photo) => curateMedia(photo)) || [],
|
||||
@@ -55,10 +56,13 @@ export async function fetchTags(options = {}) {
|
||||
}
|
||||
}),
|
||||
knex('tags_posters')
|
||||
.select('media.*', 'tags_posters.tag_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
|
||||
.leftJoin('media', 'media.id', 'tags_posters.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.leftJoin('entities', 'entities.id', 'media.entity_id')
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id'),
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.groupBy('media.id', 'tags_posters.tag_id', 'sfw_media.id', 'entities.id', 'parents.id'),
|
||||
]);
|
||||
|
||||
const postersByTagId = Object.fromEntries(posters.map((poster) => [poster.tag_id, poster]));
|
||||
@@ -69,7 +73,7 @@ export async function fetchTags(options = {}) {
|
||||
}));
|
||||
}
|
||||
|
||||
export async function fetchTagsById(tagIds, options = {}, reqUser) {
|
||||
export async function fetchTagsById(tagIds, options = {}, reqUser, context = {}) {
|
||||
const [tags, posters, photos, alerts] = await Promise.all([
|
||||
knex('tags')
|
||||
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
|
||||
@@ -80,14 +84,17 @@ export async function fetchTagsById(tagIds, options = {}, reqUser) {
|
||||
}
|
||||
}),
|
||||
knex('tags_posters')
|
||||
.select('media.*', 'tags_posters.tag_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
|
||||
.select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
|
||||
.leftJoin('tags', 'tags.id', 'tags_posters.tag_id')
|
||||
.leftJoin('media', 'media.id', 'tags_posters.media_id')
|
||||
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
|
||||
.leftJoin('entities', 'entities.id', 'media.entity_id')
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
|
||||
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')),
|
||||
knex('tags_photos')
|
||||
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'))
|
||||
.groupBy('media.id', 'tags_posters.tag_id', 'sfw_media.id', 'entities.id', 'parents.id'),
|
||||
context.restriction ? [] : knex('tags_photos')
|
||||
.select('tags_photos.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
|
||||
.leftJoin('tags', 'tags.id', 'tags_photos.tag_id')
|
||||
.leftJoin('media', 'media.id', 'tags_photos.media_id')
|
||||
@@ -118,6 +125,7 @@ export async function fetchTagsById(tagIds, options = {}, reqUser) {
|
||||
}, {
|
||||
alerts: alerts.filter((alert) => alert.tag_id === tagEntry.id),
|
||||
append: options.append,
|
||||
...context,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -136,6 +144,7 @@ export async function fetchTagsById(tagIds, options = {}, reqUser) {
|
||||
}, {
|
||||
alerts: alerts.filter((alert) => alert.tag_id === tag.id),
|
||||
append: options.append,
|
||||
...context,
|
||||
});
|
||||
}).filter(Boolean);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
|
||||
import IPCIDR from 'ip-cidr';
|
||||
import argv from '../argv.js';
|
||||
|
||||
import {
|
||||
login,
|
||||
@@ -14,6 +15,10 @@ import {
|
||||
import { fetchUser } from '../users.js';
|
||||
|
||||
function getIp(req) {
|
||||
if (argv.ip) {
|
||||
return argv.ip;
|
||||
}
|
||||
|
||||
const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress;
|
||||
|
||||
const unmappedIp = ip?.includes('.')
|
||||
|
||||
@@ -51,6 +51,7 @@ export default async function mainHandler(req, res, next) {
|
||||
siteKey: config.auth.captcha.siteKey,
|
||||
},
|
||||
},
|
||||
restriction: req.restriction,
|
||||
meta: {
|
||||
now: new Date().toISOString(),
|
||||
},
|
||||
|
||||
80
src/web/restrictions.js
Normal file
80
src/web/restrictions.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import config from 'config';
|
||||
import path from 'path';
|
||||
import { Reader } from '@maxmind/geoip2-node';
|
||||
|
||||
import initLogger from '../logger.js';
|
||||
|
||||
const logger = initLogger();
|
||||
const regions = config.restrictions.regions;
|
||||
|
||||
export default async function initRestrictionHandler() {
|
||||
const reader = await Reader.open('assets/GeoLite2-City.mmdb');
|
||||
|
||||
function getRestriction(req) {
|
||||
if (req.session.restriction && req.session.country && req.session.restrictionIp === req.userIp) {
|
||||
return {
|
||||
restriction: req.session.restriction,
|
||||
country: req.session.country,
|
||||
};
|
||||
}
|
||||
|
||||
const location = reader.city(req.userIp);
|
||||
const country = location.country.isoCode;
|
||||
const subdivision = location.subdivisions?.[0]?.isoCode;
|
||||
|
||||
if (regions[country]?.[subdivision]) {
|
||||
// state or province restriction
|
||||
return {
|
||||
restriction: config.restrictions.modes[regions[country][subdivision]],
|
||||
country,
|
||||
};
|
||||
}
|
||||
|
||||
if (regions[country]) {
|
||||
// country restriction
|
||||
return {
|
||||
restriction: config.restrictions.modes[regions[country]],
|
||||
country,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
restriction: null,
|
||||
country,
|
||||
};
|
||||
}
|
||||
|
||||
function restrictionHandler(req, res, next) {
|
||||
if (!config.restrictions.enabled) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { restriction, country } = getRestriction(req);
|
||||
|
||||
if (restriction === 'block' || req.path === '/sfw/') {
|
||||
res.render(path.join(import.meta.dirname, '../../assets/sfw.ejs'), {
|
||||
noVpn: config.restrictions.noVpn.includes(country),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.session.restriction !== restriction) {
|
||||
req.session.restrictionIp = req.userIp;
|
||||
req.session.restriction = restriction;
|
||||
req.session.country = country;
|
||||
}
|
||||
|
||||
req.restriction = restriction;
|
||||
req.country = country;
|
||||
} catch (error) {
|
||||
logger.error(`Failed Maxmind IP lookup for ${req.ip}: ${error.message}`);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
return restrictionHandler;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import redis from '../redis.js';
|
||||
|
||||
import errorHandler from './error.js';
|
||||
import consentHandler from './consent.js';
|
||||
import initRestrictionHandler from './restrictions.js';
|
||||
|
||||
import { scenesRouter } from './scenes.js';
|
||||
import { actorsRouter } from './actors.js';
|
||||
@@ -48,9 +49,11 @@ const isProduction = process.env.NODE_ENV === 'production';
|
||||
export default async function initServer() {
|
||||
const app = express();
|
||||
const router = Router();
|
||||
const restrictionHandler = await initRestrictionHandler();
|
||||
|
||||
app.use(compression());
|
||||
app.disable('x-powered-by');
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
router.use(boolParser());
|
||||
|
||||
@@ -58,7 +61,7 @@ export default async function initServer() {
|
||||
router.use('/', express.static('static'));
|
||||
router.use('/media', express.static(config.media.path));
|
||||
|
||||
router.use((req, res, next) => {
|
||||
router.use((req, _res, next) => {
|
||||
if (req.headers.cookie) {
|
||||
const cookies = cookie.parse(req.headers.cookie);
|
||||
|
||||
@@ -109,11 +112,13 @@ export default async function initServer() {
|
||||
router.use(viteDevMiddleware);
|
||||
}
|
||||
|
||||
router.get('/consent', (req, res) => {
|
||||
router.use(restrictionHandler);
|
||||
|
||||
router.get('/consent', (_req, res) => {
|
||||
res.sendFile(path.join(import.meta.dirname, '../../assets/consent.html'));
|
||||
});
|
||||
|
||||
router.use('/api/*', async (req, res, next) => {
|
||||
router.use('/api/*', async (req, _res, next) => {
|
||||
if (req.headers['api-user']) {
|
||||
await verifyKey(req.headers['api-user'], req.headers['api-key'], req);
|
||||
|
||||
@@ -159,7 +164,7 @@ export default async function initServer() {
|
||||
|
||||
router.use(consentHandler);
|
||||
|
||||
router.use((req, res, next) => {
|
||||
router.use((_req, res, next) => {
|
||||
/* eslint-disable no-param-reassign */
|
||||
res.set('Accept-CH', 'Sec-CH-Prefers-Color-Scheme');
|
||||
res.set('Vary', 'Sec-CH-Prefers-Color-Scheme');
|
||||
|
||||
2
static
2
static
Submodule static updated: 4b6b7e5e73...389d659cb2
Reference in New Issue
Block a user