From a80e188d15824d536ea11732398f065e657da36f Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Wed, 7 Jan 2026 06:17:51 +0100 Subject: [PATCH] Added Bellesa. --- package-lock.json | 61 +++++++++----------- package.json | 2 +- seeds/01_networks.js | 19 +++++++ seeds/02_sites.js | 67 ++++++++++++++++++++++ src/app.js | 4 +- src/scrapers/aylo.js | 2 +- src/scrapers/bellesa.js | 117 +++++++++++++++++++++++++++++++++++++++ src/scrapers/scrapers.js | 3 + 8 files changed, 237 insertions(+), 38 deletions(-) create mode 100644 src/scrapers/bellesa.js diff --git a/package-lock.json b/package-lock.json index a253059e..beef3c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "tunnel": "0.0.6", "ua-parser-js": "^1.0.37", "undici": "^5.28.1", - "unprint": "^0.17.9", + "unprint": "^0.18.1", "url-pattern": "^1.0.3", "v-tooltip": "^2.1.3", "video.js": "^8.6.1", @@ -6339,15 +6339,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -9921,25 +9912,6 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -20368,12 +20340,12 @@ } }, "node_modules/unprint": { - "version": "0.17.9", - "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.17.9.tgz", - "integrity": "sha512-0sCmeNBT5xU9PIDYShfmmsWpeDx7T7Arp7nMFQMwVctd5AHpjZhP/GvJBWUJde+u+76fEuEEtxrCFFA5dRSQ0Q==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.18.1.tgz", + "integrity": "sha512-lL7aIQfVoVY3oC69WM6GOUabwfJkMQxM4XfIT5EA21CsisJassWU3sX2ZQqIrsX4NujOeDVgzU2qYFJ/QWpoqQ==", "dependencies": { - "axios": "^0.27.2", "bottleneck": "^2.19.5", + "cookie": "^1.1.1", "deepmerge": "^4.2.2", "eslint": "^8.17.0", "eslint-config-airbnb": "^19.0.4", @@ -20383,7 +20355,8 @@ "object-hash": "^3.0.0", "patchright": "^1.56.1", "srcset": "^4.0.0", - "tunnel": "^0.0.6" + "tunnel": "^0.0.6", + "undici": "^7.18.2" } }, "node_modules/unprint/node_modules/@tootallnate/once": { @@ -20394,6 +20367,18 @@ "node": ">= 6" } }, + "node_modules/unprint/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/unprint/node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", @@ -20606,6 +20591,14 @@ "node": ">=8" } }, + "node_modules/unprint/node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/unprint/node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", diff --git a/package.json b/package.json index 9096da3f..70ac3a35 100755 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "tunnel": "0.0.6", "ua-parser-js": "^1.0.37", "undici": "^5.28.1", - "unprint": "^0.17.9", + "unprint": "^0.18.1", "url-pattern": "^1.0.3", "v-tooltip": "^2.1.3", "video.js": "^8.6.1", diff --git a/seeds/01_networks.js b/seeds/01_networks.js index 419ed6f6..6fee8c11 100755 --- a/seeds/01_networks.js +++ b/seeds/01_networks.js @@ -61,6 +61,15 @@ const parentNetworks = [ }, parent: 'gamma', }, + { + slug: 'bellesa', + name: 'Bellesa', + url: 'https://www.bellesa.co', + parameters: { + api: 'https://www.bellesa.co/api/rest/v1', + source: 'bellesa', + }, + }, { slug: 'radical', alias: ['kb productions'], @@ -208,6 +217,16 @@ const networks = [ scene: 'https://bangbros.com/video', }, }, + { + slug: 'bellesaplus', + name: 'Bellesa Plus', + url: 'https://bellesaplus.co', + parent: 'bellesa', + parameters: { + api: 'https://bellesaplus.co/api/rest/v1', + source: 'plus', + }, + }, { slug: 'blowpass', name: 'Blowpass', diff --git a/seeds/02_sites.js b/seeds/02_sites.js index 9c6bf043..edc687c2 100755 --- a/seeds/02_sites.js +++ b/seeds/02_sites.js @@ -2143,6 +2143,73 @@ const sites = [ layout: 'members', }, }, + // BELLESA + { + name: 'House Party', + slug: 'houseparty', + url: 'https://www.bellesa.co/videos?providers=bellesa-house-party', + parent: 'bellesa', + }, + { + name: 'Bellesa House', + slug: 'bellesahouse', + url: 'https://www.bellesa.co/videos?providers=bellesa-house', + parent: 'bellesa', + }, + { + name: 'Blind Date', + slug: 'blinddate', + url: 'https://www.bellesa.co/videos?providers=bellesa-blind-date', + parent: 'bellesa', + }, + { + name: 'Belle Says', + slug: 'bellesays', + url: 'https://www.bellesa.co/videos?providers=belle-says', + parent: 'bellesa', + }, + { + name: 'Zero To Hero', + slug: 'zerotohero', + url: 'https://www.bellesa.co/videos?providers=zero-to-hero', + parent: 'bellesa', + }, + { + name: 'Bellesa Films', + slug: 'bellesafilms', + url: 'https://www.bellesa.co/videos?providers=bellesa-films', + parent: 'bellesa', + }, + { + name: 'Bellesa House Plus', + slug: 'bellesahouseplus', + url: 'https://bellesaplus.co/videos?providers=bellesa-house', + parent: 'bellesaplus', + }, + { + name: 'Bellesa Films Plus', + slug: 'bellesafilmsplus', + url: 'https://bellesaplus.co/videos?providers=bellesa-films', + parent: 'bellesaplus', + }, + { + name: 'House Party Plus', + slug: 'housepartyplus', + url: 'https://bellesaplus.co/videos?providers=bellesa-house-party', + parent: 'bellesaplus', + }, + { + name: 'Blind Date Plus', + slug: 'blinddateplus', + url: 'https://bellesaplus.co/videos?providers=bellesa-blind-date', + parent: 'bellesaplus', + }, + { + name: 'Belle Says Plus', + slug: 'bellesaysplus', + url: 'https://bellesaplus.co/videos?providers=belle-says', + parent: 'bellesaplus', + }, // BIPHORIA { slug: 'biphoria', diff --git a/src/app.js b/src/app.js index 62227fca..d9568681 100755 --- a/src/app.js +++ b/src/app.js @@ -33,11 +33,11 @@ let done = false; unprint.options({ timeout: argv.requestTimeout, headers: { - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', }, context: { // browser requests - userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', }, limits: { ...config.limits, diff --git a/src/scrapers/aylo.js b/src/scrapers/aylo.js index 7accc197..b68b2784 100755 --- a/src/scrapers/aylo.js +++ b/src/scrapers/aylo.js @@ -5,7 +5,7 @@ const Promise = require('bluebird'); const { CookieJar } = Promise.promisifyAll(require('tough-cookie')); const cookie = require('cookie'); const moment = require('moment'); -const unprint = require('unprint'); +// const unprint = require('unprint'); const qu = require('../utils/qu'); const slugify = require('../utils/slugify'); diff --git a/src/scrapers/bellesa.js b/src/scrapers/bellesa.js new file mode 100644 index 00000000..b1ba5131 --- /dev/null +++ b/src/scrapers/bellesa.js @@ -0,0 +1,117 @@ +'use strict'; + +const unprint = require('unprint'); + +const slugify = require('../utils/slugify'); + +const channelMap = { + bellesa_house_party: 'House Party', + bellesa_blind_date: 'Blind Date', +}; + +function scrapeScene(data, entity, parameters) { + const release = {}; + + release.entryId = data.id; + release.url = `${new URL(entity.url).origin}/videos/${data.id}/${slugify(data.title)}`; + + release.title = data.title; + release.description = data.description; + + release.date = new Date(data.posted_on * 1000); + release.duration = data.duration; + + release.actors = data.performers?.map((actor) => ({ + name: actor.name, + url: `https://www.bellesa.co/pornstar/${actor.handle}`, // no actor page on Bellesa Plus, presumably shared database + entryId: actor.uid, + gender: actor.gender, + avatar: actor.image, + })); + + release.tags = [ + ...(data.tags?.split(',') || []), + ...(data.categories?.map((category) => category.name) || []), + ]; + + release.qualities = data.resolutions?.split(',').map(Number); + + release.poster = data.image; + + const trailerId = data.source || data.trailer; + + if (trailerId) { + // the regular site has full videos as 'trailers' + if (parameters.source === 'plus') { + release.trailer = [1080, 720, 480].map((quality) => ({ + src: `https://s.bellesa.co/v/${trailerId}/${quality}.mp4`, + quality, + })); + } + + release.teaser = `https://s.bellesa.co/v/${trailerId}/preview_62.mp4`; + } + + const channel = data.content_provider?.[0]?.name; + + if (channel) { + release.channel = `${channelMap[slugify(channel, '_')] || channel}${data.access.plus ? ' Plus' : ''}`; + } + + return release; +} + +async function fetchLatest(channel, page = 1, { parameters }) { + const provider = new URL(channel.url).searchParams.get('providers'); + + const res = await unprint.get(`${parameters.api}/videos?filter[provider]=${provider}&limit=24&sources=${parameters.source}&page=${page}`, { + headers: { + // currently only required for Bellesa Plus + Referer: `${new URL(channel.url).origin}/videos`, + Cookie: 'bellesa_agegate=true', + 'User-Agent': null, + }, + }); + + if (res.ok) { + return res.data.map((scene) => scrapeScene(scene, channel, parameters)); + } + + return res.status; +} + +async function fetchScene(url, entity, baseRelease, { parameters }) { + if (baseRelease) { + // identical data + return baseRelease; + } + + const res = await unprint.get(url, { + headers: { + // currently only required for Bellesa Plus + Referer: `${new URL(entity.url).origin}/videos`, + Cookie: 'bellesa_agegate=true', + 'User-Agent': null, + }, + parser: { + runScripts: 'dangerously', + }, + }); + + if (res.ok) { + const data = res.context.window.__INITIAL_DATA__.video || res.context.window.__INITIAL_DATA__.data.video; + + if (data) { + return scrapeScene(data, entity, parameters); + } + + return null; + } + + return res.status; +} + +module.exports = { + fetchLatest, + fetchScene, +}; diff --git a/src/scrapers/scrapers.js b/src/scrapers/scrapers.js index 8de42839..798aa0fb 100755 --- a/src/scrapers/scrapers.js +++ b/src/scrapers/scrapers.js @@ -12,6 +12,7 @@ const badoink = require('./badoink'); const bamvisions = require('./bamvisions'); const bang = require('./bang'); const bradmontana = require('./bradmontana'); +const bellesa = require('./bellesa'); const cherrypimps = require('./cherrypimps'); const cliffmedia = require('./cliffmedia'); const cumlouder = require('./cumlouder'); @@ -101,6 +102,7 @@ const scrapers = { bamvisions, bang, bangbros: aylo, + bellesa, bluedonkeymedia, bradmontana, brazzers: aylo, @@ -217,6 +219,7 @@ const scrapers = { bamvisions, bang, bangbros: aylo, + bellesa, bjraw: radical, blacked: vixen, blackedraw: vixen,