Added dynamic affiliate URLs and video player restrictions.

This commit is contained in:
DebaucheryLibrarian 2026-01-28 00:57:32 +01:00
parent 31c62e01f9
commit 0bf0b716b2
7 changed files with 54 additions and 23 deletions

View File

@ -46,6 +46,13 @@
@play="playing = true; paused = false;"
@pause="playing = false; paused = true;"
/>
<Icon
v-if="(release.trailer || release.teaser).isRestricted"
v-tooltip="'Restricted video'"
icon="blocked"
class="restricted"
/>
</div>
<div
@ -192,6 +199,15 @@ const coversInAlbum = props.release.covers?.length > 0 && props.release.trailer;
width: calc(21/9 * 16rem);
flex-shrink: 0;
aspect-ratio: 16/9;
position: relative;
}
.restricted {
position: absolute;
top: 0;
left: 0;
padding: .5rem;
fill: var(--highlight-weak-10);
}
:deep(.player) {

View File

@ -104,5 +104,9 @@ module.exports = {
assetPath: '/img',
mediaPath: '/media',
s3Path: 'https://s3.wasabisys.com',
videoRestrictions: [ // entity slugs
'_kink',
'_kinkmen',
],
},
};

View File

@ -1,3 +1,5 @@
import format from 'template-format';
function getWatchUrl(scene) {
if (scene.url) {
return scene.url;
@ -29,6 +31,16 @@ export function getAffiliateSceneUrl(scene) {
? scene.affiliate.parameters.replaceScene.url
: scene.affiliate.url;
if (scene.affiliate.parameters.dynamicScene) {
const scenePath = new URL(watchUrl).pathname;
return format(scene.affiliate.parameters.dynamicScene, {
scenePath: scene.affiliate.parameters.prefixSlash
? scenePath
: scenePath.replace(/^\//, ''),
});
}
if (affiliateUrl?.includes('/track')
&& scene.affiliate.parameters.scene !== false
&& (!scene.channel.isIndependent || scene.channel.id === scene.affiliate.entityId)) { // standard NATS redirect
@ -58,8 +70,6 @@ export function getAffiliateEntityUrl(entity) {
? entity.affiliate.parameters.replaceEntity.url
: entity.affiliate.url;
console.log(entity.affiliate, entity.url);
if (entity.id === entity.affiliate.entityId) {
return affiliateUrl;
}
@ -73,6 +83,16 @@ export function getAffiliateEntityUrl(entity) {
return entity.url;
}
if (entity.affiliate.parameters.dynamicEntity) {
const entityPath = new URL(entity.url).pathname;
return format(entity.affiliate.parameters.dynamicEntity, {
entityPath: entity.affiliate.parameters.prefixSlash
? entityPath
: entityPath.replace(/^\//, ''),
});
}
if (affiliateUrl?.includes('/track')
&& entity.affiliate.parameters.channel !== false) {
const { pathname, search } = new URL(entity.url);

View File

@ -30,5 +30,6 @@ export function curateMedia(media, context = {}) {
parent: media.entity_parent,
}),
type: context.type || null,
isRestricted: context.isRestricted,
};
}

View File

@ -19,7 +19,7 @@ import { getAffiliateSceneUrl } from './affiliates.js';
const logger = initLogger();
const mj = new MerkleJson();
function curateScene(rawScene, assets) {
function curateScene(rawScene, assets, reqUser) {
if (!rawScene) {
return null;
}
@ -107,8 +107,6 @@ function curateScene(rawScene, assets) {
poster: curateMedia(serie.serie_poster, { type: 'poster' }),
})),
poster: curateMedia(assets.poster, { type: 'poster' }),
trailer: curateMedia(assets.trailer, { type: 'trailer' }),
teaser: curateMedia(assets.teaser, { type: 'teaser' }),
photos: assets.photos?.map((photo) => curateMedia(photo, { type: 'photo' })) || [],
caps: assets.caps?.map((cap) => curateMedia(cap, { type: 'cap' })) || [],
stashes: assets.stashes?.map((stash) => curateStash(stash)) || [],
@ -125,6 +123,13 @@ function curateScene(rawScene, assets) {
isNew: assets.lastBatchId === rawScene.created_batch_id,
};
const isVideoRestricted = config.media.videoRestrictions.includes(curatedScene.channel.slug) || config.media.videoRestrictions.includes(`_${curatedScene.network?.slug}`);
if ((!isVideoRestricted || reqUser.abilities?.trailerAccess)) {
curatedScene.trailer = curateMedia(assets.trailer, { type: 'trailer', isRestricted: isVideoRestricted });
curatedScene.teaser = curateMedia(assets.teaser, { type: 'teaser', isRestricted: isVideoRestricted });
}
curatedScene.watchUrl = getAffiliateSceneUrl(curatedScene);
return curatedScene;
@ -344,7 +349,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
stashes: sceneStashes,
actorStashes: sceneActorStashes,
lastBatchId,
});
}, reqUser);
}).filter(Boolean);
}

View File

@ -29,6 +29,7 @@ export function curateUser(user, _assets = {}) {
isIdentityVerified: user.identity_verified,
avatar: `/media/avatars/${user.id}_${user.username}.png`,
role: user.role,
abilities: user.abilities,
createdAt: user.created_at,
};
@ -61,23 +62,6 @@ export async function fetchUser(userId, options = {}, _reqUser) {
throw new HttpError(`User '${userId}' not found`, 404);
}
/*
const [stashes, templates] = await Promise.all([
knex('stashes')
.select('stashes.*', 'stashes_meta.*')
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id')
.where('user_id', user.id)
.modify((builder) => {
if (reqUser?.id !== user.id && !options.includeStashes) {
builder.where('public', true);
}
}),
options.includeTemplates
? knex('users_templates').where('user_id', user.id)
: null,
]);
*/
if (options.raw) {
// return { user, stashes, templates };
return { user };

View File

@ -24,6 +24,7 @@ export default async function mainHandler(req, res, next) {
username: req.user.username,
email: req.user.email,
role: req.user.role,
abilities: req.user.abilities,
avatar: req.user.avatar,
},
assets: req.user ? {