diff --git a/config/default.js b/config/default.js index 72393d5f..7542a434 100755 --- a/config/default.js +++ b/config/default.js @@ -192,9 +192,6 @@ module.exports = { 'traxxx', // porn doe 'forbondage', - 'score', - // porncz - 'porncz', ], }, profiles: [ diff --git a/package-lock.json b/package-lock.json index f0ee6502..f609d4dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "connect-session-knex": "^4.0.0", "convert": "^4.14.0", "cookie": "^0.6.0", + "css": "^3.0.0", "csv-parse": "^6.1.0", "csv-stringify": "^6.4.4", "date-fns": "^2.30.0", @@ -93,7 +94,7 @@ "tunnel": "0.0.6", "ua-parser-js": "^1.0.37", "undici": "^5.28.1", - "unprint": "^0.18.7", + "unprint": "^0.18.11", "url-pattern": "^1.0.3", "v-tooltip": "^2.1.3", "video.js": "^8.6.1", @@ -6269,6 +6270,17 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.23", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", @@ -7807,6 +7819,16 @@ "node": ">= 8" } }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, "node_modules/css-loader": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", @@ -8055,6 +8077,14 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -19072,6 +19102,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -20340,9 +20380,9 @@ } }, "node_modules/unprint": { - "version": "0.18.7", - "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.18.7.tgz", - "integrity": "sha512-Su7SQ7UxvM5SHuvkLmSGMiWi+nS6Qh3489qqbPYaqvH9DFBDc5C4544mGyysU/7ZKO+5RtAi59prVTNPo8rlSw==", + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/unprint/-/unprint-0.18.11.tgz", + "integrity": "sha512-mHOfweWWLqhEIRnjhdqCzEpHhIx+m/GwE2eDvJNNbnVEPbV8q8EaN6eGH3vkcAwDVgNIOakZaTZFK+VKy13Lsg==", "dependencies": { "bottleneck": "^2.19.5", "cookie": "^1.1.1", diff --git a/package.json b/package.json index 3f6a0288..803ce361 100755 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "connect-session-knex": "^4.0.0", "convert": "^4.14.0", "cookie": "^0.6.0", + "css": "^3.0.0", "csv-parse": "^6.1.0", "csv-stringify": "^6.4.4", "date-fns": "^2.30.0", @@ -152,7 +153,7 @@ "tunnel": "0.0.6", "ua-parser-js": "^1.0.37", "undici": "^5.28.1", - "unprint": "^0.18.7", + "unprint": "^0.18.11", "url-pattern": "^1.0.3", "v-tooltip": "^2.1.3", "video.js": "^8.6.1", diff --git a/seeds/00_tags.js b/seeds/00_tags.js index 47e53d72..6cdff601 100755 --- a/seeds/00_tags.js +++ b/seeds/00_tags.js @@ -2679,6 +2679,10 @@ const aliases = [ name: 'threesome - fmm', for: 'mfm', }, + { + name: 'threesome (fmm)', // doesn't usually mean bisexual + for: 'mfm', + }, { name: '4k ultra hd', for: '4k', diff --git a/seeds/01_networks.js b/seeds/01_networks.js index eae9c7bb..0aa258b6 100755 --- a/seeds/01_networks.js +++ b/seeds/01_networks.js @@ -707,7 +707,6 @@ const networks = [ slug: 'score', name: 'SCORE', url: 'https://www.scorepass.com', - description: '', }, { slug: 'sexyhub', diff --git a/seeds/02_sites.js b/seeds/02_sites.js index b01b11b2..7e8cbf17 100755 --- a/seeds/02_sites.js +++ b/seeds/02_sites.js @@ -9384,6 +9384,7 @@ const sites = [ { name: 'Czech Deviant', slug: 'czechdeviant', + delete: true, url: 'https://www.czechdeviant.com', parent: 'porncz', parameters: { @@ -9400,7 +9401,7 @@ const sites = [ }, }, { - name: 'Fuckingoffice', + name: 'Fucking Office', slug: 'fuckingoffice', url: 'https://www.fuckingoffice.com', parent: 'porncz', @@ -11339,6 +11340,951 @@ const sites = [ }, }, // SCORE + { + name: '18 Eighteen', + slug: '18eighteen', + url: 'https://www.18eighteen.com', + parent: 'score', + parameters: { + path: '/xxx-teen-videos', + }, + }, + { + name: '40 Something Mag', + slug: '40somethingmag', + url: 'https://www.40somethingmag.com', + parent: 'score', + parameters: { + path: '/xxx-mature-videos', + }, + }, + { + name: '50 Plus MILFs', + slug: '50plusmilfs', + url: 'https://www.50plusmilfs.com', + parent: 'score', + parameters: { + path: '/xxx-milf-videos', + }, + }, + { + name: '60 Plus MILFs', + slug: '60plusmilfs', + url: 'https://www.60plusmilfs.com', + parent: 'score', + parameters: { + path: '/xxx-granny-videos', + }, + }, + { + name: 'Anal QTs', + slug: 'analqts', + url: 'https://www.analqts.com', + parent: 'score', + parameters: { + path: '/anal-sex-videos', + }, + }, + { + name: 'Ashley Sage Ellison', + slug: 'ashleysageellison', + url: 'https://www.ashleysageellison.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Asian Coochies', + slug: 'asiancoochies', + url: 'https://www.asiancoochies.com', + parent: 'score', + parameters: { + path: '/asian-porn-videos', + }, + }, + { + name: 'Autumn Jade', + slug: 'autumnjade', + url: 'https://www.autumn-jade.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Boob Alexya', + slug: 'bigboobalexya', + url: 'https://www.bigboobalexya.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Boob Bundle', + slug: 'bigboobbundle', + url: 'https://www.bigboobbundle.com', + delete: true, + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Boob Daria', + slug: 'bigboobdaria', + url: 'https://www.bigboobdaria.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Boobs POV', + slug: 'bigboobspov', + url: 'https://www.bigboobspov.com', + parent: 'score', + parameters: { + path: '/big-boob-videos', + }, + }, + { + name: 'Big Boob Vanessa Y', + slug: 'bigboobvanessay', + url: 'https://www.bigboobvanessay.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Tit Angela White', + slug: 'bigtitangelawhite', + url: 'https://www.bigtitangelawhite.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Tit Hitomi', + slug: 'bigtithitomi', + url: 'https://www.bigtithitomi.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Tit Hooker', + slug: 'bigtithooker', + url: 'https://www.bigtithooker.com', + parent: 'score', + parameters: { + path: '/big-boob-videos', + }, + }, + { + name: 'Big Tit Katie Thornton', + slug: 'bigtitkatiethornton', + url: 'https://www.bigtitkatiethornton.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Tit Terry Nova', + slug: 'bigtitterrynova', + url: 'https://www.bigtitterrynova.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Big Tit Venera', + slug: 'bigtitvenera', + url: 'https://www.bigtitvenera.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Black And Stacked', + slug: 'blackandstacked', + url: 'https://www.blackandstacked.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Boned At Home', + slug: 'bonedathome', + url: 'https://www.bonedathome.com', + parent: 'score', + parameters: { + path: '/amateur-videos', + }, + }, + { + name: 'Bootylicious', + slug: 'bootyliciousmag', + url: 'https://www.bootyliciousmag.com', + parent: 'score', + parameters: { + path: '/big-booty-videos', + }, + }, + { + name: 'Busty Angelique', + slug: 'bustyangelique', + url: 'https://www.bustyangelique.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Arianna', + slug: 'bustyarianna', + url: 'https://www.bustyarianna.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Danni Ashe', + slug: 'bustydanniashe', + url: 'https://www.bustydanniashe.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Dusty Stash', + slug: 'bustydustystash', + url: 'https://www.bustydustystash.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Ines Cudna', + slug: 'bustyinescudna', + url: 'https://www.bustyinescudna.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Kelly Kay', + slug: 'bustykellykay', + url: 'https://www.bustykellykay.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Kerry Marie', + slug: 'bustykerrymarie', + url: 'https://www.bustykerrymarie.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Lezzies', + slug: 'bustylezzies', + url: 'https://www.bustylezzies.com', + parent: 'score', + parameters: { + path: '/lesbian-videos', + }, + }, + { + name: 'Busty Lorna Morgan', + slug: 'bustylornamorgan', + url: 'https://www.bustylornamorgan.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Merilyn', + slug: 'bustymerilyn', + url: 'https://www.bustymerilyn.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Busty Old Sluts', + slug: 'bustyoldsluts', + url: 'https://www.bustyoldsluts.com', + parent: 'score', + parameters: { + path: '/big-tit-scenes', + }, + }, + { + name: 'Busty Sammie Black', + slug: 'bustysammieblack', + url: 'https://www.bustysammieblack.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Cherry Brady', + slug: 'cherrybrady', + url: 'https://www.cherrybrady.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Chicks on Black Dicks', + slug: 'chicksonblackdicks', + url: 'https://www.chicksonblackdicks.com', + parent: 'score', + parameters: { + path: '/interracial-porn-videos', + }, + }, + { + name: 'Chloes World', + slug: 'chloesworld', + url: 'https://www.chloesworld.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Christy Marks', + slug: 'christymarks', + url: 'https://www.christymarks.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Cock 4 Stepmom', + slug: 'cock4stepmom', + url: 'https://www.cock4stepmom.com', + parent: 'score', + parameters: { + path: '/stepmom-xxx-scenes', + }, + }, + { + name: 'Codi Vore XXX', + slug: 'codivorexxx', + url: 'https://www.codivorexxx.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Creampie for Granny', + slug: 'creampieforgranny', + url: 'https://www.creampieforgranny.com', + parent: 'score', + parameters: { + path: '/milf-creampie-scenes', + }, + }, + { + name: 'Crystal Gunns World', + slug: 'crystalgunnsworld', + url: 'https://www.crystalgunnsworld.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Daylene Rio', + slug: 'daylenerio', + url: 'https://www.daylenerio.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Desiraes World', + slug: 'desiraesworld', + url: 'https://www.desiraesworld.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Diane Poppos', + slug: 'dianepoppos', + url: 'https://www.dianepoppos.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Ebony Thots', + slug: 'ebonythots', + url: 'https://www.ebonythots.com', + parent: 'score', + parameters: { + path: '/black-girl-videos', + }, + }, + { + name: 'Eva Notty Videos', + slug: 'evanottyvideos', + url: 'https://www.evanottyvideos.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Feed Her Fuck Her', + slug: 'feedherfuckher', + url: 'https://www.feedherfuckher.com', + parent: 'score', + parameters: { + path: '/bbw-videos', + }, + }, + { + name: 'Flat And Fucked MILFs', + slug: 'flatandfuckedmilfs', + url: 'https://www.flatandfuckedmilfs.com', + parent: 'score', + parameters: { + path: '/xxx-milf-scenes', + }, + }, + { + name: 'Granny Gets A Facial', + slug: 'grannygetsafacial', + url: 'https://www.grannygetsafacial.com', + parent: 'score', + parameters: { + path: '/granny-facial-scenes', + }, + }, + { + name: 'Granny Loves BBC', + slug: 'grannylovesbbc', + url: 'https://www.grannylovesbbc.com', + parent: 'score', + parameters: { + path: '/bbc-granny-scenes', + }, + }, + { + name: 'Granny Loves Young Cock', + slug: 'grannylovesyoungcock', + url: 'https://www.grannylovesyoungcock.com', + parent: 'score', + parameters: { + path: '/xxx-granny-scenes', + }, + }, + { + name: 'Hairy Coochies', + slug: 'hairycoochies', + url: 'https://www.hairycoochies.com', + parent: 'score', + parameters: { + path: '/hairy-pussy-videos', + }, + }, + { + name: 'Home Alone MILFs', + slug: 'homealonemilfs', + url: 'https://www.homealonemilfs.com', + parent: 'score', + parameters: { + path: '/milf-scenes', + }, + }, + { + name: 'Horny Asian MILFs', + slug: 'hornyasianmilfs', + url: 'https://www.hornyasianmilfs.com', + parent: 'score', + parameters: { + path: '/asian-milf-scenes', + }, + }, + { + name: 'I Boned Your Mom', + slug: 'ibonedyourmom', + url: 'https://www.ibonedyourmom.com', + parent: 'score', + parameters: { + path: '/mom-fucking-scenes', + }, + }, + { + name: 'I Fucked the Boss', + slug: 'ifuckedtheboss', + url: 'https://www.ifuckedtheboss.com', + parent: 'score', + parameters: { + path: '/working-girl-scenes', + }, + }, + { + name: 'Jessica Turner', + slug: 'jessicaturner', + url: 'https://www.jessicaturner.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Joana Bliss', + slug: 'joanabliss', + url: 'https://www.joanabliss.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Julia Miles', + slug: 'juliamiles', + url: 'https://www.juliamiles.co.uk', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Karina Hart', + slug: 'karinahart', + url: 'https://www.karinahart.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Karla James', + slug: 'karlajames', + url: 'https://www.karlajames.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Latina Coochies', + slug: 'latinacoochies', + url: 'https://www.latinacoochies.com', + parent: 'score', + parameters: { + path: '/latina-porn-videos', + }, + }, + { + name: 'Latin Mommas', + slug: 'latinmommas', + url: 'https://www.latinmommas.com', + parent: 'score', + parameters: { + path: '/latina-milf-scenes', + }, + }, + { + name: 'Leanne Crow Videos', + slug: 'leannecrowvideos', + url: 'https://www.leannecrowvideos.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Leg Sex', + slug: 'legsex', + url: 'https://www.legsex.com', + parent: 'score', + parameters: { + path: '/foot-fetish-videos', + }, + }, + { + name: 'Linseys World', + slug: 'linseysworld', + url: 'https://www.linseysworld.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Mega Tits Minka', + slug: 'megatitsminka', + url: 'https://www.megatitsminka.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Micky Bells', + slug: 'mickybells', + url: 'https://www.mickybells.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'MILF Bundle', + slug: 'milfbundle', + delete: true, + url: 'https://www.milfbundle.com', + parent: 'score', + parameters: { + path: '/milf-scenes', + }, + }, + { + name: 'MILF Threesomes', + slug: 'milfthreesomes', + url: 'https://www.milfthreesomes.com', + parent: 'score', + parameters: { + path: '/milf-group-scenes', + }, + }, + { + name: 'MILF Tugs', + slug: 'milftugs', + url: 'https://www.milftugs.com', + parent: 'score', + parameters: { + path: '/milf-handjob-scenes', + }, + }, + { + name: 'Milly Marks', + slug: 'millymarks', + url: 'https://www.millymarks.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Mommys Toy Time', + slug: 'mommystoytime', + url: 'https://www.mommystoytime.com', + parent: 'score', + parameters: { + path: '/milf-sex-toy-scenes', + }, + }, + { + name: 'Natalie Fiore', + slug: 'nataliefiore', + url: 'https://www.nataliefiore.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Naughty Footjobs', + slug: 'naughtyfootjobs', + url: 'https://www.naughtyfootjobs.com', + parent: 'score', + parameters: { + path: '/foot-job-videos', + }, + }, + { + name: 'Naughty Mag', + slug: 'naughtymag', + url: 'https://www.naughtymag.com', + parent: 'score', + parameters: { + path: '/amateur-videos', + }, + }, + { + name: 'Naughty Tugs', + slug: 'naughtytugs', + url: 'https://www.naughtytugs.com', + parent: 'score', + parameters: { + path: '/hand-job-videos', + }, + }, + { + name: 'Nicole Peters', + slug: 'nicolepeters', + url: 'https://www.nicolepeters.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Old Horny MILFs', + slug: 'oldhornymilfs', + url: 'https://www.oldhornymilfs.com', + parent: 'score', + parameters: { + path: '/milf-scenes', + }, + }, + { + name: 'Picking Up Pussy', + slug: 'pickinguppussy', + url: 'https://www.pickinguppussy.com', + parent: 'score', + parameters: { + path: '/xxx-teen-videos', + }, + }, + { + name: 'Porn Loser', + slug: 'pornloser', + url: 'https://www.pornloser.com', + parent: 'score', + parameters: { + path: '/amateur-videos', + }, + }, + { + name: 'Porn Mega Load', + slug: 'pornmegaload', + delete: true, + url: 'https://www.pornmegaload.com', + parent: 'score', + parameters: { + path: '/hd-porn-scenes', + }, + }, + { + name: 'Renee Ross Videos', + slug: 'reneerossvideos', + url: 'https://www.reneerossvideos.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Roxi Red', + slug: 'roxired', + url: 'https://www.roxired.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'SaRennas World', + slug: 'sarennasworld', + url: 'https://www.sarennasworld.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Score Classics', + slug: 'scoreclassics', + url: 'https://www.scoreclassics.com', + parent: 'score', + parameters: { + path: '/classic-boob-videos', + }, + }, + { + name: 'Scoreland', + slug: 'scoreland', + url: 'https://www.scoreland.com', + parent: 'score', + parameters: { + path: '/big-boob-videos', + }, + }, + { + name: 'Scoreland2', + slug: 'scoreland2', + url: 'https://www.scoreland2.com', + parent: 'score', + parameters: { + path: '/big-boob-scenes', + }, + }, + { + name: 'SCOREtv', + slug: 'scoretv', + delete: true, + parent: 'score', + }, + { + name: 'Scoreland TV', + slug: 'scorelandtv', + delete: true, + url: 'https://www.scorepass.com/scorelandtv', + parent: 'score', + priority: 1, + }, + { + name: 'Score Videos', + slug: 'scorevideos', + url: 'https://www.scorevideos.com', + parent: 'score', + parameters: { + path: '/porn-videos', + }, + }, + { + name: 'Sha Rizel Videos', + slug: 'sharizelvideos', + url: 'https://www.sharizelvideos.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Silver Sluts', + slug: 'silversluts', + url: 'https://www.silversluts.com', + parent: 'score', + parameters: { + path: '/granny-scenes', + }, + }, + { + name: 'Stacy Vandenberg Boobs', + slug: 'stacyvandenbergboobs', + url: 'https://www.stacyvandenbergboobs.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Susie Wildin', + slug: 'susiewildin', + url: 'https://www.susiewildin.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Tawny Peaks', + slug: 'tawnypeaks', + url: 'https://www.tawny-peaks.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Tiffany Towers', + slug: 'tiffanytowers', + url: 'https://www.tiffany-towers.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'Tits And Tugs', + slug: 'titsandtugs', + url: 'https://www.titsandtugs.com', + parent: 'score', + parameters: { + path: '/big-boob-videos', + }, + }, + { + name: 'TNA Tryouts', + slug: 'tnatryouts', + url: 'https://www.tnatryouts.com', + parent: 'score', + parameters: { + path: '/xxx-teen-videos', + }, + }, + { + name: 'Valory Irene', + slug: 'valoryirene', + url: 'https://www.valoryirene.com', + parent: 'score', + parameters: { + path: '/videos', + }, + }, + { + name: 'XL Girls', + slug: 'xlgirls', + url: 'https://www.xlgirls.com', + parent: 'score', + parameters: { + path: '/bbw-videos', + }, + }, + { + name: 'Your Mom Loves Anal', + slug: 'yourmomlovesanal', + url: 'https://www.yourmomlovesanal.com', + parent: 'score', + parameters: { + path: '/anal-milf-scenes', + }, + }, + { + name: "Your Mom's Got Big Tits", + slug: 'yourmomsgotbigtits', + url: 'https://www.yourmomsgotbigtits.com', + parent: 'score', + parameters: { + path: '/big-tit-mom-scenes', + }, + }, + { + name: 'Your Wife My Meat', + slug: 'yourwifemymeat', + url: 'https://www.yourwifemymeat.com', + parent: 'score', + parameters: { + path: '/wife-fucking-scenes', + }, + }, + /* legacy { name: '18 Eighteen', slug: '18eighteen', @@ -11874,6 +12820,7 @@ const sites = [ url: 'https://www.milfbundle.com/yourwifemymeat', parent: 'score', }, + */ // SEX LIKE REAL { name: 'Sex Like Real', diff --git a/src/entities.js b/src/entities.js index 2d2db80b..28484417 100755 --- a/src/entities.js +++ b/src/entities.js @@ -36,7 +36,7 @@ function curateEntity(entity, includeParameters = false) { id: entity.id, name: entity.name, url: entity.url, - origin: new URL(entity.url).origin, + origin: entity.url && new URL(entity.url).origin, description: entity.description, slug: entity.slug, type: entity.type, diff --git a/src/scrapers/kellymadison.js b/src/scrapers/kellymadison.js index 0207c28c..6fe30ed5 100755 --- a/src/scrapers/kellymadison.js +++ b/src/scrapers/kellymadison.js @@ -189,7 +189,7 @@ function scrapeProfile({ query }) { const birthday = new Date(Date.UTC(0, Number(month) - 1, Number(day))); if (profile.age) { - birthday.setUTCFullYear(new Date().getFullYear() - profile.age); // indicate birth year is unknown + birthday.setUTCFullYear(new Date().getFullYear() - profile.age); } else { birthday.setUTCFullYear(0); // indicate birth year is unknown } diff --git a/src/scrapers/score.js b/src/scrapers/score.js index 599ea47d..b3949dc3 100755 --- a/src/scrapers/score.js +++ b/src/scrapers/score.js @@ -1,253 +1,276 @@ 'use strict'; -const { ex, exa, get } = require('../utils/q'); +const unprint = require('unprint'); + const slugify = require('../utils/slugify'); -const http = require('../utils/http'); -const { heightToCm, lbsToKg } = require('../utils/convert'); +const { stripQuery } = require('../utils/url'); +const { convert } = require('../utils/convert'); -function scrapePhotos(html) { - const { qis } = ex(html, '#photos-page'); - const photos = qis('img'); +const sizeRegex = /_lg|_xl|_tn/; - return photos.map((photo) => [ - photo - .replace('x_800', 'x_xl') - .replace('_tn', ''), - photo, - ]); -} - -async function fetchPhotos(url) { - const res = await http.get(url); - - if (res.statusCode === 200) { - return scrapePhotos(res.body.toString(), url); +function resizeSrc(src) { + if (!src) { + return null; } - return []; + return Array.from(new Set([ + src.replace(sizeRegex, '_1280'), + src.replace(sizeRegex, '_800'), + src.replace(sizeRegex, '_xl'), + src, + ])); } -function scrapeAll(html, site) { - return exa(html, '.container .video, .container-fluid .video').map(({ q, qa, qd, ql }) => { +function scrapeAll(scenes, channel, parameters) { + return scenes.map(({ query }) => { const release = {}; + const poster = query.img('.item-img img'); - release.title = q('.title, .i-title', true); + const url = stripQuery(query.url('a.i-title, .item-img a')); - const linkEl = q('a'); - const url = new URL(linkEl.href); - release.url = `${url.origin}${url.pathname}`; + release.title = query.content('a.i-title, h2.i-title'); + release.duration = query.duration('.time-ol'); - // this is a photo album, not a scene (used for profiles) - if (/photos\//.test(url)) return null; + release.date = query.date('.i-date', ['MMM. Do', 'MMM. YYYY'], { match: /(\w+\.? \d{1,2}\w+)|(\w+\.? \d{4})/ }); - [release.entryId] = url.pathname.split('/').slice(-2); + if (!release.date) { + const date = query.dateAgo('.i-date'); - release.date = qd('.i-date', 'MMM DD', /\w+ \d{1,2}$/) - || qd('.dt-box', 'MMM.DD YYYY'); - release.actors = site?.parameters?.actors || qa('.model, .i-model', true); - release.duration = ql('.i-amount, .amount'); - - const posterEl = q('.item-img img'); - - if (posterEl) { - release.poster = `https:${posterEl.src}`; + if (date) { + release.date = date.date; + release.datePrecision = date.precision === 'week' ? 'month' : date.precision; + } } - if (posterEl?.dataset.gifPreview) { - release.teaser = { - src: `https:${posterEl.dataset.gifPreview}`, - }; + release.actors = query.content('.i-model').split(',').map((actor) => actor.trim()); + + if (url.includes('join.') || url.includes('/join')) { + // no link available, attempt to reconstruct from poster URL + const entryId = poster?.match(/posting_(\d+)/)?.[1]; + + if (entryId) { + // we can get deep data from this + release.entryId = entryId; + release.url = `${channel.origin}${parameters.path}/${slugify(release.actors[0], '-', { lower: false })}/${entryId}/`; + } else { + // lost cause, make up entryId to register shallow data + release.entryId = slugify(release.title); + } + } else { + release.url = url; + release.entryId = new URL(release.url).pathname.match(/\/(\d+)\/?$/)[1]; } + if (poster) { + const caps = Array.from(new Set(Array.from({ length: 6 }, (_src, index) => { + const file = `${String(index + 1).padStart(2, '0')}_lg`; + + return poster.replace(/0\d_lg/, file); + }))).map((src) => resizeSrc(src)); + + release.poster = Array.from({ length: caps[0].length }).flatMap((_value, index) => caps.map((src) => src[index])); // try all the best sources first + + if (caps.length > 1) { + release.caps = caps; + } + } + + release.photos = query.imgs('.thumbs img'); // cards layout + + release.teaser = [ + query.video('.preview-clip source[type="video/mp4"]'), + query.video('.preview-clip source[type="video/webm"]'), + ].filter(Boolean); + return release; - }).filter(Boolean); + }); } -async function scrapeScene(html, url, site) { - const { qu } = ex(html, '#videos-page, #content section'); +async function fetchLatest(channel, page = 1, { parameters }) { + const res = await unprint.get(`${channel.origin}${parameters.path}/?page=${page}`, { + interface: 'request', // seemingly less prone to HTTPParserError: Response does not match the HTTP/1.1 protocol (Invalid character in chunk size) + selectAll: '.videos .video, .video-wide', // video-wide for cards layout e.g. Big Boobs POV + }); + + if (res.ok) { + return scrapeAll(res.context, channel, parameters); + } + + return res.status; +} + +function scrapeScene({ query }, url) { const release = {}; - [release.entryId] = new URL(url).pathname.split('/').slice(-2); + const info = Object.fromEntries(query.all('.stat').map((infoEl) => [ + slugify(unprint.query.content(infoEl, '.label')), + unprint.query.content(infoEl, '.value'), + ])); - release.title = qu.q('h2.text-uppercase, h2.title, #breadcrumb-top + h1', true) - || qu.q('h1.m-title', true)?.split(/ยป|\//).slice(-1)[0].trim(); - release.description = qu.text('.p-desc, .desc'); + release.url = stripQuery(url); + release.entryId = new URL(url).pathname.match(/\/(\d+)\/?$/)[1]; - release.actors = qu.all('.value a[href*=models], .value a[href*=performer], .value a[href*=teen-babes]', true); + release.title = query.content('.p-desc h2, #videos_page-page h1'); + release.description = query.text('.p-desc, .desc'); - if (release.actors.length === 0) { - const actorEl = qu.all('.stat').find((stat) => /Featuring/.test(stat.textContent)); - const actorString = qu.text(actorEl); + release.date = unprint.extractDate(info.date, 'MMMM Do, YYYY', { match: /\w+ \d{1,2}\w+, \d{4}/ }); + release.duration = unprint.extractDuration(info.duration) || Number(info.duration) * 60 || null; - release.actors = actorString?.split(/,\band\b|,/g).map((actor) => actor.trim()) || []; - } + release.actors = query.all('//span[contains(text(), "Featuring")]/following-sibling::span/a').map((actorEl) => ({ + name: unprint.query.content(actorEl), + url: stripQuery(unprint.query.url(actorEl, null)), + })); - if (release.actors.length === 0 && site.parameters?.actors) release.actors = site.parameters.actors; + release.tags = query.contents('.p-desc a[href*="tag/"], .desc a[href*="tag/"]'); - release.tags = qu.all('a[href*=tag]', true); + const style = query.content('.vp style'); + const poster = query.img('#videos_page-page .item-img img') || style?.match(/background-image: url\('(http[\w.:/_-]+)'\);/)?.[1]; + const fallbackPoster = resizeSrc(query.img('meta[itemprop="image"]', { attribute: 'content' })); // usually a different image - const dateEl = qu.all('.value').find((el) => /\w+ \d+\w+, \d{4}/.test(el.textContent)); - release.date = qu.date(dateEl, null, 'MMMM Do, YYYY') - || qu.date('.date', 'MMMM Do, YYYY', /\w+ \d{1,2}\w+, \d{4}/) - || qu.date('.info .holder', 'MM/DD/YYYY', /\d{2}\/\d{2}\/\d{4}/); + const photos = query.all('.gallery .thumb').map((imgEl) => { + const link = unprint.query.url(imgEl, 'a'); + const img = unprint.query.img(imgEl, 'img'); + const isJoin = !link || link.includes('join.') || link.includes('/join'); - const durationEl = qu.all('value').find((el) => /\d{1,3}:\d{2}/.test(el.textContent)); - release.duration = qu.dur(durationEl); + return Array.from(new Set([ + ...isJoin ? [] : [link], + img.replace('_tn', ''), + img, + ])); + }); - release.poster = qu.poster('video') || qu.img('.flowplayer img') || html.match(/posterImage: '(.*\.jpg)'/)?.[1] || null; // _800.jpg is larger than _xl.jpg in landscape - const photosUrl = qu.url('.stat a[href*=photos]'); + if (poster) { + release.poster = resizeSrc(poster); - if (photosUrl) { - release.photos = await fetchPhotos(photosUrl); + if (fallbackPoster?.includes(poster)) { + release.photos = [fallbackPoster, ...photos]; // fallback poster isn't usually in photoset, append + } else { + release.photos = photos; + } } else { - release.photos = qu.imgs('img[src*=ThumbNails], .p-photos .tn img').map((photo) => [ - photo.replace('_tn', ''), - photo, - ]); + release.poster = fallbackPoster; + release.photos = photos; } - const trailers = qu.all('a[href*=Trailers]'); - - if (trailers) { - release.trailer = trailers.map((trailer) => { - const src = `https:${trailer.href}`; - const format = trailer.textContent.trim().match(/^\w+/)[0].toLowerCase(); - const quality = parseInt(trailer.textContent.trim().match(/\d+([a-zA-Z]+)?$/)[0], 10); - - return format === 'mp4' ? { src, quality } : null; - }).filter(Boolean); - } - - const stars = qu.q('.rate-box').dataset.score; - if (stars) release.rating = { stars }; + release.trailer = query.all('.vp video source').map((videoEl) => ({ + src: unprint.query.video(videoEl, null), + quality: parseInt(unprint.query.attribute(videoEl, null, 'res'), 10) || null, + })); return release; } -function scrapeModels(html, actorName) { - const { qa } = ex(html); - const model = qa('.model a').find((link) => link.title === actorName); - - return model?.href || null; -} - -async function fetchActorReleases(url, accReleases = []) { - const res = await get(url); +async function fetchScene(url, channel, baseRelease) { + const res = await unprint.get(url, { + interface: 'request', + }); if (res.ok) { - const releases = accReleases.concat(scrapeAll(res.item.document.body.outerHTML)); - const nextPage = res.item.qu.url('.next-pg'); - - if (nextPage && new URL(nextPage).searchParams.has('page')) { // last page has 'next' button linking to join page - return fetchActorReleases(nextPage, releases); - } - - return releases; + return scrapeScene(res.context, url, channel, baseRelease); } - return null; + return res.status; } -async function scrapeProfile(html, actorUrl, withReleases) { - const { q, qa, qi } = ex(html, '#model-page'); - const profile = { gender: 'female' }; +function scrapeProfile({ query }, url) { + const profile = { url }; + const { pathname } = new URL(url); - const bio = qa('.stat').reduce((acc, el) => { - const prop = q(el, '.label', true).slice(0, -1); - const key = slugify(prop, '_'); - const value = q(el, '.value', true); + const bio = Object.fromEntries(query.all('.m-info .stat').map((bioEl) => [ + slugify(unprint.query.content(bioEl, '.label'), '_'), + unprint.query.content(bioEl, '.value'), + ])); - return { - ...acc, - [key]: value, - }; - }, {}); - - if (bio.location) profile.residencePlace = bio.location.replace('Czech Repulic', 'Czech Republic'); // see Laura Lion - - if (bio.birthday) { - const birthMonth = bio.birthday.match(/^\w+/)[0].toLowerCase(); - const [birthDay] = bio.birthday.match(/\d+/); - - profile.birthday = [birthMonth, birthDay]; // currently unused, not to be confused with birthdate + if (pathname.includes('big-boob-models')) { + profile.gender = 'female'; } - if (bio.ethnicity) profile.ethnicity = bio.ethnicity; - if (bio.hair_color) profile.hair = bio.hair_color; + if (pathname.includes('male-performer')) { + profile.gender = 'male'; + } - if (bio.height) profile.height = heightToCm(bio.height); - if (bio.weight) profile.weight = lbsToKg(bio.weight); + profile.avatar = query.img('.item-img a img:not([src*="posting"])'); - if (bio.bra_size) profile.bust = bio.bra_size; - if (bio.measurements) [, profile.waist, profile.hip] = bio.measurements.split('-'); + profile.placeOfResidence = bio.location; + profile.ethnicity = bio.ethnicity; - if (bio.occupation) profile.occupation = bio.occupation; + profile.height = convert(bio.height, 'cm'); + profile.weight = convert(bio.weight, 'lb', 'kg'); - const avatar = qi('img'); - if (avatar) profile.avatar = avatar; + if (bio.bra_size && bio.measurements) { + profile.measurements = bio.measurements.replace(/^\d+-/, `${bio.bra_size}-`); + } else { + profile.measurements = bio.measurements || bio.bra_size; + } - if (withReleases) { - const { origin, pathname } = new URL(actorUrl); - profile.releases = await fetchActorReleases(`${origin}${pathname}/scenes?page=1`); + profile.hairColor = bio.hair_color; + + const birthday = unprint.extractDate(bio.birthday, 'MMMM D', { match: /\w+.?\s+\d{1,2}/ }); + + if (birthday) { + birthday.setFullYear(0); // indicate birth year is unknown + profile.dateOfBirth = birthday; } return profile; } -async function fetchLatest(site, page = 1) { - const latestPath = site.parameters?.path || '/big-boob-videos'; - const url = `${site.url}${latestPath}?page=${page}`; - const res = await http.get(url); - - if (res.statusCode === 200) { - return scrapeAll(res.body.toString(), site); +async function getActorUrl(actor) { + if (actor.url) { + return actor.url; } - return res.statusCode; -} + const searchRes = await unprint.post('https://www.scoreland.com/search-es/', { + keywords: actor.name, + 's_filters[site]': 'all', + 's_filters[type]': 'models', + }, { + interface: 'request', + form: true, + followRedirects: false, + }); -async function fetchScene(url, site) { - const res = await http.get(url); + const res = await unprint.get(searchRes.headers.location, { + interface: 'request', + cookies: { + cisession: searchRes.cookies.cisession, + }, + // followRedirects: false, + selectAll: '.li-item.model', + }); - if (res.statusCode === 200) { - return scrapeScene(res.body.toString(), url, site); + if (res.ok) { + const actorEl = res.context.find(({ query }) => slugify(query.content('.i-model')) === actor.slug); + const url = actorEl?.query.url('.i-model'); + + if (url) { + // messy nats link pointing to unpredictable sites, all data seems to be available on scoreland + const { pathname } = new URL(url); + const actorPath = pathname.match(/\/[\w-]+\/\d+\/?$/); + + if (actorPath) { + return `https://www.scoreland.com/big-boob-models${actorPath[0]}`; + } + } } return null; } -async function fetchProfile({ name: actorName }, context, include, page = 1, source = 0) { - const letter = actorName.charAt(0).toUpperCase(); +async function fetchProfile(actor) { + const url = await getActorUrl(actor); - const sources = [ - `https://www.scoreland.com/big-boob-models/browse/${letter}/?page=${page}`, - `https://www.50plusmilfs.com/xxx-milf-models/browse/${letter}/?page=${page}`, - ]; + if (url) { + const res = await unprint.get(url, { + interface: 'request', + select: '#model-page', + }); - const url = sources[source]; - - const res = await http.get(url, { - followRedirects: false, - }); - - if (res.statusCode === 200) { - const actorUrl = scrapeModels(res.body.toString(), actorName); - - if (actorUrl) { - const actorRes = await http.get(actorUrl); - - if (actorRes.statusCode === 200) { - return scrapeProfile(actorRes.body.toString(), actorUrl, include.scenes); - } - - return null; + if (res.ok) { + return scrapeProfile(res.context, url); } - return fetchProfile({ name: actorName }, context, include, page + 1, source); - } - - if (sources[source + 1]) { - return fetchProfile({ name: actorName }, context, include, 1, source + 1); + return res.status; } return null; diff --git a/tests/profiles.js b/tests/profiles.js index b0f9e7ad..756630a6 100644 --- a/tests/profiles.js +++ b/tests/profiles.js @@ -174,6 +174,7 @@ const actors = [ { entity: 'testedefudelidade', name: 'May Akemi', fields: ['avatar'] }, { entity: 'sexlikereal', name: 'Agatha Vega', fields: ['avatar', 'birthPlace', 'height', 'weight', 'description'] }, { entity: 'porncz', name: 'Kama Oxi', fields: ['avatar', 'gender', 'birthCountry', 'ethnicity', 'age', 'hairColor', 'cup', 'naturalBoobs', 'hasTattoos'] }, + { entity: 'score', name: 'Vanessa Blue', fields: ['avatar', 'gender', 'placeOfResidence', 'ethnicity', 'height', 'weight', 'measurements', 'hairColor', 'dateOfBirth'] }, ]; const actorScrapers = scrapers.actors;