diff --git a/.gitignore b/.gitignore index da8074f2..71781010 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ public/js/* public/css/* config/* !config/default.js +assets/js/config/ +!assets/js/config/default.js diff --git a/assets/js/main.js b/assets/js/main.js index 7d23d2ff..98c8466d 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -15,11 +15,11 @@ function init() { watch: { pageTitle(title) { if (title) { - document.title = `Porn Radar - ${title}`; + document.title = `traxxx - ${title}`; return; } - document.title = 'Porn Radar'; + document.title = 'traxxx'; }, }, methods: { diff --git a/assets/views/header.jsx b/assets/views/header.jsx deleted file mode 100644 index 012099be..00000000 --- a/assets/views/header.jsx +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const React = require('react'); - -const Header = () => ( -
-

Porn Radar

-
-); - -module.exports = Header; diff --git a/assets/views/home.jsx b/assets/views/home.jsx deleted file mode 100644 index 0c06fe77..00000000 --- a/assets/views/home.jsx +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -const React = require('react'); -const moment = require('moment'); -const PropTypes = require('prop-types'); - -const Layout = require('./layout.jsx'); - -const Home = ({ releases }) => ( - - - -); - -Home.propTypes = { - releases: PropTypes.arrayOf(PropTypes.object), -}; - -Home.defaultProps = { - releases: [], -}; - -module.exports = Home; diff --git a/assets/views/layout.jsx b/assets/views/layout.jsx deleted file mode 100644 index fde287a5..00000000 --- a/assets/views/layout.jsx +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const React = require('react'); -const PropTypes = require('prop-types'); - -const Header = require('./header.jsx'); - -const Layout = ({ children, title }) => ( - - - {title - ? Porn Radar | {title} - : Porn Radar - } - - - - - -
- -
- {children} -
- - -); - -Layout.propTypes = { - children: PropTypes.node.isRequired, - title: PropTypes.string, -}; - -Layout.defaultProps = { - title: null, -}; - -module.exports = Layout; diff --git a/assets/views/release.jsx b/assets/views/release.jsx deleted file mode 100644 index c1dc7540..00000000 --- a/assets/views/release.jsx +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -const React = require('react'); -const PropTypes = require('prop-types'); -const moment = require('moment'); - -const Layout = require('./layout.jsx'); - -const Release = ({ release, thumbnails }) => ( - -
-

{release.title}

-

{moment(release.date).format('MMMM DD, YYYY')}

-

{release.shootId}

- -

- - {release.site.name} - -

- -

{release.network.name}

- -

- - View on {new URL(release.site.url).host.replace('www.', '')} - -

- - - -

{release.description}

- - - - {thumbnails.map((thumbnail, index) => ( - {`Thumbnail - ))} -
-
-); - -Release.propTypes = { - release: PropTypes.object, - thumbnails: PropTypes.arrayOf(PropTypes.string), -}; - -Release.defaultProps = { - release: null, - thumbnails: [], -}; - -module.exports = Release; diff --git a/public/index.html b/public/index.html index 17ad9000..7e260e61 100644 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,7 @@ - Porn Radar + traxxx diff --git a/seeds/00_networks.js b/seeds/00_networks.js index b0a3a236..ca950512 100644 --- a/seeds/00_networks.js +++ b/seeds/00_networks.js @@ -1,5 +1,3 @@ -'use strict'; - /* eslint-disable max-len */ exports.seed = knex => Promise.resolve() .then(() => knex.raw(`${knex('networks').insert([ @@ -33,6 +31,12 @@ exports.seed = knex => Promise.resolve() url: 'https://ddfnetwork.com', description: 'European porn videos hub with exclusive VR, 4K and full HD XXX videos and hot sex photos of Europes finest porn star babes.', }, + { + slug: 'evilangel', + name: 'Evil Angel', + url: 'https://evilangel.com', + description: 'Welcome to the award winning Evil Angel website, home to the most popular pornstars of today, yesterday and tomorrow in their most extreme and hardcore porn scenes to date. We feature almost 30 years of rough sex videos and hardcore anal porn like you\'ve never seen before, and have won countless AVN and XBiz awards including \'Best Site\' and \'Best Studio\'.', + }, { slug: 'julesjordan', name: 'Jules Jordan', diff --git a/seeds/01_sites.js b/seeds/01_sites.js index 15cf6640..af0fb923 100644 --- a/seeds/01_sites.js +++ b/seeds/01_sites.js @@ -828,6 +828,14 @@ exports.seed = knex => Promise.resolve() description: 'Fantasy Blowjobs & POV Cock Sucking Videos and Photos Produced in VR, 4K and full HD featuring Sexy European Pornstars', network_id: networksMap['ddfnetwork'], }, + // EVIL ANGEL + { + slug: 'evilangel', + name: 'Evil Angel', + url: 'https://evilangel.com', + description: 'Welcome to the award winning Evil Angel website, home to the most popular pornstars of today, yesterday and tomorrow in their most extreme and hardcore porn scenes to date. We feature almost 30 years of rough sex videos and hardcore anal porn like you\'ve never seen before, and have won countless AVN and XBiz awards including \'Best Site\' and \'Best Studio\'.', + network_id: networksMap['evilangel'], + }, // JULES JORDAN { slug: 'julesjordan', diff --git a/seeds/02_tags.js b/seeds/02_tags.js index f21275d8..7b1e1b43 100644 --- a/seeds/02_tags.js +++ b/seeds/02_tags.js @@ -721,6 +721,11 @@ exports.seed = knex => Promise.resolve() slug: 'toys', alias_for: null, }, + { + tag: 'transsexual', + slug: 'transsexual', + alias_for: null, + }, { tag: 'triple penetration', slug: 'triple-penetration', @@ -1157,6 +1162,10 @@ exports.seed = knex => Promise.resolve() tag: 'mff', alias_for: tagsMap['fmf'], }, + { + tag: 'mature & milf', + alias_for: tagsMap['milf'], + }, { tag: 'natural', alias_for: tagsMap['natural-boobs'], @@ -1321,6 +1330,10 @@ exports.seed = knex => Promise.resolve() tag: 'tittyfuck', alias_for: tagsMap['titty-fuck'], }, + { + tag: 'trans', + alias_for: tagsMap['transsexual'], + }, { tag: 'trimmed pussy', alias_for: tagsMap['trimmed'], diff --git a/src/fetch-releases.js b/src/fetch-releases.js index 92184ecd..0b8b238f 100644 --- a/src/fetch-releases.js +++ b/src/fetch-releases.js @@ -317,10 +317,13 @@ async function fetchReleases() { } console.log(`${site.id}: Failed to fetch releases`); + return []; } } + console.error(`Cound not find scraper for '${site.name}' (${site.slug})`); + return []; }, { concurrency: 2, diff --git a/src/scrapers/evilangel.js b/src/scrapers/evilangel.js new file mode 100644 index 00000000..d420297d --- /dev/null +++ b/src/scrapers/evilangel.js @@ -0,0 +1,142 @@ +'use strict'; + +const bhttp = require('bhttp'); +const cheerio = require('cheerio'); +const moment = require('moment'); + +const { matchTags } = require('../tags'); + +async function scrape(json, site) { + return Promise.all(json.map(async (scene) => { + const { + title, + description, + length, + master_categories: rawTags, + ratings_up: likes, + ratings_down: dislikes, + } = scene; + + const entryId = scene.clip_id; + const url = `https://evilangel.com/en/video/${scene.url_title}/${entryId}`; + const date = moment(scene.release_date, 'YYYY-MM-DD').toDate(); + const actors = scene.actors.map(({ name }) => name); + const director = scene.directors[0].name; + + const tags = await matchTags(rawTags); + const poster = `https://images-evilangel.gammacdn.com/movies${scene.pictures.resized}`; + + return { + url, + entryId, + title, + description, + length, + actors, + director, + date, + tags, + poster, + rating: { + likes, + dislikes, + }, + site, + }; + })); +} + +async function scrapeScene(html, url, site) { + const $ = cheerio.load(html, { normalizeWhitespace: true }); + const json = $('script[type="application/ld+json"]').html(); + const videoJson = $('script:contains("window.ScenePlayerOptions")').html(); + + const [data, data2] = JSON.parse(json); + const videoData = JSON.parse(videoJson.slice(videoJson.indexOf('{'), videoJson.indexOf('};') + 1)); + const entryId = new URL(url).pathname.split('/').slice(-1)[0]; + + const { + name: title, + description, + } = data; + // date in data object is not the release date of the scene, but the date the entry was added + const date = moment.utc($('.updatedDate').first().text(), 'MM-DD-YYYY').toDate(); + + const actors = data.actor.map(actor => actor.name); + + const director = (data.director && data.director[0].name) || (data2.director && data2.director[0].name) || null; + const stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5; + + const duration = moment.duration(data.duration.slice(2).split(':')).asSeconds(); + + const rawTags = data.keywords.split(', '); + const tags = await matchTags(rawTags); + + const poster = videoData.picPreview; + const trailer = `${videoData.playerOptions.host}${videoData.url}`; + + return { + url, + entryId, + title, + date, + actors, + director, + description, + duration, + tags, + poster, + trailer: { + src: trailer, + quality: parseInt(videoData.sizeOnLoad, 10), + }, + rating: { + stars, + }, + site, + }; +} + +async function fetchLatest(site, page = 1, upcoming = false) { + const res = await bhttp.get('https://evilangel.com/en/videos'); + const body = res.body.toString(); + + const apiLine = body.split('\n').find(bodyLine => bodyLine.match('apiKey')); + const apiSerial = apiLine.slice(apiLine.indexOf('{'), apiLine.indexOf('};') + 1); + const apiData = JSON.parse(apiSerial); + + const { applicationID: appId, apiKey } = apiData.api.algolia; + const userAgent = 'Algolia for vanilla JavaScript (lite) 3.27.0;instantsearch.js 2.7.4;JS Helper 2.26.0'; + + const apiRes = await bhttp.post(`https://${appId.toLowerCase()}-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=${userAgent}&x-algolia-application-id=${appId}&x-algolia-api-key=${apiKey}`, { + requests: [ + { + indexName: 'all_scenes', + params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? '' : 0}"]]`, + }, + ], + }, { + headers: { + Referer: 'https://www.evilangel.com/en/videos', + }, + encodeJSON: true, + }); + + return scrape(apiRes.body.results[0].hits, site); +} + +async function fetchUpcoming(site) { + return fetchLatest(site, 1, true); +} + +async function fetchScene(url, site) { + const res = await bhttp.get(url); + + return scrapeScene(res.body.toString(), url, site); +} + +module.exports = { + fetchLatest, + fetchUpcoming, + fetchScene, +}; diff --git a/src/scrapers/index.js b/src/scrapers/index.js index 3c087e88..3ca6637b 100644 --- a/src/scrapers/index.js +++ b/src/scrapers/index.js @@ -5,6 +5,7 @@ const bangbros = require('./bangbros'); const blowpass = require('./blowpass'); const brazzers = require('./brazzers'); const ddfnetwork = require('./ddfnetwork'); +const evilangel = require('./evilangel'); const julesjordan = require('./julesjordan'); const kink = require('./kink'); const legalporno = require('./legalporno'); @@ -22,6 +23,7 @@ module.exports = { blowpass, brazzers, ddfnetwork, + evilangel, julesjordan, kink, legalporno,