@@ -210,19 +211,19 @@ import Search from './search.vue';
async function addAlert() {
await this.$store.dispatch('addAlert', {
- actors: this.actors.map(actor => actor.id),
- tags: this.tags.map(tag => tag.id),
+ actors: this.actors.map((actor) => actor.id),
+ tags: this.tags.map((tag) => tag.id),
entity: this.entity?.id,
notify: this.notify,
email: this.email,
- stashes: this.stashes.map(stash => stash.id),
+ stashes: this.stashes.map((stash) => stash.id),
});
this.$emit('close', true);
}
function addActor(actor) {
- if (!this.actors.some(selectedActor => selectedActor.id === actor.id)) {
+ if (!this.actors.some((selectedActor) => selectedActor.id === actor.id)) {
this.actors = this.actors.concat(actor);
}
@@ -235,7 +236,7 @@ function addEntity(entity) {
}
function addTag(tag) {
- if (!this.tags.some(selectedTag => selectedTag.id === tag.id)) {
+ if (!this.tags.some((selectedTag) => selectedTag.id === tag.id)) {
this.tags = this.tags.concat(tag);
}
@@ -243,7 +244,7 @@ function addTag(tag) {
}
function removeActor(actor) {
- this.actors = this.actors.filter(listedActor => listedActor.id !== actor.id);
+ this.actors = this.actors.filter((listedActor) => listedActor.id !== actor.id);
}
function removeEntity() {
@@ -251,11 +252,11 @@ function removeEntity() {
}
function removeTag(tag) {
- this.tags = this.tags.filter(listedTag => listedTag.id !== tag.id);
+ this.tags = this.tags.filter((listedTag) => listedTag.id !== tag.id);
}
function addStash(stash) {
- if (!this.stashes.some(selectedStash => selectedStash.id === stash.id)) {
+ if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) {
this.stashes = this.stashes.concat(stash);
}
@@ -263,7 +264,7 @@ function addStash(stash) {
}
function removeStash(stash) {
- this.stashes = this.stashes.filter(listedStash => listedStash.id !== stash.id);
+ this.stashes = this.stashes.filter((listedStash) => listedStash.id !== stash.id);
}
export default {
@@ -273,6 +274,7 @@ export default {
Entity,
Search,
},
+ emits: ['close'],
data() {
return {
actors: [],
@@ -284,7 +286,6 @@ export default {
availableStashes: this.$store.state.auth.user.stashes,
};
},
- emits: ['close'],
methods: {
addActor,
addAlert,
diff --git a/assets/components/container/container.vue b/assets/components/container/container.vue
index d770bbc5..5dea40d6 100644
--- a/assets/components/container/container.vue
+++ b/assets/components/container/container.vue
@@ -62,7 +62,7 @@ async function setConsent(consent, includeQueer) {
}
if (includeQueer) {
- this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter(tag => !['gay', 'bisexual', 'transsexual'].includes(tag)));
+ this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)));
return;
}
diff --git a/assets/components/entities/entity.vue b/assets/components/entities/entity.vue
index a52da49c..4007c035 100644
--- a/assets/components/entities/entity.vue
+++ b/assets/components/entities/entity.vue
@@ -108,6 +108,7 @@
:fetch-releases="fetchEntity"
:items-total="totalCount"
:items-per-page="limit"
+ :available-tags="entity.tags"
/>
@@ -153,8 +154,8 @@ async function fetchEntity(scroll = true) {
this.pageTitle = entity.name;
- const campaign = entity.campaigns.find(campaignX => !campaignX.banner)
- || entity.parent?.campaigns.find(campaignX => !campaignX.banner);
+ const campaign = entity.campaigns.find((campaignX) => !campaignX.banner)
+ || entity.parent?.campaigns.find((campaignX) => !campaignX.banner);
const affiliateParams = new URLSearchParams({
...(entity.url && Object.fromEntries(new URL(entity.url).searchParams)), // preserve any query in entity URL, e.g. ?siteId=5
diff --git a/assets/components/entities/tile.vue b/assets/components/entities/tile.vue
index 0bade8e8..1bb34e9a 100644
--- a/assets/components/entities/tile.vue
+++ b/assets/components/entities/tile.vue
@@ -41,9 +41,10 @@
- {{ entity.sceneTotal }} scenes
+ {{ entity.sceneTotal }} scenes
{{ entity.childrenTotal }} channels
@@ -78,9 +79,10 @@ export default {
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
text-decoration: none;
+ overflow: hidden;
&:hover .count {
- color: var(--lighten);
+ bottom: 0;
}
}
@@ -100,6 +102,8 @@ export default {
}
.name {
+ display: flex;
+ align-items: center;
color: var(--text-light);
font-size: 1.25rem;
font-weight: bold;
@@ -109,11 +113,15 @@ export default {
display: flex;
justify-content: space-between;
width: 100%;
+ position: absolute;
+ bottom: -1.75rem;
box-sizing: border-box;
padding: .25rem .5rem;
- border-top: solid 1px var(--lighten-hint);
- color: var(--lighten-weak);
+ background: var(--darken-strong);
+ box-shadow: 0 0 3px var(--darken);
+ color: var(--text-light);
text-align: center;
- font-size: .8rem;
+ text-shadow: 1px 1px var(--darken);
+ transition: bottom .1s ease;
}
diff --git a/assets/components/filters/filter-bar.vue b/assets/components/filters/filter-bar.vue
index 944862d1..2b6551db 100644
--- a/assets/components/filters/filter-bar.vue
+++ b/assets/components/filters/filter-bar.vue
@@ -18,19 +18,16 @@
@@ -44,10 +41,6 @@ import ActorFilter from './actor-filter.vue';
import ChannelFilter from './channel-filter.vue';
import TagFilter from './tag-filter.vue';
-function filter(state) {
- return state.ui.filter;
-}
-
function range() {
return this.$route.params.range;
}
@@ -114,7 +107,6 @@ export default {
},
computed: {
...mapState({
- filter,
range,
batch,
}),
@@ -129,6 +121,43 @@ export default {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/img/logos/zerotolerance/misc/zero-tolerance-films.png b/public/img/logos/zerotolerance/misc/zero-tolerance-films.png
new file mode 100644
index 00000000..840d28af
Binary files /dev/null and b/public/img/logos/zerotolerance/misc/zero-tolerance-films.png differ
diff --git a/public/img/logos/zerotolerance/misc/zero-tolerance-films.svg b/public/img/logos/zerotolerance/misc/zero-tolerance-films.svg
new file mode 100644
index 00000000..1560cda9
--- /dev/null
+++ b/public/img/logos/zerotolerance/misc/zero-tolerance-films.svg
@@ -0,0 +1,572 @@
+
+
+
diff --git a/public/img/logos/zerotolerance/thumbs/addicted2girls.png b/public/img/logos/zerotolerance/thumbs/addicted2girls.png
index c9067dfa..18044171 100644
Binary files a/public/img/logos/zerotolerance/thumbs/addicted2girls.png and b/public/img/logos/zerotolerance/thumbs/addicted2girls.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/favicon.png b/public/img/logos/zerotolerance/thumbs/favicon.png
index 492f4412..dcf6c232 100644
Binary files a/public/img/logos/zerotolerance/thumbs/favicon.png and b/public/img/logos/zerotolerance/thumbs/favicon.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/favicon_dark.png b/public/img/logos/zerotolerance/thumbs/favicon_dark.png
index ff18bd84..e4938a58 100644
Binary files a/public/img/logos/zerotolerance/thumbs/favicon_dark.png and b/public/img/logos/zerotolerance/thumbs/favicon_dark.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/favicon_light.png b/public/img/logos/zerotolerance/thumbs/favicon_light.png
index ff18bd84..e4938a58 100644
Binary files a/public/img/logos/zerotolerance/thumbs/favicon_light.png and b/public/img/logos/zerotolerance/thumbs/favicon_light.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/genderx.png b/public/img/logos/zerotolerance/thumbs/genderx.png
index 94937f59..f00de314 100644
Binary files a/public/img/logos/zerotolerance/thumbs/genderx.png and b/public/img/logos/zerotolerance/thumbs/genderx.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/genderxfilms.png b/public/img/logos/zerotolerance/thumbs/genderxfilms.png
new file mode 100644
index 00000000..ce758006
Binary files /dev/null and b/public/img/logos/zerotolerance/thumbs/genderxfilms.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/network.png b/public/img/logos/zerotolerance/thumbs/network.png
index 05dd3b3b..137e9a8e 100644
Binary files a/public/img/logos/zerotolerance/thumbs/network.png and b/public/img/logos/zerotolerance/thumbs/network.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/zerotolerance.png b/public/img/logos/zerotolerance/thumbs/zerotolerance.png
index 58351010..9916977e 100644
Binary files a/public/img/logos/zerotolerance/thumbs/zerotolerance.png and b/public/img/logos/zerotolerance/thumbs/zerotolerance.png differ
diff --git a/public/img/logos/zerotolerance/thumbs/zerotolerancefilms.png b/public/img/logos/zerotolerance/thumbs/zerotolerancefilms.png
new file mode 100644
index 00000000..56f067a5
Binary files /dev/null and b/public/img/logos/zerotolerance/thumbs/zerotolerancefilms.png differ
diff --git a/public/img/logos/zerotolerance/zerotolerancefilms.png b/public/img/logos/zerotolerance/zerotolerancefilms.png
new file mode 100644
index 00000000..840d28af
Binary files /dev/null and b/public/img/logos/zerotolerance/zerotolerancefilms.png differ
diff --git a/public/img/tags/airtight/adriana_chechik_hardx.jpeg b/public/img/tags/airtight/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..bb816d50
Binary files /dev/null and b/public/img/tags/airtight/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/airtight/lazy/adriana_chechik_hardx.jpeg b/public/img/tags/airtight/lazy/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..2731fa62
Binary files /dev/null and b/public/img/tags/airtight/lazy/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/airtight/thumbs/adriana_chechik_hardx.jpeg b/public/img/tags/airtight/thumbs/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..da7e0917
Binary files /dev/null and b/public/img/tags/airtight/thumbs/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/bisexual/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg b/public/img/tags/bisexual/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg
new file mode 100644
index 00000000..31bf6eeb
Binary files /dev/null and b/public/img/tags/bisexual/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg differ
diff --git a/public/img/tags/bisexual/lazy/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg b/public/img/tags/bisexual/lazy/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg
new file mode 100644
index 00000000..7ee06ab5
Binary files /dev/null and b/public/img/tags/bisexual/lazy/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg differ
diff --git a/public/img/tags/bisexual/thumbs/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg b/public/img/tags/bisexual/thumbs/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg
new file mode 100644
index 00000000..ae81b67c
Binary files /dev/null and b/public/img/tags/bisexual/thumbs/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_0.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_0.jpeg
new file mode 100644
index 00000000..aed5615c
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_0.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_0a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_0a.jpeg
new file mode 100644
index 00000000..246941fc
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_0a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_0b.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_0b.jpeg
new file mode 100644
index 00000000..126cecc9
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_0b.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_1.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_1.jpeg
new file mode 100644
index 00000000..f7ff6f65
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_1.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_2.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_2.jpeg
new file mode 100644
index 00000000..521d1357
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_2.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_2a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_2a.jpeg
new file mode 100644
index 00000000..f4924835
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_2a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_3.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_3.jpeg
new file mode 100644
index 00000000..04987fa0
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_3.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_3a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_3a.jpeg
new file mode 100644
index 00000000..23b66ebd
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_3a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_4.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_4.jpeg
new file mode 100644
index 00000000..6a2d3c24
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_4.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_4a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_4a.jpeg
new file mode 100644
index 00000000..c2c3d1a7
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_4a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_5.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_5.jpeg
new file mode 100644
index 00000000..ffaede25
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_5.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_5a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_5a.jpeg
new file mode 100644
index 00000000..ca738a4c
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_5a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_6.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_6.jpeg
new file mode 100644
index 00000000..54aac348
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_6.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_6a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_6a.jpeg
new file mode 100644
index 00000000..bd5b2502
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_6a.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_7.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_7.jpeg
new file mode 100644
index 00000000..b4ee52c6
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_7.jpeg differ
diff --git a/public/img/tags/blowbang/cory_chase_interracialblowbang_7a.jpeg b/public/img/tags/blowbang/cory_chase_interracialblowbang_7a.jpeg
new file mode 100644
index 00000000..5c17c9e5
Binary files /dev/null and b/public/img/tags/blowbang/cory_chase_interracialblowbang_7a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang.jpeg
new file mode 100644
index 00000000..65e73ffa
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0.jpeg
new file mode 100644
index 00000000..65e73ffa
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0a.jpeg
new file mode 100644
index 00000000..29360485
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0b.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0b.jpeg
new file mode 100644
index 00000000..8cb194d5
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_0b.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_1.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_1.jpeg
new file mode 100644
index 00000000..435381af
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_1.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2.jpeg
new file mode 100644
index 00000000..abe073bc
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2a.jpeg
new file mode 100644
index 00000000..1b6970f1
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_2a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3.jpeg
new file mode 100644
index 00000000..f58cb2f0
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3a.jpeg
new file mode 100644
index 00000000..076257f5
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_3a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4.jpeg
new file mode 100644
index 00000000..b61c2f6c
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4a.jpeg
new file mode 100644
index 00000000..20009f07
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_4a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5.jpeg
new file mode 100644
index 00000000..76fd680d
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5a.jpeg
new file mode 100644
index 00000000..22b8645b
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_5a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6.jpeg
new file mode 100644
index 00000000..6843665e
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6a.jpeg
new file mode 100644
index 00000000..4e69b846
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_6a.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7.jpeg
new file mode 100644
index 00000000..419361ee
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7.jpeg differ
diff --git a/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7a.jpeg b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7a.jpeg
new file mode 100644
index 00000000..6fb06843
Binary files /dev/null and b/public/img/tags/blowbang/lazy/cory_chase_interracialblowbang_7a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang.jpeg
new file mode 100644
index 00000000..c9e0827d
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0.jpeg
new file mode 100644
index 00000000..c9e0827d
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0a.jpeg
new file mode 100644
index 00000000..7811f5af
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0b.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0b.jpeg
new file mode 100644
index 00000000..141caf59
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_0b.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_1.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_1.jpeg
new file mode 100644
index 00000000..9fa13b46
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_1.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2.jpeg
new file mode 100644
index 00000000..d5643108
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2a.jpeg
new file mode 100644
index 00000000..efa20558
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_2a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3.jpeg
new file mode 100644
index 00000000..1821e20b
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3a.jpeg
new file mode 100644
index 00000000..6c2c07a1
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_3a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4.jpeg
new file mode 100644
index 00000000..06bade37
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4a.jpeg
new file mode 100644
index 00000000..585bea28
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_4a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5.jpeg
new file mode 100644
index 00000000..c3ac0639
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5a.jpeg
new file mode 100644
index 00000000..ae929f3a
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_5a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6.jpeg
new file mode 100644
index 00000000..b3b2434b
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6a.jpeg
new file mode 100644
index 00000000..dcc954a0
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_6a.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7.jpeg
new file mode 100644
index 00000000..e69c49fa
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7.jpeg differ
diff --git a/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7a.jpeg b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7a.jpeg
new file mode 100644
index 00000000..cc29615f
Binary files /dev/null and b/public/img/tags/blowbang/thumbs/cory_chase_interracialblowbang_7a.jpeg differ
diff --git a/public/img/tags/dvp/adriana_chechik_hardx.jpeg b/public/img/tags/dvp/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..23fe1825
Binary files /dev/null and b/public/img/tags/dvp/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/dvp/lazy/adriana_chechik_hardx.jpeg b/public/img/tags/dvp/lazy/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..0212b2f7
Binary files /dev/null and b/public/img/tags/dvp/lazy/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/dvp/thumbs/adriana_chechik_hardx.jpeg b/public/img/tags/dvp/thumbs/adriana_chechik_hardx.jpeg
new file mode 100644
index 00000000..51a7a246
Binary files /dev/null and b/public/img/tags/dvp/thumbs/adriana_chechik_hardx.jpeg differ
diff --git a/public/img/tags/enhanced-boobs/lazy/ricki_raxxx_pornpros.jpeg b/public/img/tags/enhanced-boobs/lazy/ricki_raxxx_pornpros.jpeg
new file mode 100644
index 00000000..610d1085
Binary files /dev/null and b/public/img/tags/enhanced-boobs/lazy/ricki_raxxx_pornpros.jpeg differ
diff --git a/public/img/tags/enhanced-boobs/ricki_raxxx_pornpros.jpeg b/public/img/tags/enhanced-boobs/ricki_raxxx_pornpros.jpeg
new file mode 100644
index 00000000..e625a2d1
Binary files /dev/null and b/public/img/tags/enhanced-boobs/ricki_raxxx_pornpros.jpeg differ
diff --git a/public/img/tags/enhanced-boobs/thumbs/ricki_raxxx_pornpros.jpeg b/public/img/tags/enhanced-boobs/thumbs/ricki_raxxx_pornpros.jpeg
new file mode 100644
index 00000000..28e716b8
Binary files /dev/null and b/public/img/tags/enhanced-boobs/thumbs/ricki_raxxx_pornpros.jpeg differ
diff --git a/public/img/tags/gay/andy_taylor_deangelo_jackson_men.jpeg b/public/img/tags/gay/andy_taylor_deangelo_jackson_men.jpeg
new file mode 100644
index 00000000..544cef69
Binary files /dev/null and b/public/img/tags/gay/andy_taylor_deangelo_jackson_men.jpeg differ
diff --git a/public/img/tags/gay/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg b/public/img/tags/gay/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg
new file mode 100644
index 00000000..31bf6eeb
Binary files /dev/null and b/public/img/tags/gay/bunny_colby_dante_colle_dillon_diaz_devilsfilm.jpeg differ
diff --git a/public/img/tags/gay/lazy/andy_taylor_deangelo_jackson_men.jpeg b/public/img/tags/gay/lazy/andy_taylor_deangelo_jackson_men.jpeg
new file mode 100644
index 00000000..f276ce8a
Binary files /dev/null and b/public/img/tags/gay/lazy/andy_taylor_deangelo_jackson_men.jpeg differ
diff --git a/public/img/tags/gay/thumbs/andy_taylor_deangelo_jackson_men.jpeg b/public/img/tags/gay/thumbs/andy_taylor_deangelo_jackson_men.jpeg
new file mode 100644
index 00000000..dd31d79e
Binary files /dev/null and b/public/img/tags/gay/thumbs/andy_taylor_deangelo_jackson_men.jpeg differ
diff --git a/public/img/tags/transsexual/amanda_fialho_tsraw.jpeg b/public/img/tags/transsexual/amanda_fialho_tsraw.jpeg
new file mode 100644
index 00000000..93ed7734
Binary files /dev/null and b/public/img/tags/transsexual/amanda_fialho_tsraw.jpeg differ
diff --git a/public/img/tags/transsexual/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg b/public/img/tags/transsexual/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg
new file mode 100644
index 00000000..ba9f9608
Binary files /dev/null and b/public/img/tags/transsexual/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg differ
diff --git a/public/img/tags/transsexual/lazy/amanda_fialho_tsraw.jpeg b/public/img/tags/transsexual/lazy/amanda_fialho_tsraw.jpeg
new file mode 100644
index 00000000..33ea2f84
Binary files /dev/null and b/public/img/tags/transsexual/lazy/amanda_fialho_tsraw.jpeg differ
diff --git a/public/img/tags/transsexual/lazy/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg b/public/img/tags/transsexual/lazy/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg
new file mode 100644
index 00000000..83c35adc
Binary files /dev/null and b/public/img/tags/transsexual/lazy/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg differ
diff --git a/public/img/tags/transsexual/thumbs/amanda_fialho_tsraw.jpeg b/public/img/tags/transsexual/thumbs/amanda_fialho_tsraw.jpeg
new file mode 100644
index 00000000..af94212f
Binary files /dev/null and b/public/img/tags/transsexual/thumbs/amanda_fialho_tsraw.jpeg differ
diff --git a/public/img/tags/transsexual/thumbs/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg b/public/img/tags/transsexual/thumbs/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg
new file mode 100644
index 00000000..b269a853
Binary files /dev/null and b/public/img/tags/transsexual/thumbs/kelly_silva_mel_almeida_brazilliantranssexuals.jpeg differ
diff --git a/public/img/tags/triple-penetration/lazy/lucky_bee_analvids.jpeg b/public/img/tags/triple-penetration/lazy/lucky_bee_analvids.jpeg
new file mode 100644
index 00000000..d5ae9656
Binary files /dev/null and b/public/img/tags/triple-penetration/lazy/lucky_bee_analvids.jpeg differ
diff --git a/public/img/tags/triple-penetration/lucky_bee_analvids.jpeg b/public/img/tags/triple-penetration/lucky_bee_analvids.jpeg
new file mode 100644
index 00000000..85118b65
Binary files /dev/null and b/public/img/tags/triple-penetration/lucky_bee_analvids.jpeg differ
diff --git a/public/img/tags/triple-penetration/thumbs/lucky_bee_analvids.jpeg b/public/img/tags/triple-penetration/thumbs/lucky_bee_analvids.jpeg
new file mode 100644
index 00000000..a96531ce
Binary files /dev/null and b/public/img/tags/triple-penetration/thumbs/lucky_bee_analvids.jpeg differ
diff --git a/seeds/00_tags.js b/seeds/00_tags.js
index 0729e6d0..e31e117c 100644
--- a/seeds/00_tags.js
+++ b/seeds/00_tags.js
@@ -392,14 +392,14 @@ const tags = [
name: 'triple anal',
slug: 'tap',
description: 'Getting fucked in the ass by not one, two, but *three* cocks at the same time.',
- priority: 7,
+ priority: 8,
group: 'penetration',
},
{
name: 'triple vaginal',
slug: 'tvp',
description: 'Getting fucked in the pussy by *three* cocks at the same time.',
- priority: 7,
+ priority: 8,
group: 'penetration',
},
{
@@ -514,7 +514,7 @@ const tags = [
{
name: 'free use',
slug: 'free-use',
- description: 'The fantasy of giving universal consent, and making yourself available for any sexual activity, anywhere, anytime. As long as you are accommodating, you may continue what you were doing and pay no attention to how you\'re being used, and generally no one around you thinks considers it out of the ordinary.',
+ description: 'The fantasy of giving universal consent, and making yourself available for any sexual activity, anywhere, anytime, and possibly anyone, in a society that usually considers your role as a passive sex slave perfectly ordinary. As long as you are accommodating, you may carry on with your regular activities, and pay no attention to how you are being used.',
},
{
name: 'MFF threesome',
@@ -1088,6 +1088,10 @@ const aliases = [
name: '2on1',
for: 'threesome',
},
+ {
+ name: '3some',
+ for: 'threesome',
+ },
{
name: '2-on-1',
for: 'threesome',
@@ -1940,6 +1944,10 @@ const aliases = [
name: 'squirt',
for: 'squirting',
},
+ {
+ name: 'tap',
+ for: 'tap',
+ },
{
name: 'tattoo',
for: 'tattoos',
@@ -1979,6 +1987,18 @@ const aliases = [
for: 'transsexual',
secondary: true,
},
+ {
+ name: 'triple anal (tap)',
+ for: 'tap',
+ },
+ {
+ name: 'shemale',
+ for: 'transsexual',
+ },
+ {
+ name: 'tranny',
+ for: 'transsexual',
+ },
{
name: 'ts',
for: 'transsexual',
@@ -2021,13 +2041,13 @@ const aliases = [
},
];
-exports.seed = knex => Promise.resolve()
+exports.seed = (knex) => Promise.resolve()
.then(async () => upsert('tags_groups', groups, 'slug', knex))
.then(async () => {
const groupEntries = await knex('tags_groups').select('*');
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const tagsWithGroups = tags.map(tag => ({
+ const tagsWithGroups = tags.map((tag) => ({
name: tag.name,
slug: tag.slug || slugify(tag.name),
description: tag.description,
@@ -2042,7 +2062,7 @@ exports.seed = knex => Promise.resolve()
const tagEntries = await knex('tags').select('*').where({ alias_for: null });
const tagsMap = tagEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const tagAliases = aliases.map(alias => ({
+ const tagAliases = aliases.map((alias) => ({
name: alias.name,
alias_for: tagsMap[alias.for],
secondary: !!alias.secondary,
diff --git a/seeds/01_networks.js b/seeds/01_networks.js
index 450b3631..d0b1e4a5 100644
--- a/seeds/01_networks.js
+++ b/seeds/01_networks.js
@@ -52,6 +52,12 @@ const parentNetworks = [
},
parent: 'gamma',
},
+ {
+ slug: 'radical',
+ alias: ['kb productions'],
+ name: 'Radical Entertainment',
+ url: 'https://radicalcash.com',
+ },
];
const networks = [
@@ -219,6 +225,7 @@ const networks = [
description: 'DigitalPlayground.com is the leader in high quality adult blockbuster movies and award winning sex parodies that feature the most exclusive pornstars online! Adult Film Database of adult movies.',
parameters: {
actorPath: 'modelprofile',
+ forceDeep: true, // Digital Playground has movie and series information not available in the latest updates API
},
parent: 'mindgeek',
},
@@ -405,6 +412,9 @@ const networks = [
url: 'https://www.milehighmedia.com',
description: 'MileHighMedia.com is the only niche porn network you need! Watch lesbian sex, hardcore fucking and family porn stories with the hottest teens & MILFs!',
parent: 'mindgeek',
+ parameters: {
+ forceDeep: true, // Mile High Media has movie and series information not available in the latest updates API
+ },
},
{
slug: 'mofos',
@@ -506,6 +516,10 @@ const networks = [
url: 'https://www.realitykings.com',
description: 'Home of HD reality porn featuring the nicest tits and ass online! The hottest curvy girls in real amateur sex stories are only on REALITYkings.com',
parent: 'mindgeek',
+ parameters: {
+ childSession: true,
+ parentSession: false,
+ },
},
{
slug: 'score',
@@ -550,6 +564,7 @@ const networks = [
slug: 'topwebmodels',
name: 'Top Web Models',
url: 'https://tour.topwebmodels.com',
+ parent: 'radical',
parameters: {
apiKey: '5b637cd8c4bc59cd13686f1c38dcb780',
},
@@ -624,12 +639,12 @@ const networks = [
},
];
-exports.seed = knex => Promise.resolve()
+exports.seed = (knex) => Promise.resolve()
.then(async () => {
- const grandParentNetworkEntries = await upsert('entities', grandParentNetworks.map(network => ({ ...network, type: 'network' })), ['slug', 'type'], knex);
+ const grandParentNetworkEntries = await upsert('entities', grandParentNetworks.map((network) => ({ ...network, type: 'network' })), ['slug', 'type'], knex);
const grandParentNetworksBySlug = [].concat(grandParentNetworkEntries.inserted, grandParentNetworkEntries.updated).reduce((acc, network) => ({ ...acc, [network.slug]: network.id }), {});
- const parentNetworksWithGrandParent = parentNetworks.map(network => ({
+ const parentNetworksWithGrandParent = parentNetworks.map((network) => ({
slug: network.slug,
name: network.name,
type: network.type || 'network',
@@ -643,7 +658,7 @@ exports.seed = knex => Promise.resolve()
const parentNetworkEntries = await upsert('entities', parentNetworksWithGrandParent, ['slug', 'type'], knex);
const parentNetworksBySlug = [].concat(parentNetworkEntries.inserted, parentNetworkEntries.updated).reduce((acc, network) => ({ ...acc, [network.slug]: network.id }), {});
- const networksWithParent = networks.map(network => ({
+ const networksWithParent = networks.map((network) => ({
slug: network.slug,
name: network.name,
type: network.type || 'network',
@@ -665,14 +680,14 @@ exports.seed = knex => Promise.resolve()
networkEntries.updated,
).reduce((acc, network) => ({ ...acc, [network.slug]: network.id }), {});
- const tagSlugs = networks.map(network => network.tags).flat().filter(Boolean);
+ const tagSlugs = networks.map((network) => network.tags).flat().filter(Boolean);
const tagEntries = await knex('tags').whereIn('slug', tagSlugs);
const tagIdsBySlug = tagEntries.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.id }), {});
const tagAssociations = networks
- .map(network => (network.tags
- ? network.tags.map(tagSlug => ({
+ .map((network) => (network.tags
+ ? network.tags.map((tagSlug) => ({
entity_id: networkIdsBySlug[network.slug],
tag_id: tagIdsBySlug[tagSlug],
inherit: true,
diff --git a/seeds/02_sites.js b/seeds/02_sites.js
index 128cc17e..2d50e9b1 100644
--- a/seeds/02_sites.js
+++ b/seeds/02_sites.js
@@ -810,6 +810,13 @@ const sites = [
parameters: { siteId: 3261 },
parent: 'bang',
},
+ {
+ name: 'Bang! Podcast',
+ slug: 'bangpodcast',
+ url: 'https://www.bang.com/videos?in=bang!%20podcast',
+ parameters: { siteId: 6305 },
+ parent: 'bang',
+ },
// BANGBROS
{
name: 'Ass Parade',
@@ -1147,15 +1154,6 @@ const sites = [
code: 'ff',
},
},
- {
- name: 'Filthy Family',
- slug: 'filthyfamily',
- url: 'https://filthyfamily.com',
- parent: 'bangbros',
- parameters: {
- legacy: true,
- },
- },
{
name: 'Fuck Team Five',
slug: 'fuckteamfive',
@@ -1456,6 +1454,237 @@ const sites = [
code: 'ma',
},
},
+ // BANG BROS MEMBER SITES
+ {
+ name: 'Filthy Family',
+ slug: 'filthyfamily',
+ url: 'https://filthyfamily.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ product: 631,
+ },
+ },
+ {
+ name: 'MyGF',
+ slug: 'mygf',
+ url: 'https://mygf.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ product: 290,
+ },
+ },
+ {
+ name: 'Abuse Me',
+ slug: 'abuseme',
+ url: 'https://abuseme.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ product: 553,
+ },
+ },
+ {
+ name: 'Arabs Exposed',
+ slug: 'arabsexposed',
+ url: 'https://arabsexposed.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Black Loads',
+ slug: 'blackloads',
+ url: 'https://blackloads.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Black Patrol',
+ slug: 'blackpatrol',
+ url: 'https://blackpatrol.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Blacks On Moms',
+ slug: 'blacksonmoms',
+ url: 'https://blacksonmoms.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Blue Pill Men',
+ slug: 'bluepillmen',
+ url: 'https://bluepillmen.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Brandi Belle',
+ slug: 'brandibelle',
+ url: 'https://brandibelle.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Busty Adventures',
+ slug: 'bustyadventures',
+ url: 'https://bustyadventures.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'CFNM Show',
+ slug: 'cfnmshow',
+ url: 'https://cfnmshow.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'College Rules',
+ slug: 'collegerules',
+ url: 'https://collegerules.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ product: 523,
+ },
+ },
+ {
+ name: 'Culioneros',
+ slug: 'culioneros',
+ url: 'https://culioneros.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Dancing Bear',
+ slug: 'dancingbear',
+ url: 'https://dancingbear.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Don\'t Fuck My Daughter',
+ slug: 'dontfuckmydaughter',
+ url: 'https://dontfuckmydaughter.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Fucky Sucky',
+ slug: 'fuckysucky',
+ url: 'https://fuckysucky.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Girls Gone Wild',
+ slug: 'girlsgonewild',
+ url: 'https://girlsgonewild.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Haze Her',
+ slug: 'hazeher',
+ url: 'https://hazeher.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ product: 485,
+ },
+ },
+ {
+ name: 'Mia Khalifa',
+ slug: 'miakhalifa',
+ url: 'https://miakhalifa.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'My Dirty Vault',
+ slug: 'mydirtyvault',
+ url: null,
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Public Invasion',
+ slug: 'publicinvasion',
+ url: 'https://publicinvasion.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Sex Busters',
+ slug: 'sexbusters',
+ url: 'https://sexbusters.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Taylor Bow',
+ slug: 'taylorbow',
+ url: 'https://taylorbow.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'Virtual Porn',
+ slug: 'virtualporn',
+ url: 'https://virtualporn.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
+ {
+ name: 'XXXPawn',
+ slug: 'xxxpawn',
+ url: 'https://xxxpawn.com',
+ parent: 'bangbros',
+ parameters: {
+ layout: 'members',
+ },
+ },
// BLOWPASS
{
slug: '1000facials',
@@ -2424,7 +2653,7 @@ const sites = [
{
slug: 'digitalplayground',
name: 'Digital Playground',
- url: 'https://www.digitalplayground.com/scenes',
+ url: 'https://www.digitalplayground.com',
description: '',
parameters: { extract: true },
parent: 'digitalplayground',
@@ -2458,6 +2687,13 @@ const sites = [
description: '',
parent: 'digitalplayground',
},
+ {
+ slug: 'dpstarsexchallenges',
+ name: 'DP Star Sex Challenges',
+ url: 'https://www.digitalplayground.com/scenes?site=210',
+ parent: 'digitalplayground',
+ hasLogo: false,
+ },
{
slug: 'blockbuster',
name: 'Blockbuster',
@@ -2657,7 +2893,7 @@ const sites = [
url: 'https://www.evilangel.com',
parent: 'evilangel',
parameters: {
- filterExclusive: true,
+ filterExclusive: false,
},
},
{
@@ -4480,7 +4716,7 @@ const sites = [
slug: 'boundgangbangs',
name: 'Bound Gangbangs',
alias: ['bgb', 'bgbs'],
- url: 'https://www.kink.com/channel/boundgangbangs',
+ url: 'https://www.kink.com/channel/bound-gangbangs',
description: 'Powerless whores tied in bondage and stuffed with a cock in every hole. At BoundGangbangs women get surprise extreme gangbangs, blindfolds, deepthroat blowjobs, sex punishment, bondage, double penetration and interracial sex.',
parent: 'kink',
},
@@ -4585,7 +4821,7 @@ const sites = [
{
slug: 'hardcoregangbang',
name: 'Hardcore Gangbang',
- url: 'https://www.kink.com/channel/hardcoregangbang',
+ url: 'https://www.kink.com/channel/hardcore-gangbang',
description: "Where all women's hardcore gangbang fantasies come true. Watch extreme, brutal gangbangs with pornstars, models, & MILFs that crave cock in every hole. HardcoreGangbang.com has the best creampie gang bangs online.",
parent: 'kink',
},
@@ -4751,11 +4987,10 @@ const sites = [
},
// LEGALPORNO
{
- slug: 'legalporno',
- name: 'LegalPorno',
- alias: ['clip', 'analvids', 'gonzo'],
- url: 'https://www.legalporno.com',
- description: 'The Best HD Porn For You!',
+ slug: 'analvids',
+ name: 'AnalVids',
+ alias: ['legalporno', 'clip', 'gonzo'],
+ url: 'https://www.analvids.com',
independent: true,
parent: 'wgcz',
},
@@ -5043,7 +5278,7 @@ const sites = [
{
slug: 'bigdicksatschool',
name: 'Big Dicks At School',
- url: 'https://www.bigdicksatschool.com',
+ url: 'https://www.men.com/scenes?site=252',
description: '',
parameters: { siteId: 252 },
tags: ['gay'],
@@ -5052,7 +5287,7 @@ const sites = [
{
slug: 'drillmyhole',
name: 'Drill My Hole',
- url: 'https://www.drillmyhole.com',
+ url: 'https://www.men.com/scenes?site=253',
description: '',
parameters: { siteId: 253 },
tags: ['gay'],
@@ -5061,7 +5296,7 @@ const sites = [
{
slug: 'str8togay',
name: 'Str8 to Gay',
- url: 'https://www.str8togay.com',
+ url: 'https://www.men.com/scenes?site=254',
tags: ['gay'],
parameters: { siteId: 254 },
parent: 'men',
@@ -5069,7 +5304,7 @@ const sites = [
{
slug: 'thegayoffice',
name: 'The Gay Office',
- url: 'https://www.thegayoffice.com',
+ url: 'https://www.men.com/scenes?site=255',
tags: ['gay'],
parameters: { siteId: 255 },
parent: 'men',
@@ -5077,7 +5312,7 @@ const sites = [
{
slug: 'jizzorgy',
name: 'Jizz Orgy',
- url: 'https://www.jizzorgy.com',
+ url: 'https://www.men.com/scenes?site=256',
tags: ['gay'],
parameters: { siteId: 256 },
parent: 'men',
@@ -5085,7 +5320,7 @@ const sites = [
{
slug: 'menofuk',
name: 'Men of UK',
- url: 'https://www.menofuk.com',
+ url: 'https://www.men.com/scenes?site=258',
tags: ['gay'],
parameters: { siteId: 258 },
parent: 'men',
@@ -5093,7 +5328,7 @@ const sites = [
{
slug: 'toptobottom',
name: 'Top to Bottom',
- url: 'https://www.toptobottom.com',
+ url: 'https://www.men.com/scenes?site=259',
tags: ['gay'],
parameters: { siteId: 259 },
parent: 'men',
@@ -5101,7 +5336,7 @@ const sites = [
{
slug: 'godsofmen',
name: 'Gods of Men',
- url: 'https://www.godsofmen.com',
+ url: 'https://www.men.com/scenes?site=260',
tags: ['gay'],
parameters: { siteId: 260 },
parent: 'men',
@@ -5192,7 +5427,10 @@ const sites = [
name: 'Doghouse Digital',
url: 'https://www.doghousedigital.com',
alias: ['dhd'],
- parameters: { siteId: 321 },
+ parameters: {
+ siteId: 321,
+ native: true,
+ },
parent: 'milehighmedia',
},
{
@@ -5208,7 +5446,10 @@ const sites = [
name: 'Reality Junkies',
url: 'https://www.realityjunkies.com',
alias: ['rj'],
- parameters: { siteId: 324 },
+ parameters: {
+ siteId: 324,
+ native: true,
+ },
parent: 'milehighmedia',
},
{
@@ -5216,7 +5457,10 @@ const sites = [
name: 'Sweetheart Video',
url: 'https://www.sweetheartvideo.com',
alias: ['shv'],
- parameters: { siteId: 325 },
+ parameters: {
+ siteId: 325,
+ native: true,
+ },
parent: 'milehighmedia',
},
{
@@ -5224,7 +5468,10 @@ const sites = [
name: 'Sweet Sinner',
url: 'https://www.sweetsinner.com',
alias: ['ss'],
- parameters: { siteId: 326 },
+ parameters: {
+ siteId: 326,
+ native: true,
+ },
parent: 'milehighmedia',
},
{
@@ -5233,7 +5480,10 @@ const sites = [
alias: ['fs'],
tags: ['family'],
url: 'https://www.familysinners.com',
- parameters: { siteId: 317 },
+ parameters: {
+ siteId: 317,
+ native: true,
+ },
parent: 'milehighmedia',
},
{
@@ -5242,7 +5492,10 @@ const sites = [
url: 'https://www.iconmale.com',
alias: ['im'],
tags: ['gay'],
- parameters: { native: true, siteId: 328 },
+ parameters: {
+ siteId: 328,
+ native: true,
+ },
parent: 'milehighmedia',
},
// MOFOS
@@ -5353,6 +5606,83 @@ const sites = [
description: "Fresh, young amateur girls with beautiful tight bodies, pushing themselves to the limit! It's just another great way that today's hottest new models are choosing to showcase their stunning bodies and show all of us that they're ready for more! Soaking wet masturbation, fisting, squirting, double penetration and anal toys are just some of the things they do to show us how freaky they can be and how ready they are to graduate from toys to thick, fat cock!",
parent: 'mofos',
},
+ {
+ slug: 'pornstarvote',
+ name: 'Pornstar Vote',
+ alias: ['psv'],
+ url: 'https://www.mofos.com/scenes?site=200',
+ parent: 'mofos',
+ },
+ {
+ slug: 'thesexscout',
+ name: 'The Sex Scout',
+ alias: ['tss'],
+ url: 'https://www.mofos.com/scenes?site=199',
+ parent: 'mofos',
+ },
+ {
+ slug: 'projectrv',
+ name: 'Project RV',
+ alias: ['prv'],
+ url: 'https://www.mofos.com/scenes?site=197',
+ parent: 'mofos',
+ },
+ {
+ slug: 'bustedbabysitters',
+ name: 'Busted Babysitters',
+ alias: ['bbs'],
+ url: 'https://www.mofos.com/scenes?site=195',
+ parent: 'mofos',
+ },
+ {
+ slug: 'dronehunter',
+ name: 'Drone Hunter',
+ alias: ['dh'],
+ url: 'https://www.mofos.com/scenes?site=194',
+ parent: 'mofos',
+ },
+ {
+ slug: 'mofosworldwide',
+ name: 'MOFOS Worldwide',
+ alias: ['mfw'],
+ url: 'https://www.mofos.com/scenes?site=179',
+ parent: 'mofos',
+ },
+ {
+ slug: 'milfslikeitblack',
+ name: 'MILFs Like It Black',
+ alias: ['mlb'],
+ url: 'https://www.mofos.com/scenes?site=186',
+ parent: 'mofos',
+ },
+ {
+ slug: 'mofosoldschool',
+ name: 'MOFOS Old School',
+ alias: ['mos'],
+ url: 'https://www.mofos.com/scenes?site=180',
+ parent: 'mofos',
+ },
+ {
+ slug: 'teensatwork',
+ name: 'Teens At Work',
+ alias: ['taw'],
+ url: 'https://www.mofos.com/scenes?site=176',
+ parent: 'mofos',
+ },
+ {
+ slug: 'ingangwebang',
+ name: 'In Gang We Bang',
+ alias: ['igwb'],
+ url: 'https://www.mofos.com/scenes?site=181',
+ parent: 'mofos',
+ },
+ {
+ slug: 'canshetakeit',
+ name: 'Can She Take It',
+ alias: ['csti'],
+ url: 'https://www.mofos.com/scenes?site=177',
+ parent: 'mofos',
+ },
// MYLF
{
slug: 'fullofjoi',
@@ -6932,6 +7262,12 @@ const sites = [
},
},
// PORN PROS
+ {
+ name: 'Porn Pros',
+ slug: 'pornpros',
+ url: 'https://pornpros.com/site/pornpros',
+ parent: 'pornpros',
+ },
{
name: 'Real Ex Girlfriends',
slug: 'realexgirlfriends',
@@ -7056,6 +7392,7 @@ const sites = [
parent: 'pornpros',
parameters: {
parent: true,
+ latest: 'https://pornpros.com/site/flexiblepositions',
},
},
{
@@ -7065,6 +7402,7 @@ const sites = [
parent: 'pornpros',
parameters: {
parent: true,
+ latest: 'https://pornpros.com/site/publicviolations',
},
},
{
@@ -7078,6 +7416,10 @@ const sites = [
slug: 'squirtdisgrace',
url: 'https://squirtdisgrace.com',
parent: 'pornpros',
+ parameters: {
+ parent: true,
+ latest: 'https://pornpros.com/site/squirtdisgrace',
+ },
},
{
name: 'Cum Disgrace',
@@ -7235,6 +7577,44 @@ const sites = [
parent: 'puretaboo',
},
*/
+ // RADICAL ENTERTAINMENT
+ {
+ name: 'PurgatoryX',
+ slug: 'purgatoryx',
+ url: 'https://tour.purgatoryx.com',
+ independent: true,
+ parent: 'radical',
+ },
+ {
+ name: 'Got Filled',
+ slug: 'gotfilled',
+ url: 'https://gotfilled.com',
+ independent: true,
+ parent: 'radical',
+ parameters: {
+ layout: 'metadata',
+ },
+ },
+ {
+ name: 'Inserted',
+ slug: 'inserted',
+ url: 'https://inserted.com',
+ independent: true,
+ parent: 'radical',
+ parameters: {
+ layout: 'metadata',
+ },
+ },
+ {
+ name: 'BJ Raw',
+ slug: 'bjraw',
+ url: 'https://bjraw.com',
+ independent: true,
+ parent: 'radical',
+ parameters: {
+ layout: 'metadata',
+ },
+ },
// REALITY KINGS
{
name: 'Look At Her Now',
@@ -7436,7 +7816,7 @@ const sites = [
description: "There's no denying it, at Reality Kings we love all kinds of pussy! Ask us what we really love however, and you'll get one answer: hot wet pussy! Team Squirt invites you to strap on your snorkel and fins, because we're going diving in some of the wettest pussy around! This is NOT pee ladies and gentlemen, this is real female ejaculation. Watch these beautiful ladies experience pleasure beyond belief as they try to control their squirting pussy on camera. Masturbation, fucking, whatever it takes, these babes will do anything for a squirting orgasm! Team Squirt has tons of high quality videos of girls squirting available for you to download right now. Be prepared, this is some serious female squirting content! From the girl, to the camera... everything is drenched when these super soakers take aim. These babes all pack a loaded, squirting pussy, and they know exactly how to use it! Grab your eye protection and join the team... Team Squirt.",
parameters: null,
slug: 'teamsquirt',
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -7445,7 +7825,7 @@ const sites = [
description: "We all love them, from the sexy mom at the grocery store, to the mature hottie down the block... we're talking about the MILF Next Door! There is nothing that these hot MILFs need more than a good pounding. If you don't know what a MILF is, allow us to explain... a Mother I'd Like to Fuck, a MILF! Watch as these sex starved sluts and their girlfriends search for a lucky dude to satisfy their craving for cock. MILF Next Door offers lesbian threesomes, amazing foursomes, and more mature sex movies featuring the hottest mature women! Start downloading some of this incredible content right now from our free pics and videos below. Every episode features another stunningly hot MILF finally getting the attention she deserves. If you love everyday mom's and can't wait to see these ladies get off, join Reality Kings and the MILF Next Door.",
parameters: null,
slug: 'milfnextdoor',
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -7535,7 +7915,7 @@ const sites = [
description: 'Wives in Pantyhose features all kinds of real wives in sexy lingerie fingering their pussies with sex toys while they squeeze their big mature tits and moan. This Reality Kings network site has collected tons of pantyhose pics of hot wives and presented them to you for your viewing pleasure. No matter whether you prefer Latinas, MILFs, redheads, blondes or ebony babes, Wives in Pantyhose has all the sexiest nylon wives masturbating. There are even pantyhose lesbians playing with each other using dildos while they orgasm in smoking hot pantyhose videos. Wives in Pantyhose is easily one the best collection of real wives engaging in pantyhose porn ever put together on the net. So if you have a housewife pantyhose fetish, the the website Wives in Pantyhose is sure to deliver for you all the best models and porn the Reality Kings network has to offer.',
parameters: null,
slug: 'wivesinpantyhose',
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -7560,7 +7940,7 @@ const sites = [
description: 'There are big natural breasts, then there are Extreme Naturals. On this site, we say, "Go big or go home!" That\'s why we only deliver massive naturals straight from the best Reality Kings has to offer. Extreme Naturals has painstakingly combed the RK network for the best giant naturals models and the hottest big naturals videos with the most hardcore XXX. These sexy babes have giant naturals that bounce while they ride cock and while they get stroked from behind doggy style in their perfect porn asses. For true fans of huge natural breasts, be sure to watch tons of free big naturals videos exclusively available as Extreme Naturals trailers on the website. Whether you like your giant naturals to be on Latinas, MILFs, college babes, blondes, teens or ebony babes, Extreme Naturals has the best collection of massive naturals straight from the vaults of Reality Kings.',
parameters: null,
slug: 'extremenaturals',
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -7602,7 +7982,7 @@ const sites = [
description: 'Have you been spying on that hot couple next door$26 See My Wife invites you to view the private porn collection of horny amateurs everywhere! We\'re talking about 100% user submitted movies and pictures. Real women appearing in the hottest wife sex scenes around, that is what See My Wife is about. Our users have a chance to make 0 for pics and 00 for videos when they submit their homemade content. If you\'ve ever said "I wish I could bang my wife on film and get paid for it," look no further! Reality Kings considers every submission when we post new episodes. Check out some of our free pics and trailers below, this is one amazing collection of girlfriend and wife sex scenes. Every week we post a new episode crammed with four incredible babes showing off in front of the camera. No need to spy on the couple next door when you come See My Wife!',
parameters: null,
slug: 'seemywife',
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -7611,7 +7991,7 @@ const sites = [
url: 'https://www.realitykings.com/scenes?site=18',
description: 'Nothing is hotter than voluptuous minxes who love getting naked. Girlsofnaked.com is home to a bevy of bodacious beauties who are all about showing as much skin to whomever is willing to satisfy their sexual desires. Our 18+ pornstars are daring and always curious for new carnal adventures in HD porn videos. Reality Kings has compiled an incredible assortment of erotica with big boob naughty nymphos. Watch them squeeze their perky nipples before rubbing their ticklish clits in steamy scenes. Our deviant divas need their juicy pussies stuffed 24/7 by the biggest cocks in the adult biz and will stop at nothing to devour as much man meat as they can fit into every hungry orifice. Girls of Naked celebrate nudity and hardcore sex in all its glory. Fetishes, orgies, bukkake, anal creampies and much more are their favorite pastimes. RK has full-length premium porno movies bursting with our luscious babes bursting out of their clothes just for you!',
parameters: null,
- hasLogo: false,
+ hasLogo: true,
parent: 'realitykings',
},
{
@@ -8231,7 +8611,10 @@ const sites = [
name: 'Dane Jones',
alias: ['dnj'],
url: 'https://www.danejones.com/',
- parameters: { siteId: 290 },
+ parameters: {
+ siteId: 290,
+ native: true,
+ },
parent: 'sexyhub',
},
{
@@ -8239,7 +8622,10 @@ const sites = [
name: 'Lesbea',
alias: ['lsb'],
url: 'https://www.lesbea.com',
- parameters: { siteId: 291 },
+ parameters: {
+ siteId: 291,
+ native: true,
+ },
tags: ['lesbian'],
parent: 'sexyhub',
},
@@ -8307,7 +8693,68 @@ const sites = [
tags: ['solo'],
parent: 'spizoo',
},
+ {
+ slug: 'mrluckypov',
+ name: 'Mr. Lucky POV',
+ url: 'https://mrluckypov.com',
+ tags: ['pov'],
+ parent: 'spizoo',
+ },
+ {
+ slug: 'mrluckyvip',
+ name: 'Mr. Lucky VIP',
+ url: 'https://mrluckyvip.com',
+ tags: ['bts'],
+ parent: 'spizoo',
+ },
+ {
+ slug: 'mrluckyraw',
+ name: 'Mr. Lucky Raw',
+ url: 'https://mrluckyraw.com',
+ parent: 'spizoo',
+ },
+ {
+ slug: 'firstclasspov',
+ name: 'First Class POV',
+ url: 'https://firstclasspov.com',
+ tags: ['pov'],
+ parent: 'spizoo',
+ },
+ {
+ slug: 'rawattack',
+ name: 'Raw Attack',
+ url: 'https://rawattack.com',
+ parent: 'spizoo',
+ },
+ {
+ slug: 'realsensual',
+ name: 'Real Sensual',
+ url: 'https://realsensual.com',
+ parent: 'spizoo',
+ },
+ {
+ slug: 'vlogxxx',
+ name: 'VlogXXX',
+ url: 'https://vlogxxx.com',
+ parent: 'spizoo',
+ },
// TEAM SKEET
+ {
+ slug: 'analeuro',
+ name: 'Anal Euro',
+ url: 'https://www.teamskeet.com/series/anal-euro',
+ alias: ['ae'],
+ parameters: { id: 'anal-euro' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'blackstepdad',
+ name: 'Black StepDad',
+ url: 'https://www.teamskeet.com/series/black-stepdad',
+ alias: ['bsd'],
+ parameters: { id: 'black-stepdad' },
+ parent: 'teamskeet',
+ },
{
slug: 'exxxtrasmall',
name: 'Exxxtra Small',
@@ -8324,6 +8771,14 @@ const sites = [
parameters: { id: 'teenpies' },
parent: 'teamskeet',
},
+ {
+ slug: 'imadeporn',
+ name: 'I Made Porn',
+ url: 'https://www.teamskeet.com/series/i-made-porn',
+ alias: ['imp'],
+ parameters: { id: 'i-made-porn' },
+ parent: 'teamskeet',
+ },
{
slug: 'innocenthigh',
name: 'Innocent High',
@@ -8397,6 +8852,31 @@ const sites = [
parameters: { id: 'teens-do-porn' },
parent: 'teamskeet',
},
+ {
+ slug: 'sexandgrades',
+ name: 'Sex And Grades',
+ url: 'https://www.teamskeet.com/series/sex-and-grades',
+ tags: ['schoolgirl'],
+ alias: ['sag'],
+ parameters: { id: 'sex-and-grades' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'spanish18',
+ name: 'Spanish 18',
+ url: 'https://www.teamskeet.com/series/spanish18',
+ alias: ['spa18'],
+ parameters: { id: 'spanish18' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'petiteteens18',
+ name: 'Petite Teens 18',
+ alias: ['pt18'],
+ url: 'https://www.teamskeet.com/series/petiteteens18',
+ parameters: { id: 'petiteteens18' },
+ parent: 'teamskeet',
+ },
{
slug: 'povlife',
name: 'POV Life',
@@ -8552,6 +9032,14 @@ const sites = [
parameters: { id: 'stay-home-pov' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetclassics',
+ name: 'Team Skeet Classics',
+ url: 'https://www.teamskeet.com/series/classics',
+ alias: ['cls'],
+ parameters: { id: 'classics' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetlabs',
name: 'Team Skeet Labs',
@@ -8574,7 +9062,6 @@ const sites = [
alias: ['tsc'],
url: 'https://www.teamskeet.com/series/selects',
parameters: { id: 'selects' },
- hasLogo: false,
parent: 'teamskeet',
},
{
@@ -8600,6 +9087,14 @@ const sites = [
parameters: { id: 'owen-gray' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxaveryblack',
+ name: 'TS X Avery Black',
+ url: 'https://www.teamskeet.com/series/avery-black',
+ alias: ['avb'],
+ parameters: { id: 'avery-black' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxbaeb',
name: 'TS X BAEB',
@@ -8616,6 +9111,47 @@ const sites = [
parameters: { id: 'bananafever' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxbang',
+ name: 'TS X Bang',
+ url: 'https://www.teamskeet.com/series/bang',
+ alias: ['txb'],
+ parameters: { id: 'bang' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetxbjraw',
+ name: 'TS X BJ Raw',
+ url: 'https://www.teamskeet.com/series/bjraw',
+ alias: ['bjr'],
+ parameters: { id: 'bjraw' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetxbrandibraids',
+ name: 'TS X Brandi Braids',
+ url: 'https://www.teamskeet.com/series/brandi-braids',
+ alias: ['brb'],
+ parameters: { id: 'brandi-braids' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetxbrattyfootgirls',
+ name: 'TS X Bratty Foot Girls',
+ url: 'https://www.teamskeet.com/series/bratty-foot-girls',
+ alias: ['bft'],
+ parameters: { id: 'bratty-foot-girls' },
+ tags: ['feet', 'femdom'],
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetxbritstudioxxx',
+ name: 'TS X Brit Studio XXX',
+ url: 'https://www.teamskeet.com/series/britstudioxxx',
+ alias: ['bsx'],
+ parameters: { id: 'britstudioxxx' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxclubsweethearts',
name: 'TS X Club Sweethearts',
@@ -8624,6 +9160,14 @@ const sites = [
parameters: { id: 'clubsweethearts' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxcumkitchen',
+ name: 'TS X CumKitchen',
+ url: 'https://www.teamskeet.com/series/cumkitchen',
+ alias: ['cmk'],
+ parameters: { id: 'cumkitchen' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxdoctaytay',
name: 'TS X Doc Tay Tay',
@@ -8632,6 +9176,14 @@ const sites = [
parameters: { id: 'doctaytay' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxerotiquetvlive',
+ name: 'TS X ErotiqueTVLive',
+ url: 'https://www.teamskeet.com/series/erotiquetvlive',
+ alias: ['etl'],
+ parameters: { id: 'erotiquetvlive' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxevaelfie',
name: 'TS X Eva Elfie',
@@ -8656,6 +9208,22 @@ const sites = [
parameters: { id: 'fucking-awesome' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxherbcollins',
+ name: 'TS X Herb Collins',
+ url: 'https://www.teamskeet.com/series/herb-collins',
+ alias: ['hrb'],
+ parameters: { id: 'herb-collins' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetximpuredesire',
+ name: 'TS X Impure Desire',
+ url: 'https://www.teamskeet.com/series/impuredesire',
+ alias: ['imd'],
+ parameters: { id: 'impuredesire' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxjamesdeen',
name: 'TS X James Deen',
@@ -8664,6 +9232,14 @@ const sites = [
parameters: { id: 'james-deen' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxjavhub',
+ name: 'TS X Javhub',
+ alias: ['jav'],
+ url: 'https://www.teamskeet.com/series/javhub',
+ parameters: { id: 'javhub' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxjoybear',
name: 'TS X Joybear',
@@ -8712,6 +9288,22 @@ const sites = [
parameters: { id: 'mickey-mod' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxpovperv',
+ name: 'TS X POV Perv',
+ alias: ['pvp'],
+ url: 'https://www.teamskeet.com/series/x-pov-perv',
+ parameters: { id: 'x-pov-perv' },
+ parent: 'teamskeet',
+ },
+ {
+ slug: 'teamskeetxpurgatoryx',
+ name: 'TS X PurgatoryX',
+ alias: ['prg'],
+ url: 'https://www.teamskeet.com/series/x-purgatoryx',
+ parameters: { id: 'x-purgatoryx' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxreislin',
name: 'TS X Reislin',
@@ -8744,6 +9336,14 @@ const sites = [
parameters: { id: 'pov-god' },
parent: 'teamskeet',
},
+ {
+ slug: 'teamskeetxtoughlovex',
+ name: 'TS X ToughLoveX',
+ alias: ['tlv'],
+ url: 'https://www.teamskeet.com/series/toughlovex',
+ parameters: { id: 'toughlovex' },
+ parent: 'teamskeet',
+ },
{
slug: 'teamskeetxyoungbusty',
name: 'TS X Young Busty',
@@ -10500,6 +11100,13 @@ const sites = [
},
},
// WHALE MEMBER
+ {
+ name: 'Facials 4K',
+ slug: 'facials4k',
+ url: 'https://facials4k.com',
+ tags: ['fake-cum', 'facial', '4k'],
+ parent: 'whalemember',
+ },
{
name: 'Cum 4K',
slug: 'cum4k',
@@ -10685,28 +11292,32 @@ const sites = [
parent: 'zerotolerance',
parameters: {
scene: 'https://www.addicted2girls.com/en/video/addicted2girls',
+ movie: 'https://www.addicted2girls.com/en/dvd',
},
},
{
- slug: 'genderx',
- name: 'GenderX',
- url: 'https://www.genderx.com',
+ slug: 'genderxfilms',
+ name: 'GenderXFilms',
+ url: 'https://www.genderxfilms.com',
tags: ['transsexual'],
parent: 'zerotolerance',
parameters: {
- scene: 'https://www.genderx.com/en/video',
+ movie: 'https://www.genderxfilms.com/en/dvd',
},
},
{
- slug: 'zerotolerance',
- name: 'Zero Tolerance',
- url: 'https://www.zerotolerance.com',
+ slug: 'zerotolerancefilms',
+ name: 'Zero Tolerance Films',
+ url: 'https://www.zerotolerancefilms.com',
parent: 'zerotolerance',
+ parameters: {
+ scene: 'https://www.zerotolerancefilms.com/en/video/zerotolerancefilms',
+ },
},
];
/* eslint-disable max-len */
-exports.seed = knex => Promise.resolve()
+exports.seed = (knex) => Promise.resolve()
.then(async () => {
const networks = await knex('entities')
.where('type', 'network')
@@ -10717,7 +11328,7 @@ exports.seed = knex => Promise.resolve()
const tags = await knex('tags').select('*').whereNull('alias_for');
const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const sitesWithNetworks = sites.map(site => ({
+ const sitesWithNetworks = sites.map((site) => ({
slug: site.slug,
name: site.name,
type: site.type || 'channel',
@@ -10735,8 +11346,8 @@ exports.seed = knex => Promise.resolve()
const { inserted, updated } = await upsert('entities', sitesWithNetworks, ['slug', 'type'], knex);
const sitesMap = [].concat(inserted, updated).reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const tagAssociations = sites.map(site => (site.tags
- ? site.tags.map(tagSlug => ({
+ const tagAssociations = sites.map((site) => (site.tags
+ ? site.tags.map((tagSlug) => ({
entity_id: sitesMap[site.slug],
tag_id: tagsMap[tagSlug],
inherit: true,
diff --git a/seeds/03_studios.js b/seeds/03_studios.js
index 01fdc54c..3bda37ad 100644
--- a/seeds/03_studios.js
+++ b/seeds/03_studios.js
@@ -1,767 +1,1291 @@
const upsert = require('../src/utils/upsert');
const studios = [
- // LegalPorno
+ // ANALVIDS / LEGALPORNO
{
- slug: 'gonzocom',
- name: 'Gonzo.com',
- alias: ['sz'],
- url: 'https://www.legalporno.com/studios/gonzo_com',
- parent: 'legalporno',
- },
- {
- slug: 'giorgiograndi',
name: 'Giorgio Grandi',
- url: 'https://www.legalporno.com/studios/giorgio-grandi',
- alias: ['gio'],
- parent: 'legalporno',
+ slug: 'giorgiograndi',
+ url: 'https://www.analvids.com/studios/giorgio-grandi',
+ parent: 'analvids',
+ alias: [
+ 'gio',
+ ],
},
{
- slug: 'hardpornworld',
- name: 'Hard Porn World',
- url: 'https://www.legalporno.com/studios/hard-porn-world',
- alias: ['gp'],
- parent: 'legalporno',
- },
- {
- slug: 'interracialvision',
- name: 'Interracial Vision',
- url: 'https://www.legalporno.com/studios/interracial-vision',
- alias: ['iv'],
- parent: 'legalporno',
- },
- {
- slug: 'giorgioslab',
- name: 'Giorgio\'s Lab',
- url: 'https://www.legalporno.com/studios/giorgio--s-lab',
- alias: ['gl'],
- parent: 'legalporno',
- },
- {
- slug: 'americananal',
- name: 'American Anal',
- url: 'https://www.legalporno.com/studios/american-anal',
- alias: ['aa'],
- parent: 'legalporno',
- },
- {
- slug: 'assablanca',
- name: 'Assablanca',
- url: 'https://www.legalporno.com/studios/assablanca',
- alias: ['ab'],
- parent: 'legalporno',
- },
- {
- slug: 'focus',
- name: 'Focus',
- url: 'https://www.legalporno.com/studios/focus',
- alias: ['fs'],
- parent: 'legalporno',
- },
- {
- slug: 'anal4her',
- name: 'Anal 4 Her',
- url: 'https://www.legalporno.com/studios/anal-4-her',
- alias: ['af', 'anal forever'],
- parent: 'legalporno',
- },
- {
- slug: 'gonzoinbrazil',
- name: 'Gonzo in Brazil',
- url: 'https://www.legalporno.com/studios/gonzo-in-brazil',
- alias: ['bz'],
- parent: 'legalporno',
- },
- {
- slug: 'mranal',
- name: 'Mr Anal',
- url: 'https://www.legalporno.com/studios/mr-anal',
- alias: ['ma'],
- parent: 'legalporno',
- },
- {
- slug: 'tarrawhite',
- name: 'Tarra White',
- url: 'https://www.legalporno.com/studios/tarra-white',
- alias: ['tw'],
- parent: 'legalporno',
- },
- {
- slug: 'sineplexsos',
- name: 'Sineplex SOS',
- url: 'https://www.legalporno.com/studios/sineplex-sos',
- alias: ['rs'],
- parent: 'legalporno',
- },
- {
- slug: 'fmodels',
- name: 'F Models',
- url: 'https://www.legalporno.com/studios/f-models',
- alias: ['fm'],
- parent: 'legalporno',
- },
- {
- slug: 'sineplexcz',
- name: 'Sineplex CZ',
- url: 'https://www.legalporno.com/studios/sineplex-cz',
- alias: ['sz'],
- parent: 'legalporno',
- },
- {
- slug: 'gg',
- name: 'GG',
- url: 'https://www.legalporno.com/studios/gg',
- parent: 'legalporno',
- },
- {
- slug: 'firstgape',
- name: 'First Gape',
- url: 'https://www.legalporno.com/studios/first-gape',
- alias: ['sal'],
- parent: 'legalporno',
- },
- {
- slug: 'omargalantiproductions',
- name: 'Omar Galanti Productions',
- url: 'https://www.legalporno.com/studios/omar-galanti-productions',
- parent: 'legalporno',
- },
- {
- slug: 'marywet',
- name: 'Marywet',
- url: 'https://www.legalporno.com/studios/marywet',
- alias: ['ots'],
- parent: 'legalporno',
- },
- {
- slug: 'norestfortheass',
- name: 'No Rest For The Ass',
- url: 'https://www.legalporno.com/studios/no-rest-for-the-ass',
- alias: ['nr'],
- parent: 'legalporno',
- },
- {
- slug: 'hairygonzo',
- name: 'Hairy Gonzo',
- url: 'https://www.legalporno.com/studios/hairy-gonzo',
- alias: ['hg'],
- parent: 'legalporno',
- },
- {
- slug: 'sineplexclassic',
- name: 'Sineplex Classic',
- url: 'https://www.legalporno.com/studios/sineplex-classic',
- parent: 'legalporno',
- },
- {
- slug: 'sinemale',
- name: 'Sinemale',
- url: 'https://www.legalporno.com/studios/sinemale',
- parent: 'legalporno',
- },
- {
- slug: 'outsidethestudio',
- name: 'Outside The Studio',
- url: 'https://www.legalporno.com/studios/outside-the-studio',
- alias: ['ots'],
- parent: 'legalporno',
- },
- {
- slug: 'kinkysex',
- name: 'Kinky Sex',
- url: 'https://www.legalporno.com/studios/kinky-sex',
- alias: ['ks'],
- parent: 'legalporno',
- },
- {
- slug: 'sexyangelproductions',
- name: 'Sexy Angel Productions',
- url: 'https://www.legalporno.com/studios/sexy-angel-productions',
- alias: ['sa'],
- parent: 'legalporno',
- },
- {
- slug: 'nfstudio',
- name: 'N&F Studio',
- url: 'https://www.legalporno.com/studios/nf-studio',
- alias: ['nf'],
- parent: 'legalporno',
- },
- {
- slug: 'natashateenproductions',
- name: 'Natasha Teen Productions',
- url: 'https://www.legalporno.com/studios/natasha-teen-productions',
- alias: ['nt'],
- parent: 'legalporno',
- },
- {
- slug: 'mixedstudios',
- name: 'Mixed Studios',
- url: 'https://www.legalporno.com/studios/mixed-studios',
- alias: ['ms'],
- parent: 'legalporno',
- },
- {
- slug: 'claudiasclips',
- name: 'Claudia\'s Clips',
- url: 'https://www.legalporno.com/studios/claudia--s-clips',
- alias: ['cm'],
- parent: 'legalporno',
- },
- {
- slug: 'rebeccasclips',
- name: 'Rebecca\'s Clips',
- url: 'https://www.legalporno.com/studios/rebecca--s-clips',
- alias: ['rv'],
- parent: 'legalporno',
- },
- {
- slug: 'private',
- name: 'Private',
- url: 'https://www.legalporno.com/studios/private',
- parent: 'legalporno',
- },
- {
- slug: 'privatecastings',
- name: 'Private Castings',
- url: 'https://www.legalporno.com/studios/private-castings',
- parent: 'legalporno',
- },
- {
- slug: 'privateblack',
- name: 'Private Black',
- url: 'https://www.legalporno.com/studios/private-black',
- parent: 'legalporno',
+ name: 'Gonzo.com',
+ slug: 'gonzocom',
+ url: 'https://www.analvids.com/studios/gonzo_com',
+ parent: 'analvids',
+ alias: [
+ 'sz',
+ ],
},
{
+ name: 'NRX-Studio',
slug: 'nrxstudio',
- name: 'NRX Studio',
- url: 'https://www.legalporno.com/studios/nrx-studio',
- alias: ['nrx'],
- parent: 'legalporno',
+ url: 'https://www.analvids.com/studios/nrx-studio',
+ parent: 'analvids',
+ alias: [
+ 'nrx',
+ ],
},
{
- slug: 'lpggg',
- name: 'GGG by John Thompson',
- url: 'https://www.legalporno.com/studios/ggg-by-john-thompson',
- parent: 'legalporno',
+ name: 'Porn World',
+ slug: 'pornworld',
+ url: 'https://www.analvids.com/studios/porn-world',
+ parent: 'analvids',
},
{
- slug: 'yummyestudio',
- name: 'Yummy Estudio',
- url: 'https://www.legalporno.com/studios/yummy-estudio',
- alias: ['ye'],
- parent: 'legalporno',
- },
- {
- slug: 'bustedtgirls',
- name: 'Busted T-Girls',
- url: 'https://www.legalporno.com/studios/busted-t-girls',
- alias: ['btg'],
- tags: ['transsexual'],
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbangbros',
- name: 'Bang Bros',
- url: 'https://www.legalporno.com/studios/bang-bros',
- parent: 'legalporno',
- },
- {
- slug: 'vkstudio',
- name: 'VK Studio',
- url: 'https://www.legalporno.com/studios/vk-studio',
- alias: ['vk'],
- parent: 'legalporno',
- },
- {
- slug: 'analmaniacs',
- name: 'Anal Maniacs by Lady Dee',
- url: 'https://www.legalporno.com/studios/anal-maniacs-by-lady-dee',
- alias: ['ld'],
- parent: 'legalporno',
- },
- {
- slug: 'pineapplestestkitchen',
- name: 'Pineapple\'s Test Kitchen',
- url: 'https://www.legalporno.com/studios/pineapple--s-test-kitchen',
- alias: ['ax'],
- parent: 'legalporno',
- },
- {
- slug: 'adelinelafouinestudio',
- name: 'Adeline Lafouine Studio',
- url: 'https://www.legalporno.com/studios/adeline-lafouine-studio',
- alias: ['al'],
- parent: 'legalporno',
- },
- {
- slug: 'jeanmariecordastudio',
- name: 'Jean Marie Corda Studio',
- url: 'https://www.legalporno.com/studios/jean-marie-corda-studio',
- alias: ['jmc'],
- parent: 'legalporno',
- },
- {
- slug: 'brianabanderasstudio',
- name: 'Briana Banderas Studio',
- url: 'https://www.legalporno.com/studios/briana-banderas-studio',
- alias: ['brb'],
- parent: 'legalporno',
- },
- {
- slug: 'rickangelstudio',
- name: 'Rick Angel Studio',
- url: 'https://www.legalporno.com/studios/rick-angel-studio',
- alias: ['ra'],
- parent: 'legalporno',
- },
- {
- slug: 'mamasitasavage',
- name: 'Mamasita Savage',
- url: 'https://www.legalporno.com/studios/mamasita-savage',
- alias: ['msv'],
- parent: 'legalporno',
- },
- {
- slug: 'badbardotclub',
- name: 'Bad Bardot Club',
- url: 'https://www.legalporno.com/studios/bad-bardot-club',
- alias: ['bbc'],
- parent: 'legalporno',
- },
- {
- slug: 'katerichstudio',
- name: 'Kate Rich Studio',
- url: 'https://www.legalporno.com/studios/kate-rich-studio',
- alias: ['krs'],
- parent: 'legalporno',
- },
- {
- slug: 'daddyenjoy',
- name: 'DaddyEnjoy',
- url: 'https://www.legalporno.com/studios/daddyenjoy',
- alias: ['de'],
- parent: 'legalporno',
- },
- {
- slug: 'laradesantisstudio',
- name: 'Lara De Santis Studio',
- url: 'https://www.legalporno.com/studios/lara-de-santis-studio',
- alias: ['lds'],
- parent: 'legalporno',
- },
- {
- slug: 'lutrosworld',
- name: 'Lutro\'s World',
- url: 'https://www.legalporno.com/studios/lutro--s-world',
- alias: ['lw'],
- parent: 'legalporno',
- },
- {
- slug: 'timeabellaproduction',
- name: 'Timea Bella Production',
- url: 'https://www.legalporno.com/studios/timea-bella-production',
- alias: ['tb'],
- parent: 'legalporno',
- },
- {
- slug: 'angelwickysproduction',
- name: 'Angel Wicky\'s Production',
- url: 'https://www.legalporno.com/studios/angel-wicky%E2%80%99s-production',
- alias: ['aw'],
- parent: 'legalporno',
- },
- {
- slug: 'bbcmaster',
- name: 'BBC Master',
- url: 'https://www.legalporno.com/studios/bbc-master',
- alias: ['jl'],
- parent: 'legalporno',
- },
- {
- slug: 'maxrajoysquad',
- name: 'Max Rajoy Squad',
- url: 'https://www.legalporno.com/studios/max-rajoy-squad',
- alias: ['mrs'],
- parent: 'legalporno',
- },
- {
- slug: 'queeneugenia',
- name: 'Queen Eugenia',
- url: 'https://www.legalporno.com/studios/queen-eugenia-studio',
- alias: ['qe'],
- parent: 'legalporno',
- },
- {
- slug: 'helenamoellerstudio',
- name: 'Helena Moeller Studio',
- url: 'https://www.legalporno.com/studios/helena-moeller-studio',
- alias: ['hms'],
- parent: 'legalporno',
- },
- {
- slug: 'allaboutsweetbunny',
- name: 'All About Sweet Bunny',
- url: 'https://www.legalporno.com/studios/all-about-sweet-bunny',
- alias: ['swb'],
- parent: 'legalporno',
- },
- {
- slug: 'clubcandyalexa',
- name: 'Club Candy Alexa',
- url: 'https://www.legalporno.com/studios/club-candy-alexa',
- alias: ['cca'],
- parent: 'legalporno',
- },
- {
- slug: 'stacybloomstudio',
- name: 'Stacy Bloom Studio',
- url: 'https://www.legalporno.com/studios/stacy-bloom-studio',
- alias: ['sbs'],
- parent: 'legalporno',
- },
- {
- slug: 'harleenvanhyntenstudio',
- name: 'Harleen Van Hynten Studio',
- url: 'https://www.legalporno.com/studios/harleen-van-hynten-studio',
- alias: ['hvh'],
- parent: 'legalporno',
- },
- {
- slug: 'cherryaleksastudio',
- name: 'Cherry Aleksa Studio',
- url: 'https://www.legalporno.com/studios/cherry-aleksa-studio',
- alias: ['ca'],
- parent: 'legalporno',
- },
- {
- slug: 'possiblyneighbours',
- name: 'Possibly Neighbours',
- url: 'https://www.legalporno.com/studios/possibly-neighbours',
- alias: ['pn'],
- parent: 'legalporno',
- },
- {
- slug: 'honourmaysmanorhouse',
- name: 'Honour Mays\' Manor House',
- url: 'https://www.legalporno.com/studios/honour-mays---manor-house',
- alias: ['hm'],
- parent: 'legalporno',
- },
- {
- slug: 'adaralovestudio',
- name: 'Adara Love Studio',
- url: 'https://www.legalporno.com/studios/adara-love-studio',
- parent: 'legalporno',
- },
- {
- slug: 'faplex',
- name: 'Faplex',
- url: 'https://www.legalporno.com/studios/faplex',
- parent: 'legalporno',
- },
- {
- slug: 'analpornworld',
name: 'Anal Porn World',
- url: 'https://www.legalporno.com/studios/anal-porn-world',
- parent: 'legalporno',
+ slug: 'analpornworld',
+ url: 'https://www.analvids.com/studios/anal-porn-world',
+ parent: 'analvids',
+ },
+ {
+ name: "Giorgio's Lab",
+ slug: 'giorgioslab',
+ url: 'https://www.analvids.com/studios/giorgio--s-lab',
+ parent: 'analvids',
+ alias: [
+ 'gl',
+ ],
+ },
+ {
+ name: 'VK Studio',
+ slug: 'vkstudio',
+ url: 'https://www.analvids.com/studios/vk-studio',
+ parent: 'analvids',
+ alias: [
+ 'vk',
+ ],
+ },
+ {
+ name: 'Busted T-Girls',
+ slug: 'bustedtgirls',
+ url: 'https://www.analvids.com/studios/busted-t-girls',
+ parent: 'analvids',
+ alias: [
+ 'btg',
+ ],
+ tags: [
+ 'transsexual',
+ ],
+ },
+ {
+ name: 'Bang Bros',
+ slug: 'bangbros',
+ url: 'https://www.analvids.com/studios/bang-bros',
+ parent: 'analvids',
+ },
+ {
+ name: 'Yummy estudio',
+ slug: 'yummyestudio',
+ url: 'https://www.analvids.com/studios/yummy-estudio',
+ parent: 'analvids',
+ alias: [
+ 'ye',
+ ],
+ },
+ {
+ name: 'Angelo Godshack Original',
+ slug: 'angelogodshackoriginal',
+ url: 'https://www.analvids.com/studios/angelo-godshack-original',
+ parent: 'analvids',
},
{
- slug: 'xfreax',
name: 'XfreaX',
- url: 'https://www.legalporno.com/studios/xfreax',
- parent: 'legalporno',
+ slug: 'xfreax',
+ url: 'https://www.analvids.com/studios/xfreax',
+ parent: 'analvids',
},
{
- slug: 'rpsnstudio',
- name: 'RPSN Studio',
- url: 'https://www.legalporno.com/studios/rpsn-studio',
- parent: 'legalporno',
- },
- {
- slug: 'queeneugeniastudio',
- name: 'Queen Eugenia Studio',
- url: 'https://www.legalporno.com/studios/queen-eugenia-studio',
- parent: 'legalporno',
- },
- {
- slug: 'blessexxx',
- name: 'Blessexxx',
- url: 'https://www.legalporno.com/studios/blessexxx',
- parent: 'legalporno',
- },
- {
- slug: 'thekrisskissexperience',
- name: 'The Kriss Kiss Experience',
- url: 'https://www.legalporno.com/studios/the-kriss-kiss-experience',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsxxxpawn',
- name: 'XXXPawn',
- url: 'https://www.legalporno.com/studios/xxxpawn',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsgaywire',
- name: 'Gaywire',
- url: 'https://www.legalporno.com/studios/gaywire',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsfilthyfamily',
- name: 'Filthy Family',
- url: 'https://www.legalporno.com/studios/filthy-family',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbrandibelle',
- name: 'Brandi Belle',
- url: 'https://www.legalporno.com/studios/brandi-belle',
- parent: 'legalporno',
- },
- {
- slug: 'analvidspublicinvasion',
- name: 'Public Invasion',
- url: 'https://www.legalporno.com/studios/public-invasion',
- parent: 'legalporno',
- },
- {
- slug: 'kittyblairstudio',
- name: 'Kitty Blair Studio',
- url: 'https://www.legalporno.com/studios/kitty-blair-studio',
- parent: 'legalporno',
- },
- {
- slug: 'bustyadventures',
- name: 'Busty Adventures',
- url: 'https://www.legalporno.com/studios/busty-adventures',
- parent: 'legalporno',
- },
- {
- slug: 'analvidscollegerules',
- name: 'College Rules',
- url: 'https://www.legalporno.com/studios/college-rules',
- parent: 'legalporno',
- },
- {
- slug: 'rockcorp',
- name: 'Rock Corp',
- url: 'https://www.legalporno.com/studios/rock-corp',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbarebackattack',
- name: 'Bareback Attack',
- url: 'https://www.legalporno.com/studios/bareback-attack',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbarebackcasting',
- name: 'Bareback Casting',
- url: 'https://www.legalporno.com/studios/bareback-casting',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsthughunter',
- name: 'Thug Hunter',
- url: 'https://www.legalporno.com/studios/thughunter',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsrubhim',
- name: 'Rub Him',
- url: 'https://www.legalporno.com/studios/rub-him',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsgaypawn',
- name: 'Gay Pawn',
- url: 'https://www.legalporno.com/studios/gay-pawn',
- parent: 'legalporno',
- },
- {
- slug: 'analvidspoundhisass',
- name: 'PoundHisAss',
- url: 'https://www.legalporno.com/studios/poundhisass',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsfuckyoucracker',
- name: 'Fuck You Cracker',
- url: 'https://www.legalporno.com/studios/fuck-you-cracker',
- parent: 'legalporno',
- },
- {
- slug: 'analvidstroopcandy',
- name: 'Troop Candy',
- url: 'https://www.legalporno.com/studios/troop-candy',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsgrabass',
- name: 'Grab Ass',
- url: 'https://www.legalporno.com/studios/grab-ass',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbutterloads',
- name: 'Butter Loads',
- url: 'https://www.legalporno.com/studios/butter-loads',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsprojectcitybus',
- name: 'Project City Bus',
- url: 'https://www.legalporno.com/studios/project-city-bus',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsexbf',
- name: 'ExBF',
- url: 'https://www.legalporno.com/studios/exbf',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsungloryhole',
- name: 'UngloryHole',
- url: 'https://www.legalporno.com/studios/ungloryhole',
- parent: 'legalporno',
- },
- {
- slug: 'editafantasystudio',
- name: 'Edita Fantasy Studio',
- url: 'https://www.legalporno.com/studios/edita-fantasy-studio',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsgaypatrol',
- name: 'Gay Patrol',
- url: 'https://www.legalporno.com/studios/gay-patrol',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsbaitbus',
- name: 'Bait Bus',
- url: 'https://www.legalporno.com/studios/bait-bus',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsoutinpublic',
- name: 'Out In Public',
- url: 'https://www.legalporno.com/studios/out-in-public',
- parent: 'legalporno',
- },
- {
- slug: 'hornybelle',
- name: 'Horny Belle',
- url: 'https://www.legalporno.com/studios/horny-belle',
- parent: 'legalporno',
- },
- {
- slug: 'suzieqstudio',
- name: 'Suzie Q Studio',
- url: 'https://www.legalporno.com/studios/suzie-q-studio',
- parent: 'legalporno',
- },
- {
- slug: 'cherryontop',
- name: 'Cherry On Top',
- url: 'https://www.legalporno.com/studios/cherry-on-top',
- parent: 'legalporno',
- },
- {
- slug: 'analvidsimmorallive',
- name: 'ImmoralLive',
- url: 'https://www.legalporno.com/studios/immorallive',
- parent: 'legalporno',
- },
- {
- slug: 'viragoldfilms',
- name: 'Vira Gold Films',
- url: 'https://www.legalporno.com/studios/vira-gold-films',
- parent: 'legalporno',
- },
- {
- slug: 'mamboperv',
- name: 'Mambo Perv',
- url: 'https://www.legalporno.com/studios/mambo-perv',
- parent: 'legalporno',
+ name: 'N F studio',
+ slug: 'nfstudio',
+ url: 'https://www.analvids.com/studios/nf-studio',
+ parent: 'analvids',
+ alias: [
+ 'nf',
+ ],
},
{
+ name: 'Black in White',
slug: 'blackinwhite',
- name: 'Black In White',
- url: 'https://www.legalporno.com/studios/black-in-white',
- alias: ['biw'],
- parent: 'legalporno',
+ url: 'https://www.analvids.com/studios/black-in-white',
+ parent: 'analvids',
+ alias: [
+ 'biw',
+ ],
},
{
- slug: 'pissinganalfantasy',
- name: 'Pissing & Anal Fantasy',
- url: 'https://www.legalporno.com/studios/pissing-anal-fantasy',
- alias: ['paf'],
- parent: 'legalporno',
+ name: 'LATIN TEENS productions',
+ slug: 'latinteensproductions',
+ url: 'https://www.analvids.com/studios/latin-teens-productions',
+ parent: 'analvids',
},
{
- slug: 'siswetanalambassador',
- name: 'Siswet Anal Ambassador',
- url: 'https://www.legalporno.com/studios/siswet-anal-ambassador',
- parent: 'legalporno',
+ name: 'Vira Gold Films',
+ slug: 'viragoldfilms',
+ url: 'https://www.analvids.com/studios/vira-gold-films',
+ parent: 'analvids',
},
{
- slug: 'lizzylaynezentertainment',
- name: 'Lizzy Laynez Entertainment',
- url: 'https://www.legalporno.com/studios/lizzy-laynez-entertainment',
- parent: 'legalporno',
+ name: 'GGG BY JOHN THOMPSON',
+ slug: 'gggbyjohnthompson',
+ url: 'https://www.analvids.com/studios/ggg-by-john-thompson',
+ parent: 'analvids',
},
{
- slug: 'gallastudio',
- name: 'Galla Studio',
- url: 'https://www.legalporno.com/studios/galla-studio',
- parent: 'legalporno',
+ name: 'Private',
+ slug: 'private',
+ url: 'https://www.analvids.com/studios/private',
+ parent: 'analvids',
},
{
- slug: 'shinaryenspurepleasure',
- name: 'Shinaryen\'s Pure Pleasure',
- url: 'https://www.legalporno.com/studios/shinaryen--s-pure-pleasure',
- parent: 'legalporno',
+ name: 'TheWonderToys Training Studio',
+ slug: 'thewondertoystrainingstudio',
+ url: 'https://www.analvids.com/studios/thewondertoys-training-studio',
+ parent: 'analvids',
},
{
- slug: 'analvidsculioneros',
- name: 'Culioneros',
- url: 'https://www.legalporno.com/studios/culioneros',
- parent: 'legalporno',
+ name: 'Natasha Teen Productions',
+ slug: 'natashateenproductions',
+ url: 'https://www.analvids.com/studios/natasha-teen-productions',
+ parent: 'analvids',
+ alias: [
+ 'nt',
+ ],
},
{
- slug: 'hentaied',
- name: 'Hentaied',
- url: 'https://www.legalporno.com/studios/hentaied',
- parent: 'legalporno',
+ name: 'Interracial Vision',
+ slug: 'interracialvision',
+ url: 'https://www.analvids.com/studios/interracial-vision',
+ parent: 'analvids',
+ alias: [
+ 'iv',
+ ],
},
{
+ name: 'PISSING E ANAL FANTASY',
+ slug: 'pissingeanalfantasy',
+ url: 'https://www.analvids.com/studios/pissing-anal-fantasy',
+ parent: 'analvids',
+ },
+ {
+ name: 'Rock Corp',
+ slug: 'rockcorp',
+ url: 'https://www.analvids.com/studios/rock-corp',
+ parent: 'analvids',
+ },
+ {
+ name: 'Mambo Perv',
+ slug: 'mamboperv',
+ url: 'https://www.analvids.com/studios/mambo-perv',
+ parent: 'analvids',
+ },
+ {
+ name: 'Stalker Prodz',
+ slug: 'stalkerprodz',
+ url: 'https://www.analvids.com/studios/stalker_prodz',
+ parent: 'analvids',
+ },
+ {
+ name: 'American Anal',
+ slug: 'americananal',
+ url: 'https://www.analvids.com/studios/american-anal',
+ parent: 'analvids',
+ alias: [
+ 'aa',
+ ],
+ },
+ {
+ name: 'LVT studio',
slug: 'lvtstudio',
- name: 'LVT Studio',
- url: 'https://www.legalporno.com/studios/lvt-studio',
- parent: 'legalporno',
+ url: 'https://www.analvids.com/studios/lvt-studio',
+ parent: 'analvids',
},
{
- slug: 'analvidsitsgonnahurt',
- name: 'It\'s Gonna Hurt',
- url: 'https://www.legalporno.com/studios/its-gonna-hurt',
- parent: 'legalporno',
+ name: 'FAPLEX',
+ slug: 'faplex',
+ url: 'https://www.analvids.com/studios/faplex',
+ parent: 'analvids',
+ },
+ {
+ name: 'Toby Dick',
+ slug: 'tobydick',
+ url: 'https://www.analvids.com/studios/toby_dick',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sineplex SOS',
+ slug: 'sineplexsos',
+ url: 'https://www.analvids.com/studios/sineplex-sos',
+ parent: 'analvids',
+ alias: [
+ 'rs',
+ ],
+ },
+ {
+ name: "Mr Anderson's Anal Academy ",
+ slug: 'mrandersonsanalacademy',
+ url: 'https://www.analvids.com/studios/mr-anderson--s-anal-academy',
+ parent: 'analvids',
+ },
+ {
+ name: 'Only3x Network',
+ slug: 'only3xnetwork',
+ url: 'https://www.analvids.com/studios/only3x_network_store',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sineplex CZ',
+ slug: 'sineplexcz',
+ url: 'https://www.analvids.com/studios/sineplex-cz',
+ parent: 'analvids',
+ alias: [
+ 'sz',
+ ],
+ },
+ {
+ name: 'Galla Studio',
+ slug: 'gallastudio',
+ url: 'https://www.analvids.com/studios/galla-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Rick Angel Studio',
+ slug: 'rickangelstudio',
+ url: 'https://www.analvids.com/studios/rick-angel-studio',
+ parent: 'analvids',
+ alias: [
+ 'ra',
+ ],
+ },
+ {
+ name: 'Siswet Anal Ambassador',
+ slug: 'siswetanalambassador',
+ url: 'https://www.analvids.com/studios/siswet-anal-ambassador',
+ parent: 'analvids',
+ },
+ {
+ name: 'BBC Master Joss Lescaf',
+ slug: 'bbcmasterjosslescaf',
+ url: 'https://www.analvids.com/studios/bbc-master',
+ parent: 'analvids',
+ },
+ {
+ name: 'Anal Maniacs by Lady Dee',
+ slug: 'analmaniacsbyladydee',
+ url: 'https://www.analvids.com/studios/anal-maniacs-by-lady-dee',
+ parent: 'analvids',
+ },
+ {
+ name: 'Freddy Gong ',
+ slug: 'freddygong',
+ url: 'https://www.analvids.com/studios/freddy_gong_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Rebel Rhyder Productions',
+ slug: 'rebelrhyderproductions',
+ url: 'https://www.analvids.com/studios/rebel_rhyder_production',
+ parent: 'analvids',
+ },
+ {
+ name: 'RPSN Studio',
+ slug: 'rpsnstudio',
+ url: 'https://www.analvids.com/studios/rpsn-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'StacyBloomStudio',
+ slug: 'stacybloomstudio',
+ url: 'https://www.analvids.com/studios/stacy-bloom-studio',
+ parent: 'analvids',
+ alias: [
+ 'sbs',
+ ],
+ },
+ {
+ name: 'SamCanRam',
+ slug: 'samcanram',
+ url: 'https://www.analvids.com/studios/samcanram',
+ parent: 'analvids',
+ },
+ {
+ name: 'Mamasita Savage',
+ slug: 'mamasitasavage',
+ url: 'https://www.analvids.com/studios/mamasita-savage',
+ parent: 'analvids',
+ alias: [
+ 'msv',
+ ],
+ },
+ {
+ name: 'Private Black',
+ slug: 'privateblack',
+ url: 'https://www.analvids.com/studios/private-black',
+ parent: 'analvids',
+ },
+ {
+ name: 'Studio PL',
+ slug: 'studiopl',
+ url: 'https://www.analvids.com/studios/studio_pl',
+ parent: 'analvids',
+ },
+ {
+ name: 'ssnatashateen',
+ slug: 'ssnatashateen',
+ url: 'https://www.analvids.com/studios/ssnatashateen',
+ parent: 'analvids',
+ },
+ {
+ name: 'Immorallive',
+ slug: 'immorallive',
+ url: 'https://www.analvids.com/studios/immorallive',
+ parent: 'analvids',
+ },
+ {
+ name: 'Andy Casanova',
+ slug: 'andycasanova',
+ url: 'https://www.analvids.com/studios/andy-casanova',
+ parent: 'analvids',
+ },
+ {
+ name: 'Adeline Lafouine Studio',
+ slug: 'adelinelafouinestudio',
+ url: 'https://www.analvids.com/studios/adeline-lafouine-studio',
+ parent: 'analvids',
+ alias: [
+ 'al',
+ ],
+ },
+ {
+ name: 'Monika Fox',
+ slug: 'monikafox',
+ url: 'https://www.analvids.com/studios/monika_fox',
+ parent: 'analvids',
+ },
+ {
+ name: 'GG',
+ slug: 'gg',
+ url: 'https://www.analvids.com/studios/gg',
+ parent: 'analvids',
+ },
+ {
+ name: 'Kate Rich Studio',
+ slug: 'katerichstudio',
+ url: 'https://www.analvids.com/studios/kate-rich-studio',
+ parent: 'analvids',
+ alias: [
+ 'krs',
+ ],
+ },
+ {
+ name: 'Porn Force',
+ slug: 'pornforce',
+ url: 'https://www.analvids.com/studios/porn-force',
+ parent: 'analvids',
+ },
+ {
+ name: 'Lydia Black Studio',
+ slug: 'lydiablackstudio',
+ url: 'https://www.analvids.com/studios/lydia_black_',
+ parent: 'analvids',
+ },
+ {
+ name: "Claudia's Clips",
+ slug: 'claudiasclips',
+ url: 'https://www.analvids.com/studios/claudia--s-clips',
+ parent: 'analvids',
+ alias: [
+ 'cm',
+ ],
+ },
+ {
+ name: 'Sineplex Classic',
+ slug: 'sineplexclassic',
+ url: 'https://www.analvids.com/studios/sineplex-classic',
+ parent: 'analvids',
+ },
+ {
+ name: 'Erika Korti Studio',
+ slug: 'erikakortistudio',
+ url: 'https://www.analvids.com/studios/erika_korti_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'MLR production ',
+ slug: 'mlrproduction',
+ url: 'https://www.analvids.com/studios/mlr_production_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Mugur Porn',
+ slug: 'mugurporn',
+ url: 'https://www.analvids.com/studios/mugur_porn',
+ parent: 'analvids',
+ },
+ {
+ name: 'Bad Bardot Club',
+ slug: 'badbardotclub',
+ url: 'https://www.analvids.com/studios/bad-bardot-club',
+ parent: 'analvids',
+ alias: [
+ 'bbc',
+ ],
+ },
+ {
+ name: 'Mya Quinn Studio',
+ slug: 'myaquinnstudio',
+ url: 'https://www.analvids.com/studios/mya_quinn_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'dreamtranny',
+ slug: 'dreamtranny',
+ url: 'https://www.analvids.com/studios/dreamtranny',
+ parent: 'analvids',
+ },
+ {
+ name: 'Z-Filmz',
+ slug: 'zfilmz',
+ url: 'https://www.analvids.com/studios/z-films',
+ parent: 'analvids',
+ },
+ {
+ name: 'Anal 4 her',
+ slug: 'anal4her',
+ url: 'https://www.analvids.com/studios/anal-4-her',
+ parent: 'analvids',
+ alias: [
+ 'af',
+ 'anal forever',
+ ],
+ },
+ {
+ name: 'Hentaied',
+ slug: 'hentaied',
+ url: 'https://www.analvids.com/studios/hentaied',
+ parent: 'analvids',
+ },
+ {
+ name: 'Kinky Sex',
+ slug: 'kinkysex',
+ url: 'https://www.analvids.com/studios/kinky-sex',
+ parent: 'analvids',
+ alias: [
+ 'ks',
+ ],
+ },
+ {
+ name: 'Briana Banderas Studio',
+ slug: 'brianabanderasstudio',
+ url: 'https://www.analvids.com/studios/briana-banderas-studio',
+ parent: 'analvids',
+ alias: [
+ 'brb',
+ ],
+ },
+ {
+ name: 'analgonzo',
+ slug: 'analgonzo',
+ url: 'https://www.analvids.com/studios/analgonzo',
+ parent: 'analvids',
+ },
+ {
+ name: 'dankreamer',
+ slug: 'dankreamer',
+ url: 'https://www.analvids.com/studios/dankreamer',
+ parent: 'analvids',
+ },
+ {
+ name: 'cherryflowerxxx',
+ slug: 'cherryflowerxxx',
+ url: 'https://www.analvids.com/studios/cherryflowerxxx',
+ parent: 'analvids',
+ },
+ {
+ name: 'Suzie Q Studio',
+ slug: 'suzieqstudio',
+ url: 'https://www.analvids.com/studios/suzie-q-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Trunk AC Studio',
+ slug: 'trunkacstudio',
+ url: 'https://www.analvids.com/studios/oliver_trunk_ac_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'GothCharlotte',
+ slug: 'gothcharlotte',
+ url: 'https://www.analvids.com/studios/gothcharlotte',
+ parent: 'analvids',
+ },
+ {
+ name: 'SpicyLab Production',
+ slug: 'spicylabproduction',
+ url: 'https://www.analvids.com/studios/spicy_lab_productio',
+ parent: 'analvids',
+ },
+ {
+ name: 'MAX RAJOY SQUAD',
+ slug: 'maxrajoysquad',
+ url: 'https://www.analvids.com/studios/max-rajoy-squad',
+ parent: 'analvids',
+ alias: [
+ 'mrs',
+ ],
+ },
+ {
+ name: 'Amor en equipo',
+ slug: 'amorenequipo',
+ url: 'https://www.analvids.com/studios/adara-love-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Eden does',
+ slug: 'edendoes',
+ url: 'https://www.analvids.com/studios/eden-does',
+ parent: 'analvids',
+ },
+ {
+ name: 'Assablanca',
+ slug: 'assablanca',
+ url: 'https://www.analvids.com/studios/assablanca',
+ parent: 'analvids',
+ alias: [
+ 'ab',
+ ],
+ },
+ {
+ name: 'Mr Anal',
+ slug: 'mranal',
+ url: 'https://www.analvids.com/studios/mr-anal',
+ parent: 'analvids',
+ alias: [
+ 'ma',
+ ],
+ },
+ {
+ name: 'Cris Angelo',
+ slug: 'crisangelo',
+ url: 'https://www.analvids.com/studios/cris_angelo_-_bigdaddyproductions',
+ parent: 'analvids',
+ },
+ {
+ name: 'Vince Karter ',
+ slug: 'vincekarter',
+ url: 'https://www.analvids.com/studios/vince_karter_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Pineapples Studio',
+ slug: 'pineapplesstudio',
+ url: 'https://www.analvids.com/studios/pineapple--s-test-kitchen',
+ parent: 'analvids',
+ },
+ {
+ name: 'Bazinga',
+ slug: 'bazinga',
+ url: 'https://www.analvids.com/studios/bazinga',
+ parent: 'analvids',
+ },
+ {
+ name: 'Harleen Van Hynten',
+ slug: 'harleenvanhynten',
+ url: 'https://www.analvids.com/studios/harleen-van-hynten-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sweetyx',
+ slug: 'sweetyx',
+ url: 'https://www.analvids.com/studios/jean-marie-corda-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'PanPorn',
+ slug: 'panporn',
+ url: 'https://www.analvids.com/studios/panporn-production',
+ parent: 'analvids',
+ },
+ {
+ name: 'Alt Perversion',
+ slug: 'altperversion',
+ url: 'https://www.analvids.com/studios/alt_perversion',
+ parent: 'analvids',
+ },
+ {
+ name: "Lutro's World",
+ slug: 'lutrosworld',
+ url: 'https://www.analvids.com/studios/lutro--s-world',
+ parent: 'analvids',
+ alias: [
+ 'lw',
+ ],
+ },
+ {
+ name: 'Mixed studios',
+ slug: 'mixedstudios',
+ url: 'https://www.analvids.com/studios/mixed-studios',
+ parent: 'analvids',
+ alias: [
+ 'ms',
+ ],
+ },
+ {
+ name: 'Lizzy Laynez Entertainment',
+ slug: 'lizzylaynezentertainment',
+ url: 'https://www.analvids.com/studios/lizzy-laynez-entertainment',
+ parent: 'analvids',
+ },
+ {
+ name: 'Melina May',
+ slug: 'melinamay',
+ url: 'https://www.analvids.com/studios/melina_may',
+ parent: 'analvids',
+ },
+ {
+ name: 'Outside the Studio',
+ slug: 'outsidethestudio',
+ url: 'https://www.analvids.com/studios/outside-the-studio',
+ parent: 'analvids',
+ alias: [
+ 'ots',
+ ],
+ },
+ {
+ name: 'Alix Lynx',
+ slug: 'alixlynx',
+ url: 'https://www.analvids.com/studios/alix--s-dream-world',
+ parent: 'analvids',
+ },
+ {
+ name: 'Argendana Official',
+ slug: 'argendanaofficial',
+ url: 'https://www.analvids.com/studios/argendana_official',
+ parent: 'analvids',
+ },
+ {
+ name: "YumYum's Studio",
+ slug: 'yumyumsstudio',
+ url: 'https://www.analvids.com/studios/yumyum_s_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Culioneros',
+ slug: 'culioneros',
+ url: 'https://www.analvids.com/studios/culioneros',
+ parent: 'analvids',
+ },
+ {
+ name: "Shinaryen's Pure Pleasure",
+ slug: 'shinaryenspurepleasure',
+ url: 'https://www.analvids.com/studios/shinaryen--s-pure-pleasure',
+ parent: 'analvids',
+ },
+ {
+ name: 'johnpricexo',
+ slug: 'johnpricexo',
+ url: 'https://www.analvids.com/studios/johnpricexoxo',
+ parent: 'analvids',
+ },
+ {
+ name: 'Lara De Santis studio',
+ slug: 'laradesantisstudio',
+ url: 'https://www.analvids.com/studios/lara-de-santis-studio',
+ parent: 'analvids',
+ alias: [
+ 'lds',
+ ],
+ },
+ {
+ name: 'Cherry Aleksa ',
+ slug: 'cherryaleksa',
+ url: 'https://www.analvids.com/studios/cherry-aleksa-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'First Gape',
+ slug: 'firstgape',
+ url: 'https://www.analvids.com/studios/first-gape',
+ parent: 'analvids',
+ alias: [
+ 'sal',
+ ],
+ },
+ {
+ name: 'kaiiaeve',
+ slug: 'kaiiaeve',
+ url: 'https://www.analvids.com/studios/kaiiaeve',
+ parent: 'analvids',
+ },
+ {
+ name: 'PossiblyNeighbours',
+ slug: 'possiblyneighbours',
+ url: 'https://www.analvids.com/studios/possibly-neighbours',
+ parent: 'analvids',
+ alias: [
+ 'pn',
+ ],
+ },
+ {
+ name: 'Dorian Del Isla',
+ slug: 'doriandelisla',
+ url: 'https://www.analvids.com/studios/dorian_del_isla',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sarah Slave Studio',
+ slug: 'sarahslavestudio',
+ url: 'https://www.analvids.com/studios/sarah_slave_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Focus',
+ slug: 'focus',
+ url: 'https://www.analvids.com/studios/focus',
+ parent: 'analvids',
+ alias: [
+ 'fs',
+ ],
+ },
+ {
+ name: 'No Rest For The Ass',
+ slug: 'norestfortheass',
+ url: 'https://www.analvids.com/studios/no-rest-for-the-ass',
+ parent: 'analvids',
+ alias: [
+ 'nr',
+ ],
+ },
+ {
+ name: 'Atomic Porn Studio',
+ slug: 'atomicpornstudio',
+ url: 'https://www.analvids.com/studios/mlr_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Alexa Moore',
+ slug: 'alexamoore',
+ url: 'https://www.analvids.com/studios/alexa_moore',
+ parent: 'analvids',
+ },
+ {
+ name: 'Jessae Rosae x Savory Father',
+ slug: 'jessaerosaexsavoryfather',
+ url: 'https://www.analvids.com/studios/jessae_rosae_x_savory_father',
+ parent: 'analvids',
+ },
+ {
+ name: 'Proton Videos',
+ slug: 'protonvideos',
+ url: 'https://www.analvids.com/studios/proton-videos',
+ parent: 'analvids',
+ },
+ {
+ name: 'Timea Bella Production',
+ slug: 'timeabellaproduction',
+ url: 'https://www.analvids.com/studios/timea-bella-production',
+ parent: 'analvids',
+ alias: [
+ 'tb',
+ ],
+ },
+ {
+ name: 'Eros Mastery',
+ slug: 'erosmastery',
+ url: 'https://www.analvids.com/studios/eros_mastery',
+ parent: 'analvids',
+ },
+ {
+ name: 'MaryWet',
+ slug: 'marywet',
+ url: 'https://www.analvids.com/studios/marywet',
+ parent: 'analvids',
+ alias: [
+ 'ots',
+ ],
+ },
+ {
+ name: 'Cassie Del Isla',
+ slug: 'cassiedelisla',
+ url: 'https://www.analvids.com/studios/del-isla',
+ parent: 'analvids',
+ },
+ {
+ name: 'DaddyEnjoy',
+ slug: 'daddyenjoy',
+ url: 'https://www.analvids.com/studios/daddyenjoy',
+ parent: 'analvids',
+ alias: [
+ 'de',
+ ],
+ },
+ {
+ name: 'Gonzo in Brazil',
+ slug: 'gonzoinbrazil',
+ url: 'https://www.analvids.com/studios/gonzo-in-brazil',
+ parent: 'analvids',
+ alias: [
+ 'bz',
+ ],
+ },
+ {
+ name: 'Private Castings',
+ slug: 'privatecastings',
+ url: 'https://www.analvids.com/studios/private-castings',
+ parent: 'analvids',
+ },
+ {
+ name: 'XXXPawn',
+ slug: 'xxxpawn',
+ url: 'https://www.analvids.com/studios/xxxpawn',
+ parent: 'analvids',
+ },
+ {
+ name: 'MyBangVan',
+ slug: 'mybangvan',
+ url: 'https://www.analvids.com/studios/mybangvan',
+ parent: 'analvids',
+ },
+ {
+ name: 'Cherry on Top',
+ slug: 'cherryontop',
+ url: 'https://www.analvids.com/studios/cherry-on-top',
+ parent: 'analvids',
+ },
+ {
+ name: 'Gaywire',
+ slug: 'gaywire',
+ url: 'https://www.analvids.com/studios/gaywire',
+ parent: 'analvids',
+ },
+ {
+ name: 'College Rules',
+ slug: 'collegerules',
+ url: 'https://www.analvids.com/studios/college-rules',
+ parent: 'analvids',
+ },
+ {
+ name: 'onlyjewelzblu Studio',
+ slug: 'onlyjewelzblustudio',
+ url: 'https://www.analvids.com/studios/onlyjewelzblu_studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Kitty Blair',
+ slug: 'kittyblair',
+ url: 'https://www.analvids.com/studios/kitty-blair-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Queen Eugenia Studio',
+ slug: 'queeneugeniastudio',
+ url: 'https://www.analvids.com/studios/queen-eugenia-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Studio GD',
+ slug: 'studiogd',
+ url: 'https://www.analvids.com/studios/studio-dg',
+ parent: 'analvids',
+ },
+ {
+ name: 'Tarra White',
+ slug: 'tarrawhite',
+ url: 'https://www.analvids.com/studios/tarra-white',
+ parent: 'analvids',
+ alias: [
+ 'tw',
+ ],
+ },
+ {
+ name: 'Filthy Family',
+ slug: 'filthyfamily',
+ url: 'https://www.analvids.com/studios/filthy-family',
+ parent: 'analvids',
+ },
+ {
+ name: 'CHERRYxLUCKY',
+ slug: 'cherryxlucky',
+ url: 'https://www.analvids.com/studios/cherryxlucky',
+ parent: 'analvids',
+ },
+ {
+ name: 'Busty Adventures',
+ slug: 'bustyadventures',
+ url: 'https://www.analvids.com/studios/busty-adventures',
+ parent: 'analvids',
+ },
+ {
+ name: 'Helena Moeller Studio',
+ slug: 'helenamoellerstudio',
+ url: 'https://www.analvids.com/studios/helena-moeller-studio',
+ parent: 'analvids',
+ alias: [
+ 'hms',
+ ],
+ },
+ {
+ name: "man's cave",
+ slug: 'manscave',
+ url: 'https://www.analvids.com/studios/mancave',
+ parent: 'analvids',
+ },
+ {
+ name: 'Jamie French Productions',
+ slug: 'jamiefrenchproductions',
+ url: 'https://www.analvids.com/studios/jamie_french_productions',
+ parent: 'analvids',
+ },
+ {
+ name: 'Public Invasion',
+ slug: 'publicinvasion',
+ url: 'https://www.analvids.com/studios/public-invasion',
+ parent: 'analvids',
+ },
+ {
+ name: 'Omar Galanti Productions',
+ slug: 'omargalantiproductions',
+ url: 'https://www.analvids.com/studios/omar-galanti-productions',
+ parent: 'analvids',
+ },
+ {
+ name: 'InfiltrateProxy',
+ slug: 'infiltrateproxy',
+ url: 'https://www.analvids.com/studios/infiltrateproxy',
+ parent: 'analvids',
+ },
+ {
+ name: 'All About Sweet Bunny',
+ slug: 'allaboutsweetbunny',
+ url: 'https://www.analvids.com/studios/all-about-sweet-bunny',
+ parent: 'analvids',
+ alias: [
+ 'swb',
+ ],
+ },
+ {
+ name: 'Anal pantyhose addicts ',
+ slug: 'analpantyhoseaddicts',
+ url: 'https://www.analvids.com/studios/anal_pantyhose_addicts_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Nelly Kent Studio',
+ slug: 'nellykentstudio',
+ url: 'https://www.analvids.com/studios/nelly-kent-production',
+ parent: 'analvids',
+ },
+ {
+ name: 'The Kriss Kiss Experience',
+ slug: 'thekrisskissexperience',
+ url: 'https://www.analvids.com/studios/the-kriss-kiss-experience',
+ parent: 'analvids',
+ },
+ {
+ name: 'F Models',
+ slug: 'fmodels',
+ url: 'https://www.analvids.com/studios/f-models',
+ parent: 'analvids',
+ alias: [
+ 'fm',
+ ],
+ },
+ {
+ name: 'Luna Sapphire ',
+ slug: 'lunasapphire',
+ url: 'https://www.analvids.com/studios/luna_sapphire_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Tiffany Leiddi ',
+ slug: 'tiffanyleiddi',
+ url: 'https://www.analvids.com/studios/tiffany_leiddi_',
+ parent: 'analvids',
+ },
+ {
+ name: 'Blessexxx',
+ slug: 'blessexxx',
+ url: 'https://www.analvids.com/studios/blessexxx',
+ parent: 'analvids',
+ },
+ {
+ name: 'CarryLight',
+ slug: 'carrylight',
+ url: 'https://www.analvids.com/studios/carrylight',
+ parent: 'analvids',
+ },
+ {
+ name: 'Horny Belle',
+ slug: 'hornybelle',
+ url: 'https://www.analvids.com/studios/horny-belle',
+ parent: 'analvids',
+ },
+ {
+ name: 'GOSTOSAS VIDEO ',
+ slug: 'gostosasvideo',
+ url: 'https://www.analvids.com/studios/gostosas_video_',
+ parent: 'analvids',
+ },
+ {
+ name: "Rebecca's Clips",
+ slug: 'rebeccasclips',
+ url: 'https://www.analvids.com/studios/rebecca--s-clips',
+ parent: 'analvids',
+ alias: [
+ 'rv',
+ ],
+ },
+ {
+ name: 'Brandi Belle',
+ slug: 'brandibelle',
+ url: 'https://www.analvids.com/studios/brandi-belle',
+ parent: 'analvids',
+ },
+ {
+ name: 'Club Candy Alexa',
+ slug: 'clubcandyalexa',
+ url: 'https://www.analvids.com/studios/club-candy-alexa',
+ parent: 'analvids',
+ alias: [
+ 'cca',
+ ],
+ },
+ {
+ name: 'Bareback Attack',
+ slug: 'barebackattack',
+ url: 'https://www.analvids.com/studios/bareback-attack',
+ parent: 'analvids',
+ },
+ {
+ name: 'bdsmmanga',
+ slug: 'bdsmmanga',
+ url: 'https://www.analvids.com/studios/bdsmmanga',
+ parent: 'analvids',
+ },
+ {
+ name: 'ViSpace',
+ slug: 'vispace',
+ url: 'https://www.analvids.com/studios/vispace',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sophie Ladder',
+ slug: 'sophieladder',
+ url: 'https://www.analvids.com/studios/sophie_ladder',
+ parent: 'analvids',
+ },
+ {
+ name: 'vangoren',
+ slug: 'vangoren',
+ url: 'https://www.analvids.com/studios/vangoren',
+ parent: 'analvids',
+ },
+ {
+ name: 'DreamInSkies',
+ slug: 'dreaminskies',
+ url: 'https://www.analvids.com/studios/dreaminskies',
+ parent: 'analvids',
+ },
+ {
+ name: 'Its Gonna Hurt',
+ slug: 'itsgonnahurt',
+ url: 'https://www.analvids.com/studios/its-gonna-hurt',
+ parent: 'analvids',
+ },
+ {
+ name: 'CRUNCHBOY',
+ slug: 'crunchboy',
+ url: 'https://www.analvids.com/studios/crunchboy',
+ parent: 'analvids',
+ },
+ {
+ name: 'BigDaddy Raw',
+ slug: 'bigdaddyraw',
+ url: 'https://www.analvids.com/studios/bigdaddy_raw',
+ parent: 'analvids',
+ },
+ {
+ name: 'Hairy Gonzo',
+ slug: 'hairygonzo',
+ url: 'https://www.analvids.com/studios/hairy-gonzo',
+ parent: 'analvids',
+ alias: [
+ 'hg',
+ ],
+ },
+ {
+ name: 'Rub Him',
+ slug: 'rubhim',
+ url: 'https://www.analvids.com/studios/rub-him',
+ parent: 'analvids',
+ },
+ {
+ name: 'UngloryHole',
+ slug: 'ungloryhole',
+ url: 'https://www.analvids.com/studios/ungloryhole',
+ parent: 'analvids',
+ },
+ {
+ name: 'Bareback Casting',
+ slug: 'barebackcasting',
+ url: 'https://www.analvids.com/studios/bareback-casting',
+ parent: 'analvids',
+ },
+ {
+ name: 'JuliaKissy',
+ slug: 'juliakissy',
+ url: 'https://www.analvids.com/studios/juliasquirt',
+ parent: 'analvids',
+ },
+ {
+ name: "Honour Mays' Manor House",
+ slug: 'honourmaysmanorhouse',
+ url: 'https://www.analvids.com/studios/honour-mays---manor-house',
+ parent: 'analvids',
+ alias: [
+ 'hm',
+ ],
+ },
+ {
+ name: 'StaceyAlexisPawg',
+ slug: 'staceyalexispawg',
+ url: 'https://www.analvids.com/studios/staceyalexispawg',
+ parent: 'analvids',
+ },
+ {
+ name: 'Gay Patrol',
+ slug: 'gaypatrol',
+ url: 'https://www.analvids.com/studios/gay-patrol',
+ parent: 'analvids',
+ },
+ {
+ name: 'Thug Hunter',
+ slug: 'thughunter',
+ url: 'https://www.analvids.com/studios/thug-hunter',
+ parent: 'analvids',
+ },
+ {
+ name: 'SexyNEBBW',
+ slug: 'sexynebbw',
+ url: 'https://www.analvids.com/studios/sexynebbw',
+ parent: 'analvids',
+ },
+ {
+ name: 'Out In Public',
+ slug: 'outinpublic',
+ url: 'https://www.analvids.com/studios/out-in-public',
+ parent: 'analvids',
+ },
+ {
+ name: 'Poundhisass',
+ slug: 'poundhisass',
+ url: 'https://www.analvids.com/studios/poundhisass',
+ parent: 'analvids',
+ },
+ {
+ name: 'Bait Bus',
+ slug: 'baitbus',
+ url: 'https://www.analvids.com/studios/bait-bus-',
+ parent: 'analvids',
+ },
+ {
+ name: 'Diamonds Production',
+ slug: 'diamondsproduction',
+ url: 'https://www.analvids.com/studios/diamonds_production',
+ parent: 'analvids',
+ },
+ {
+ name: 'Sinemale',
+ slug: 'sinemale',
+ url: 'https://www.analvids.com/studios/sinemale',
+ parent: 'analvids',
+ },
+ {
+ name: 'Fuck You Cracker',
+ slug: 'fuckyoucracker',
+ url: 'https://www.analvids.com/studios/fuck-you-cracker',
+ parent: 'analvids',
+ },
+ {
+ name: 'Dark_Lady77',
+ slug: 'darklady77',
+ url: 'https://www.analvids.com/studios/dark_lady77',
+ parent: 'analvids',
+ },
+ {
+ name: 'Butter Loads',
+ slug: 'butterloads',
+ url: 'https://www.analvids.com/studios/butter-loads',
+ parent: 'analvids',
+ },
+ {
+ name: 'Edita Fantasy Studio',
+ slug: 'editafantasystudio',
+ url: 'https://www.analvids.com/studios/edita-fantasy-studio',
+ parent: 'analvids',
+ },
+ {
+ name: 'Gay Pawn',
+ slug: 'gaypawn',
+ url: 'https://www.analvids.com/studios/gay-pawn',
+ parent: 'analvids',
+ },
+ {
+ name: 'Troop Candy',
+ slug: 'troopcandy',
+ url: 'https://www.analvids.com/studios/troop-candy',
+ parent: 'analvids',
+ },
+ {
+ name: 'Grab Ass',
+ slug: 'grabass',
+ url: 'https://www.analvids.com/studios/grab-ass',
+ parent: 'analvids',
+ },
+ {
+ name: 'sydneyscreams4u',
+ slug: 'sydneyscreams4u',
+ url: 'https://www.analvids.com/studios/sydneyscreams4u',
+ parent: 'analvids',
+ },
+ {
+ name: 'Nade Nasty ',
+ slug: 'nadenasty',
+ url: 'https://www.analvids.com/studios/nade_nasty_',
+ parent: 'analvids',
+ },
+ {
+ name: 'ExBF',
+ slug: 'exbf',
+ url: 'https://www.analvids.com/studios/exbf',
+ parent: 'analvids',
+ },
+ {
+ name: 'Project City Bus',
+ slug: 'projectcitybus',
+ url: 'https://www.analvids.com/studios/project-city-bus',
+ parent: 'analvids',
+ },
+ {
+ name: 'Nylon Lingerie Studio',
+ slug: 'nylonlingeriestudio',
+ url: 'https://www.analvids.com/studios/nylon_lingerie_studio',
+ parent: 'analvids',
},
];
/* eslint-disable max-len */
-exports.seed = knex => Promise.resolve()
+exports.seed = (knex) => Promise.resolve()
.then(async () => {
const networks = await knex('entities')
- .whereIn('slug', studios.map(studio => studio.parent));
+ .whereIn('slug', studios.map((studio) => studio.parent));
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
- const studiosWithNetwork = studios.map(studio => ({
+ const studiosWithNetwork = studios.map((studio) => ({
slug: studio.slug,
name: studio.name,
url: studio.url,
diff --git a/seeds/04_media.js b/seeds/04_media.js
index 4e12b6f1..1d6726e1 100644
--- a/seeds/04_media.js
+++ b/seeds/04_media.js
@@ -594,21 +594,21 @@ const tagMedia = [
['69', 0, 'Abby Lee Brazil and Ramon Nomar', 'wicked'],
['69', 4, 'Abella Danger and Karma Rx in "Neon Dreaming"', 'brazzers'],
['69', 2, 'Abigail Mac and Kissa Sins in "Lesbian Anal Workout"', 'hardx'],
- ['airtight', 'adriana_chechik_hope_howell_hopehowellxxx_1', 'Adriana Chechik and Hope Howell in "Hope And Adriana\'s Gangbang', 'hopehowellxxx'],
+ ['airtight', 'adriana_chechik_hardx', 'Adriana Chechik', 'hardx'],
['airtight', 7, 'Lana Rhoades in "Gangbang Me 3"', 'hardx'],
['airtight', 'hime_marie_blackedraw', 'Hime Marie', 'blackedraw'],
['airtight', 6, 'Remy Lacroix in "Ass Worship 14"', 'julesjordan'],
- ['airtight', 'anissa_kate_legalporno', 'Anissa Kate in GP1962', 'legalporno'],
+ ['airtight', 'anissa_kate_legalporno', 'Anissa Kate in GP1962', 'analvids'],
['airtight', 'emily_willis_blacked', 'Emily Willis', 'blacked'],
['airtight', 'diamond_foxxx_milfslikeitbig', 'Diamond Foxx in "Diamond\'s Bday Gangbang"', 'milfslikeitbig'],
['airtight', 'tory_lane_bigtitsatwork', 'Tory Lane in "I\'m Your Christmas Bonus"', 'bigtitsatwork'],
['airtight', 11, 'Malena Nazionale in "Rocco\'s Perverted Secretaries 2: Italian Edition"', 'roccosiffredi'],
['airtight', 3, 'Anita Bellini in "Triple Dick Gangbang"', 'handsonhardcore'],
- ['airtight', 'venera_maxima_legalporno', 'Venera Maxima in LegalPorno SZ2645', 'legalporno'],
+ ['airtight', 'venera_maxima_legalporno', 'Venera Maxima in LegalPorno SZ2645', 'analvids'],
['airtight', 'mina_ddfnetwork', 'Remy Lacroix in "Ass Worship 14"', 'julesjordan'],
['airtight', 1, 'Jynx Maze in "Pump My Ass Full of Cum 3"', 'julesjordan'],
['airtight', 10, 'Asa Akira in "Asa Akira To The Limit"', 'julesjordan'],
- ['airtight', 8, 'Veronica Leal in SZ2520', 'legalporno'],
+ ['airtight', 8, 'Veronica Leal in SZ2520', 'analvids'],
['airtight', 5, 'Chloe Amour in "DP Masters 4"', 'julesjordan'],
['airtight', 9, 'Cindy Shine in GP1658'],
['anal', 5, 'Abella Danger', 'hardx'],
@@ -656,6 +656,7 @@ const tagMedia = [
['atogm', 0, 'Alysa Gap and Logan in "Anal Buffet 4"', 'evilangel'],
['atogm', 'adriana_chechik_hope_howell_hopehowellxxx', 'Adriana Chechik and Hope Howell in "Hope And Adriana\'s Gangbang', 'hopehowellxxx'],
['bdsm', 0, 'Dani Daniels in "The Traning of Dani Daniels, Day 2"', 'thetrainingofo'],
+ ['bisexual', 'bunny_colby_dante_colle_dillon_diaz_devilsfilm', 'Bunny Colby, Dante Colle and Dillon Diaz', 'devilsfilm'],
['black', 2, 'Nia Nacci', 'sweetheartvideo'],
['black', 1, 'Ana Foxxx in "DP Me 4"', 'hardx'],
['black', 'zaawaadi_asia_rae_allblackx', 'Zaawaadi and Asia Rae in "All Black Threesome"', 'allblackx'],
@@ -664,13 +665,14 @@ const tagMedia = [
['blonde', 3, 'Kylie Page in "A Juicy Afternoon Delight"', 'newsensations'],
['blonde', 'shawna_lenee_sunrisekings', 'Shawna Lenee', 'sunrisekings'],
['blonde', 2, 'Isabelle Deltore', 'herlimit'],
+ ['blowbang', 'cory_chase_interracialblowbang_4', 'Cory Chase', 'interracialblowbang'],
['blowbang', 'ana_foxxx_hardx', 'Ana Foxxx in "Facialized Vol. 4"', 'hardx'],
- ['blowbang', 'lisey_sweet_legalporno', 'Lisey Sweet in GIO816', 'legalporno'],
+ ['blowbang', 'lisey_sweet_legalporno', 'Lisey Sweet in GIO816', 'analvids'],
['blowbang', 'angela_white_julesjordan', 'Angela White in "Her Biggest Gangbang Ever"', 'julesjordan'],
- ['blowbang', 'monika_fox_legalporno', 'Monika Fox in GL479', 'legalporno'],
+ ['blowbang', 'monika_fox_legalporno', 'Monika Fox in GL479', 'analvids'],
['blowbang', 0, 'Lacy Lennon in "Lacy Lennon\'s First Blowbang"', 'hardx'],
['blowbang', 'zaawaadi_roccosiffredi_1', 'Zaawaadi in "My Name Is Zaawaadi"', 'roccosiffredi'],
- ['blowbang', 1, 'Nicole Black in GIO1680', 'legalporno'],
+ ['blowbang', 1, 'Nicole Black in GIO1680', 'analvids'],
['blowbang', 'gina_gerson_assholefever', 'Gina Gerson in "Oppa Gangbang Style"', 'assholefever'],
['blowjob', 'clanddi_jinkcego_ddfbusty_1', 'Clanddi Jinkcego', 'ddfbusty'],
['blowjob', 'juelz_ventura_babygotboobs', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'],
@@ -711,7 +713,7 @@ const tagMedia = [
['cum-in-mouth', 4, 'Vanna Bardot and Isiah Maxwell in "Vanna Craves Isiah\'s Cock!"', 'darkx'],
['cum-in-mouth', 'kaylani_lei_milfslikeitbig', 'Kaylani Lei', 'milfslikeitbig'],
['cum-in-mouth', 2, 'Jaye Summers in "Double The Cum"', 'hardx'],
- ['cum-in-mouth', 'lara_frost_legalporno', 'Lara Frost in NRX059', 'legalporno'],
+ ['cum-in-mouth', 'lara_frost_legalporno', 'Lara Frost in NRX059', 'analvids'],
['cum-in-mouth', 0, 'Vina Sky and Avi Love', 'hardx'],
['cum-on-boobs', 'september_reign_penthouse', 'September Reign in "Sensual Ride"', 'penthouse'],
['cum-on-boobs', 'gogo_fukme_devilsfilm', 'GoGo FukMe in "BAD Relatives"', 'devilsfilm'],
@@ -722,23 +724,21 @@ const tagMedia = [
['cum-on-butt', 0, 'Jynx Maze in "Don\'t Make Me Beg 4"', 'evilangel'],
['cum-on-pussy', 'katrina_moreno_elegantraw', 'Katrina Moreno in "Pump My Ass Raw!"', 'elegantraw'],
['cum-on-pussy', 0, 'Talinka A', 'sexart'],
- ['da-tp', 7, 'Polly Petrova in YE069', 'legalporno'],
+ ['da-tp', 7, 'Polly Petrova in YE069', 'analvids'],
['da-tp', 5, 'Venera Maxima in GIO1287'],
['da-tp', 6, 'Adriana Chechik in "Gangbang Me"', 'hardx'],
- ['da-tp', 0, 'Natasha Teen in SZ2164'],
- ['da-tp', 1, 'Francys Belle in SZ1702', 'legalporno'],
['dap', 7, 'Adriana Chechik in "DP Masters 6"', 'julesjordan'],
['dap', 10, 'Kira Noir', 'hardx'],
- ['dap', 'emily_pink_legalporno', 'Emily Pink', 'legalporno'],
- ['dap', 'silvia_dellai_legalporno', 'Silvia Dellai in GIO1765', 'legalporno'],
- ['dap', 9, 'Vicky Sol in GIO1547', 'legalporno'],
+ ['dap', 'emily_pink_legalporno', 'Emily Pink', 'analvids'],
+ ['dap', 'silvia_dellai_legalporno', 'Silvia Dellai in GIO1765', 'analvids'],
+ ['dap', 9, 'Vicky Sol in GIO1547', 'analvids'],
['dap', 'cherry_kiss_roccosiffredi', 'Cherry Kiss in "My Name Is Zaawaadi"', 'roccosiffredi'],
['dap', 6, 'Sheena Shaw in "Ass Worship 14"', 'julesjordan'],
['dap', 2, 'Lana Rhoades in "Lana Rhoades Unleashed"', 'hardx'],
['dap', 11, 'Haley Reed in "Young Hot Ass"', 'evilangel'],
- ['dap', 1, 'Ria Sunn in SZ1801', 'legalporno'],
+ ['dap', 1, 'Ria Sunn in SZ1801', 'analvids'],
['dap', 5, 'Riley Reid in "The Gangbang of Riley Reid"', 'julesjordan'],
- ['dap', 0, 'Nicole Black doing double anal during a gangbang in GIO971', 'legalporno'],
+ ['dap', 0, 'Nicole Black doing double anal during a gangbang in GIO971', 'analvids'],
['deepthroat', 'janice_griffith_throated_1', 'Janice Griffith', 'throated'],
['deepthroat', 2, 'Sarah Vandella', 'throated'],
['deepthroat', 3, 'Kira Noir in "Ebony Throat Vs Monster Cock"', 'throated'],
@@ -794,19 +794,20 @@ const tagMedia = [
['double-dildo-kiss', 3, 'Kiki Daire and Brittany', 'kenmarcus'],
['double-dildo-kiss', 2, 'Adriana Chechik and Vicki Chase in "Anal Savages"', 'julesjordan'],
['dp', 2, 'Megan Rain in "DP Masters 4"', 'julesjordan'],
- ['dp', 'lara_frost_legalporno', 'Lara Frost in NRX070', 'legalporno'],
- ['dp', 'juelz_ventura_babygotboobs_1', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'],
+ ['dp', 'lara_frost_legalporno', 'Lara Frost in NRX070', 'analvids'],
['dp', 6, 'Kira Noir', 'hardx'],
+ ['dp', 3, 'Hime Marie in AA047', 'analvids'],
['dp', 'kenna_james_tushy_1', 'Kenna James in "Yoga Retreat', 'tushy'],
['dp', 5, 'Lana Rhoades in "Gangbang Me 3"', 'hardx'],
+ ['dp', 'juelz_ventura_babygotboobs_1', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'],
['dp', 'courtney_taylor_realwifestories', 'Courtney Taylor in "Divorce Me Please"', 'realwifewstories'],
- ['dp', 3, 'Hime Marie in AA047', 'legalporno'],
['dp', 'silvia_dellai_dpfanatics', 'Silvia Dellai in "Tempting Promises"', 'dpfanatics'],
['dp', 'diamond_foxxx_milfslikeitbig', 'Diamond Foxxx in "Deep Cover...Deeper Throat"', 'milfslikeitbig'],
['dp', 'zaawaadi_roccosiffredi', 'Zaawaadi in "My Name Is Zaawaadi"', 'roccosiffredi'],
['dp', 7, 'Chloe Lamour in "DP Masters 7"', 'julesjordan'],
['dp', 'poster', 'Mia Malkova in "DP Me 8"', 'hardx'],
['dp', 4, 'Rebecca Volpetti', 'handsonhardcore'],
+ ['dvp', 'adriana_chechik_hardx', 'Adriana Chechik', 'hardx'],
['dvp', 'poster', 'Riley Reid in "Pizza That Ass"', 'reidmylips'],
['dvp', 'jaclyn_case_digitalsin', 'Jaclyn Case in "2 Heads R Better Than 1 Volume Two"', 'digitalsin'],
['dvp', 'vina_sky_julesjordan', 'Vina Sky in "Asian Goddess Vina Sky Demands Two Cocks Inside Her At Once"', 'julesjordan'],
@@ -829,6 +830,7 @@ const tagMedia = [
['enhanced-boobs', 'september_reign_spizoo', 'September Rain in "September Reign Loves Jessica"', 'spizoo'],
['enhanced-boobs', 'katrina_moreno_bangbros', 'Katrina Moreno in "Stripper Cream Pie"', 'bangbros'],
['enhanced-boobs', 'sadie_santana_newsensations', 'Sadie Santana in "Backdoor Beauties"', 'newsensations'],
+ ['enhanced-boobs', 'ricki_raxxx_pornpros', 'Ricki Raxxx', 'pornpros'],
['enhanced-boobs', 'kiera_king_puremature', 'Kiera King in "Warming Up"', 'puremature'],
['enhanced-boobs', 'diana_prince_penthouse_2', 'Diana Prince in "It Is What It Seems"', 'penthouse'],
['enhanced-boobs', 'chessie_kay_chelsey_lanette_eurogirlsongirls', 'Chelsey Lanette and Chessie Kay', 'eurogirlsongirls'],
@@ -897,26 +899,27 @@ const tagMedia = [
['fisting', '1a', 'Aletta Ocean in "Solo Fisting"', '21sextury'],
['fisting', 0, 'Abella Danger and Karma Rx in "Neon Dreaming"', 'brazzers'],
['fisting-dp', 0, 'Janice Griffith and Veronica Avluv in "The Nymphomaniac\'s Apprentice', 'theupperfloor'],
- ['flexible', 'lara_frost_legalporno', 'Lara Frost in NRX059', 'legalporno'],
+ ['flexible', 'lara_frost_legalporno', 'Lara Frost in NRX059', 'analvids'],
['free-use', 'jeni_angel_brazzersexxtra', 'Jeni Angel in "Gamer Girl Threesome Action"', 'brazzersexxtra'],
['free-use', 'veruca_james_brazzersexxtra', 'Veruca James in "The Perfect Maid"', 'brazzersexxtra'],
['free-use', 'gia_dibella_freeusefantasy', 'Gia Dibella in "Learning to Freeuse"', 'freeusefantasy'],
['gangbang', 5, 'Carter Cruise\'s first gangbang in "Slut Puppies 9"', 'julesjordan'],
['gangbang', 'kristen_scott_julesjordan', 'Kristen Scott in "Interracial Gangbang!"', 'julesjordan'],
['gangbang', 'emily_willis_blacked', 'Emily Willis', 'blacked'],
- ['gangbang', 'monika_fox_legalporno', 'Monika Fox in GL479', 'legalporno'],
- ['gangbang', 7, 'Alexa Flexy in GL376', 'legalporno'],
- ['gangbang', 'silvia_dellai_legalporno', 'Silvia Dellai in GIO1825', 'legalporno'],
- ['gangbang', 'lara_frost_legalporno_1', 'Lara Frost in NRX070', 'legalporno'],
+ ['gangbang', 'monika_fox_legalporno', 'Monika Fox in GL479', 'analvids'],
+ ['gangbang', 7, 'Alexa Flexy in GL376', 'analvids'],
+ ['gangbang', 'silvia_dellai_legalporno', 'Silvia Dellai in GIO1825', 'analvids'],
+ ['gangbang', 'lara_frost_legalporno_1', 'Lara Frost in NRX070', 'analvids'],
['gangbang', 'gina_gerson_assholefever', 'Gina Gerson in "Oppa Gangbang Style"', 'assholefever'],
['gangbang', 0, '"4 On 1 Gangbangs"', 'doghousedigital'],
['gangbang', 4, 'Marley Brinx in "The Gangbang of Marley Brinx"', 'julesjordan'],
['gangbang', 1, 'Ginger Lynn in "Gangbang Mystique", a photoset shot by Suze Randall, 1984. Depicting a woman \'airtight\' pushed the boundaries of pornography at the time.'],
['gaping', 1, 'Vina Sky in "Vina Sky Does Anal"', 'hardx'],
- ['gaping', 4, 'Nicole Black in GIO1626', 'legalporno'],
+ ['gaping', 4, 'Nicole Black in GIO1626', 'analvids'],
['gaping', 'poster', 'Zoey Monroe in "Manuel DPs Them All 5"', 'julesjordan'],
['gaping', 3, 'Jessyka Swan', '21sextury'],
['gaping', 2, 'Alex Grey in "DP Masters 5"', 'julesjordan'],
+ ['gay', 'andy_taylor_deangelo_jackson_men', 'Andy Taylor and DeAngelo Jackson', 'men'],
['handjob', 0, 'Lichelle Marie in "Tug Me Sexy"', 'tugjobs'],
['handjob', 'mia_malkova_manojob', 'Mia Malkova in "Covered!"', 'manojob'],
['handjob', 'hope_howell_manojob', 'Hope Howell in "Super Slutty Step-Daugher"', 'manojob'],
@@ -953,7 +956,7 @@ const tagMedia = [
['mfm', 8, 'Ariana Marie in "DP Masters 7"', 'julesjordan'],
['mfm', 1, 'Lana Rhoades in "Gangbang Me 3"', 'hardx'],
['mfm', 'franceska_jaimes_digitalplayground', 'Franceska Jaimes in "Monarch"', 'digitalplayground'],
- ['mfm', 'hazel_moore_legalporno', 'Hazel Moore', 'legalporno'],
+ ['mfm', 'hazel_moore_legalporno', 'Hazel Moore', 'analvids'],
['mfm', 7, 'Rose Valerie', 'eurosexparties'],
['mfm', 6, 'Honey Gold in "Slut Puppies 12"', 'julesjordan'],
['natural-boobs', 1, 'Nia Nacci', 'firstclasspov'],
@@ -967,7 +970,7 @@ const tagMedia = [
['natural-boobs', 6, 'Blake Blossom in "Naturally Stacked Cutie"', 'hardx'],
['natural-boobs', 5, 'Chloe B in "Lamour"', 'metart'],
['natural-boobs', 2, 'Kylie Page', 'allgirlmassage'],
- ['nun', 0, 'Lady Zee in NF053', 'legalporno'],
+ ['nun', 0, 'Lady Zee in NF053', 'analvids'],
['nun', 1, 'Penny Pax and Darcie Dolce in "Confessions Of A Sinful Nun"', 'sweetheartvideo'],
['nun', 3, 'Higurashi Rin in "Naughty Nun"', 'allgravure'],
['nun', 2, 'Lea Lexis in "Confessions Of A Sinful Nun"', 'sweetheartvideo'],
@@ -984,12 +987,12 @@ const tagMedia = [
['oral-creampie', 1, 'Valentina Nappi', 'herlimit'],
['oral-creampie', 0, 'Henessy in "B(ass)t Friends"', 'assholefever'],
['orgy', 1, 'Megan Rain (DP), Morgan Lee (anal), Jessa Rhodes, Melissa Moore and Kimmy Granger in "Orgy Masters 8"', 'julesjordan'],
- ['orgy', 0, 'Vicky Sol and Jolee Love in GIO1550', 'legalporno'],
+ ['orgy', 0, 'Vicky Sol and Jolee Love in GIO1550', 'analvids'],
['orgy', 'zaawaadi_malena_cherry_kiss_roccosiffredi_5', 'Zaawaadi, Cherry Kiss and Malena in "My Name Is Zaawaadi"', 'roccosiffredi'],
['orgy', 'poster', 'Zoey Mornoe (DP), Jillian Janson (sex), Frida Sante, Katerina Kay and Natasha Starr in "Orgy Masters 6"', 'julesjordan'],
['parody', 0, 'Capri Cavanni and Dani Daniels in "The Whore of Wall Street"', 'brazzers'],
['piercings', 0, 'Kaegune in "When The Sun Goes Down"', 'suicidegirls'],
- ['piss-drinking', 0, 'Scarlet Domingo in GL227', 'legalporno'],
+ ['piss-drinking', 0, 'Scarlet Domingo in GL227', 'analvids'],
['pussy-eating', 5, 'Claudia Macc and Victoria Pure', 'eurogirlsongirls'],
['pussy-eating', 4, 'Anastasia Knight and Jillian Janson in "Teach Me"', 'screwbox'],
['pussy-eating', 'september_reign_penthouse', 'September Reign in "Sensual Ride"', 'penthouse'],
@@ -1014,18 +1017,20 @@ const tagMedia = [
['sex', 'jane_wilde_evilangel', 'Jane Wilde and Brock Cooper in "The Cock Hungry Chronicles"', 'evilangel'],
['squirting', 0, 'Veronica Rodriguez in "Hot Latina Squirting"', 'julesjordan'],
['squirting', 1, 'Abella Danger and Karma Rx in "Neon Dreaming"', 'brazzers'],
- ['swallowing', 0, 'Kira Thorn in GIO1023', 'legalporno'],
+ ['swallowing', 0, 'Kira Thorn in GIO1023', 'analvids'],
['tattoos', 0, 'Tigerlilly in "Wrapped In Blue"', 'suicidegirls'],
['tattoos', 1, 'Joanna Angel', 'joannaangel'],
['teen', 0, 'Alexa Flexy', 'sensualgirl'],
['teen', 1, 'Stalfra aka Precious', 'nubiles'],
['trainbang', 1, 'Ria Sunn', 'private'],
- ['trainbang', 0, 'Nicole Black in GIO971', 'legalporno'],
- ['tap', 4, 'Francys Belle in GIO1103', 'legalporno'],
- ['tap', 'lisey_sweet_legalporno', 'Lisey Sweet in GIO816', 'legalporno'],
- ['tap', 3, 'Julia Red in GIO1007', 'legalporno'],
- ['tap', 1, 'Natasha Teen in SZ2098', 'legalporno'],
- ['tap', 2, 'Kira Thorn in GIO1018', 'legalporno'],
+ ['trainbang', 0, 'Nicole Black in GIO971', 'analvids'],
+ ['transsexual', 'kelly_silva_mel_almeida_brazilliantranssexuals', 'Kelly Silva and Mel Almeida', 'brazilliantranssexuals'],
+ ['transsexual', 'amanda_fialho_tsraw', 'Amanda Fialho', 'tsraw'],
+ ['tap', 4, 'Francys Belle in GIO1103', 'analvids'],
+ ['tap', 'lisey_sweet_legalporno', 'Lisey Sweet in GIO816', 'analvids'],
+ ['tap', 3, 'Julia Red in GIO1007', 'analvids'],
+ ['tap', 1, 'Natasha Teen in SZ2098', 'analvids'],
+ ['tap', 2, 'Kira Thorn in GIO1018', 'analvids'],
['titty-fucking', 2, 'Layla London in "Touch Me"', 'bignaturals'],
['titty-fucking', 0, 'Kylie Page in "Stepsis Gives Soapy Handjob In Shower"', 'spyfam'],
['titty-fucking', 4, 'Set 5532', 'tugjobs'],
@@ -1042,8 +1047,9 @@ const tagMedia = [
['toy-dp', 0, 'Marley Brinx, Ivy Lebelle and Lyra Law in "Marley Brinx First GGDP"', 'lesbianx'],
['toys', 1, 'Chloe Lamour in "Curives In All The Right Places"', 'wetandpuffy'],
['toys', 'shawna_lenee_sunrisekings', 'Shawna Lenee', 'sunrisekings'],
+ ['triple-penetration', 'lucky_bee_analvids', 'Lucky Bee', 'analvids'],
['triple-penetration', 'angela_white_julesjordan', 'Angela White in "Her Biggest Gangbang Ever"', 'julesjordan'],
- ['triple-penetration', 'ria_sunn_legalporno', 'Ria Sunn in SZ2082', 'legalporno'],
+ ['triple-penetration', 'ria_sunn_legalporno', 'Ria Sunn in SZ2082', 'analvids'],
['tvp', 'september_reign_wefuckblackgirls', 'September Reign in "Second Appearance"', 'wefuckblackgirls'],
['trainbang', 'poster', 'Kali Roses in "Passing Me Around"', 'blacked'],
['trainbang', 'gina_gerson_assholefever', 'Gina Gerson in "Oppa Gangbang Style"', 'assholefever'],
diff --git a/seeds/06_affiliates.js b/seeds/06_affiliates.js
index c407eb6d..19ab0fa7 100644
--- a/seeds/06_affiliates.js
+++ b/seeds/06_affiliates.js
@@ -743,20 +743,20 @@ const campaigns = [
comment: '$30 per signup',
},
{
- network: 'legalporno',
- url: 'https://www.legalporno.com/new-videos?aff=BW90MHT1DP____',
+ network: 'analvids',
+ url: 'https://www.analvids.com/new-videos?aff=BW90MHT1DP____',
comment: 'default offer',
},
{
- channel: 'legalporno',
+ channel: 'analvids',
banner: 'pornworld_600_120_1',
- url: 'https://www.legalporno.com/new-videos?aff=BW90MHT1DP____',
+ url: 'https://www.analvids.com/new-videos?aff=BW90MHT1DP____',
comment: 'default offer',
},
{
- channel: 'legalporno',
+ channel: 'analvids',
banner: 'pornworld_600_120_2',
- url: 'https://www.legalporno.com/new-videos?aff=BW90MHT1DP____',
+ url: 'https://www.analvids.com/new-videos?aff=BW90MHT1DP____',
comment: 'default offer',
},
{
diff --git a/src/.eslintrc b/src/.eslintrc
index ed110336..a8e7201c 100644
--- a/src/.eslintrc
+++ b/src/.eslintrc
@@ -14,6 +14,6 @@
"prefer-destructuring": "off",
"template-curly-spacing": "off",
"object-curly-newline": "off",
- "max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}],
+ "max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true}]
}
}
diff --git a/src/actors.js b/src/actors.js
index f8c189dd..7af1d6fe 100644
--- a/src/actors.js
+++ b/src/actors.js
@@ -20,6 +20,7 @@ const scrapers = require('./scrapers/scrapers').actors;
const argv = require('./argv');
const include = require('./utils/argv-include')(argv);
const bulkInsert = require('./utils/bulk-insert');
+const chunk = require('./utils/chunk');
const logger = require('./logger')(__filename);
const { toBaseReleases } = require('./deep');
@@ -1048,33 +1049,42 @@ async function flushProfiles(actorIdsOrNames) {
logger.info(`Removed ${deleteCount} profiles`);
}
-async function deleteActors(actorIdsOrNames) {
- const actors = await knex('actors')
- .whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number'))
- .orWhere((builder) => {
- builder
- .whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string'))
- .whereNull('entity_id');
- });
+async function deleteActors(allActorIdsOrNames) {
+ const deleteCounts = await Promise.map(chunk(allActorIdsOrNames), async (actorIdsOrNames) => {
+ const actors = await knex('actors')
+ .whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number'))
+ .orWhere((builder) => {
+ builder
+ .whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string'))
+ .whereNull('entity_id');
+ });
- const actorIds = actors.map((actor) => actor.id);
+ const actorIds = actors.map((actor) => actor.id);
- const sceneIds = await knex('releases_actors')
- .select('releases.id')
- .whereIn('actor_id', actorIds)
- .leftJoin('releases', 'releases.id', 'releases_actors.release_id')
- .pluck('id');
+ const sceneIds = await knex('releases_actors')
+ .select('releases.id')
+ .whereIn('actor_id', actorIds)
+ .leftJoin('releases', 'releases.id', 'releases_actors.release_id')
+ .pluck('id');
- const [deletedScenesCount, deletedActorsCount] = await Promise.all([
- deleteScenes(sceneIds),
- knex('actors')
- .whereIn('id', actorIds)
- .delete(),
- ]);
+ const [deletedScenesCount, deletedActorsCount] = await Promise.all([
+ deleteScenes(sceneIds),
+ knex('actors')
+ .whereIn('id', actorIds)
+ .delete(),
+ ]);
+
+ return { deletedScenesCount, deletedActorsCount };
+ }, { concurrency: 10 });
+
+ const deletedActorsCount = deleteCounts.reduce((acc, count) => acc + count.deletedActorsCount, 0);
+ const deletedScenesCount = deleteCounts.reduce((acc, count) => acc + count.deletedScenesCount, 0);
await flushOrphanedMedia();
logger.info(`Removed ${deletedActorsCount} actors with ${deletedScenesCount} scenes`);
+
+ return deletedActorsCount;
}
async function flushActors() {
diff --git a/src/app.js b/src/app.js
index cb5fd1ad..a8fdac83 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,5 +1,6 @@
'use strict';
+const config = require('config');
const util = require('util');
// const log = require('why-is-node-running');
const Inspector = require('inspector-api');
@@ -24,33 +25,64 @@ const getFileEntries = require('./utils/file-entries');
const inspector = new Inspector();
let done = false;
-function logActive() {
- console.log('log active!');
-
- setTimeout(() => {
- // log();
- logActive();
- }, typeof argv.logActive === 'number' ? argv.logActive : 60000);
-}
-
/*
-function monitorMemory() {
- logger.debug(`Memory usage: ${process.memoryUsage.rss() / 1000000} MB`);
+function logActive() {
+ setTimeout(() => {
+ log();
- if (!done) {
- setTimeout(() => monitorMemory(), 10000);
- }
+ if (!done) {
+ logActive();
+ }
+ }, typeof argv.logActive === 'number' ? argv.logActive : 60000);
}
*/
-async function stopMemorySample() {
+async function snapshotMemory(trigger) {
+ const profile = await inspector.heap.takeSnapshot();
+ const filepath = `traxxx_snapshot_${trigger}M_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.heapsnapshot`;
+
+ logger.info(`Starting heap snapshot, memory usage: ${process.memoryUsage.rss() / 1000000} MB`);
+
+ await inspector.heap.disable();
+ await fs.writeFile(filepath, JSON.stringify(profile));
+
+ logger.info(`Saved heap snapshot to ${filepath}`);
+}
+
+async function stopMemorySample(snapshotTriggers) {
+ const usage = process.memoryUsage.rss() / 1000000;
+
const profile = await inspector.heap.stopSampling();
- const filepath = `${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.heapprofile`;
+ const filepath = `traxxx_sample_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.heapprofile`;
await inspector.heap.disable();
await fs.writeFile(filepath, JSON.stringify(profile));
logger.info(`Saved heap sample to ${filepath}`);
+
+ if (usage > snapshotTriggers[0]) {
+ await snapshotMemory(snapshotTriggers[0]);
+ return snapshotTriggers.slice(1);
+ }
+
+ return snapshotTriggers;
+}
+
+async function startMemorySample(snapshotTriggers = []) {
+ await inspector.heap.enable();
+ await inspector.heap.startSampling();
+
+ const usage = process.memoryUsage.rss() / 1000000;
+
+ logger.info(`Start heap sampling, memory usage: ${usage} MB`);
+
+ setTimeout(async () => {
+ const newSnapshotTriggers = await stopMemorySample(snapshotTriggers);
+
+ if (!done) {
+ await startMemorySample(newSnapshotTriggers);
+ }
+ }, config.memorySampling.sampleDuration);
}
async function startMemorySample() {
@@ -72,19 +104,19 @@ async function startMemorySample() {
async function init() {
try {
- if (argv.memory) {
- await startMemorySample();
- }
-
- if (argv.logActive) {
- logActive();
- }
-
if (argv.server) {
await initServer();
return;
}
+ if (argv.sampleMemory) {
+ await startMemorySample(config.memorySampling.snapshotIntervals);
+ }
+
+ if (argv.logActive) {
+ // logActive();
+ }
+
if (argv.updateSearch) {
await Promise.all([
updateSceneSearch(),
@@ -157,8 +189,13 @@ async function init() {
? await fetchScenes([...(sceneUrls), ...(updateBaseScenes || []), ...(actorBaseScenes || [])])
: [...(updateBaseScenes || []), ...(actorBaseScenes || [])];
- const sceneMovies = deepScenes ? deepScenes.filter((scene) => scene.movie).map((scene) => ({ ...scene.movie, entity: scene.entity })) : [];
- const deepMovies = argv.sceneMovies || argv.movie ? await fetchMovies([...(argv.movie || []), ...(sceneMovies || [])]) : sceneMovies;
+ const storedScenes = argv.save ? await storeScenes(deepScenes) : [];
+
+ const moviesFromFile = argv.moviesFile && await getFileEntries(argv.moviesFile);
+ const movieUrls = (argv.movie || []).concat(moviesFromFile || []);
+
+ const sceneMovies = deepScenes && argv.sceneMovies ? deepScenes.filter((scene) => scene.movie).map((scene) => ({ ...scene.movie, entity: scene.entity })) : [];
+ const deepMovies = argv.sceneMovies || argv.movie || movieUrls ? await fetchMovies([...movieUrls, ...(sceneMovies || []), ...[]]) : sceneMovies;
const movieScenes = argv.movieScenes ? deepMovies.map((movie) => movie.scenes?.map((scene) => ({ ...scene, movie, entity: movie.entity }))).flat().filter(Boolean) : [];
const deepMovieScenes = argv.deep ? await fetchScenes(movieScenes) : movieScenes;
@@ -169,10 +206,10 @@ async function init() {
}
if (argv.save) {
- const storedMovies = await storeMovies(deepMovies);
- const storedScenes = await storeScenes([...(deepScenes || []), ...(deepMovieScenes || [])]);
+ const storedMovies = await storeMovies(deepMovies, storedScenes[0]?.batchId);
+ const storedMovieScenes = await storeScenes(deepMovieScenes, storedScenes[0]?.batchId);
- await associateMovieScenes(storedMovies, storedScenes);
+ await associateMovieScenes(storedMovies, [...storedScenes, ...storedMovieScenes]);
}
} catch (error) {
logger.error(error);
diff --git a/src/argv.js b/src/argv.js
index de0d3fef..78b8f0dd 100644
--- a/src/argv.js
+++ b/src/argv.js
@@ -107,6 +107,11 @@ const { argv } = yargs
describe: 'Scrape movie info from URL',
type: 'array',
})
+ .option('movie-file', {
+ describe: 'Scrape movie info from URLs in a file',
+ type: 'string',
+ alias: 'movies-file',
+ })
.option('deep', {
describe: 'Fetch details for all releases',
type: 'boolean',
@@ -233,6 +238,7 @@ const { argv } = yargs
default: false,
})
.option('level', {
+ alias: 'log-level',
describe: 'Log level',
type: 'string',
default: process.env.NODE_ENV === 'development' ? 'silly' : 'info',
@@ -247,6 +253,12 @@ const { argv } = yargs
type: 'boolean',
default: process.env.NODE_ENV === 'development',
})
+ .option('sampleMemory', {
+ alias: 'memory',
+ describe: 'Take memory allocation samples, and snapshots at configured intervals',
+ type: 'boolean',
+ default: config.memorySampling.enabled,
+ })
.option('update-search', {
describe: 'Update search documents for all releases.',
type: 'boolean',
diff --git a/src/deep.js b/src/deep.js
index 5af3b1c7..a7ad4160 100644
--- a/src/deep.js
+++ b/src/deep.js
@@ -1,5 +1,6 @@
'use strict';
+const util = require('util');
const Promise = require('bluebird');
const { mergeAdvanced: merge } = require('object-merge-advanced');
@@ -9,6 +10,9 @@ const { fetchReleaseEntities, urlToSiteSlug } = require('./entities');
const logger = require('./logger')(__filename);
const qu = require('./utils/qu');
const getRecursiveParameters = require('./utils/get-recursive-parameters');
+const windows = require('./utils/http-windows');
+
+const waitImmediate = util.promisify(setImmediate);
function toBaseReleases(baseReleasesOrUrls, entity = null) {
if (!baseReleasesOrUrls) {
@@ -50,12 +54,12 @@ function toBaseReleases(baseReleasesOrUrls, entity = null) {
.filter(Boolean);
}
-async function fetchScene(scraper, url, entity, baseRelease, options) {
- if (scraper.fetchScene) {
- return scraper.fetchScene(baseRelease.url, entity, baseRelease, options, null);
+async function fetchScene(scraper, url, entity, baseRelease, options, type = 'scene') {
+ if ((type === 'scene' && scraper.fetchScene) || (type === 'movie' && scraper.fetchMovie)) {
+ return scraper[type === 'movie' ? 'fetchMovie' : 'fetchScene'](baseRelease.url, entity, baseRelease, options, null);
}
- if (scraper.scrapeScene) {
+ if ((type === 'scene' && scraper.scrapeScene) || (type === 'movie' && scraper.scrapeMovie)) {
const session = qu.session();
const res = await qu.get(url, null, null, {
@@ -66,7 +70,7 @@ async function fetchScene(scraper, url, entity, baseRelease, options) {
const cookie = await session._sessionOptions.cookieJar.get(url);
if (res.ok) {
- return scraper.scrapeScene(res.item, url, entity, baseRelease, options, {
+ return scraper[type === 'movie' ? 'scrapeMovie' : 'scrapeScene'](res.item, url, entity, baseRelease, options, {
session,
headers: res.headers,
cookieJar: session._sessionOptions.cookieJar,
@@ -80,6 +84,10 @@ async function fetchScene(scraper, url, entity, baseRelease, options) {
return null;
}
+function fetchMovie(scraper, url, entity, baseRelease, options) {
+ return fetchScene(scraper, url, entity, baseRelease, options, 'movie');
+}
+
async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
const entity = baseRelease.entity || entitiesBySlug[urlToSiteSlug(baseRelease.url)];
@@ -102,7 +110,7 @@ async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
return baseRelease;
}
- if ((type === 'scene' && !layoutScraper.fetchScene && !layoutScraper.scrapeScene) || (type === 'movie' && !layoutScraper.fetchMovie)) {
+ if ((type === 'scene' && !layoutScraper.fetchScene && !layoutScraper.scrapeScene) || (type === 'movie' && !layoutScraper.fetchMovie && !layoutScraper.scrapeMovie)) {
logger.warn(`The '${entity.name}'-scraper cannot scrape individual ${type}s`);
return baseRelease;
}
@@ -116,9 +124,28 @@ async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
parameters: getRecursiveParameters(entity),
};
+ logger.debug(`Memory usage before: ${process.memoryUsage.rss() / 1000000} MB (${baseRelease.url})`);
+
const rawScrapedRelease = type === 'scene'
- ? await fetchScene(layoutScraper, baseRelease.url, entity, baseRelease, options, null)
- : await layoutScraper.fetchMovie(baseRelease.url, entity, baseRelease, options, null);
+ ? await fetchScene(layoutScraper, baseRelease.url, entity, baseRelease, options)
+ : await fetchMovie(layoutScraper, baseRelease.url, entity, baseRelease, options);
+
+ const pathname = baseRelease.path || (baseRelease.url && new URL(baseRelease.url).pathname.replace(/\//g, '_'));
+
+ if (rawScrapedRelease) {
+ delete rawScrapedRelease.query; // some scrapers pass the qu-wrapped window instance to parent scrapers, filling up memory
+ }
+
+ if (windows.has(pathname)) {
+ logger.debug(`Closing window for ${pathname}`);
+
+ windows.get(pathname).close();
+ windows.delete(pathname);
+ }
+
+ await waitImmediate;
+
+ logger.debug(`Memory usage after: ${process.memoryUsage.rss() / 1000000} MB (${baseRelease.url})`);
const scrapedRelease = rawScrapedRelease?.scene || rawScrapedRelease;
@@ -173,12 +200,13 @@ async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
async function scrapeReleases(baseReleases, entitiesBySlug, type) {
const entitiesWithBeforeDataEntries = await Promise.all(Object.entries(entitiesBySlug).map(async ([slug, entity]) => {
if (entity.scraper?.beforeFetchScenes) {
- const preData = await entity.scraper.beforeFetchScenes(entity);
+ const parameters = getRecursiveParameters(entity);
+ const preData = await entity.scraper.beforeFetchScenes(entity, parameters);
return [slug, { ...entity, preData }];
}
- return null;
+ return [slug, entity];
}));
const entitiesWithBeforeDataBySlug = Object.fromEntries(entitiesWithBeforeDataEntries.filter(Boolean));
@@ -186,7 +214,7 @@ async function scrapeReleases(baseReleases, entitiesBySlug, type) {
return Promise.map(
baseReleases,
async (baseRelease) => scrapeRelease(baseRelease, entitiesWithBeforeDataBySlug, type),
- { concurrency: 10 },
+ { concurrency: 1 },
);
}
diff --git a/src/entities.js b/src/entities.js
index 47e731f4..9abb4044 100644
--- a/src/entities.js
+++ b/src/entities.js
@@ -6,7 +6,7 @@ const inquirer = require('inquirer');
const logger = require('./logger')(__filename);
const argv = require('./argv');
const knex = require('./knex');
-const { deleteScenes, deleteMovies } = require('./releases');
+const { deleteScenes, deleteMovies, deleteSeries } = require('./releases');
const { flushOrphanedMedia } = require('./media');
const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve');
@@ -236,7 +236,7 @@ async function fetchReleaseEntities(baseReleases) {
.filter(Boolean),
));
- return fetchEntitiesBySlug(entitySlugs);
+ return fetchEntitiesBySlug(entitySlugs, 'desc');
}
async function fetchEntity(entityId, type) {
@@ -359,29 +359,39 @@ async function flushEntities(networkSlugs = [], channelSlugs = []) {
.leftJoin('movies', 'movies.entity_id', 'selected_entities.id')
.pluck('movies.id');
- if (sceneIds.length === 0 && movieIds.length === 0) {
- logger.info(`No scenes or movies found to remove for ${entitySlugs}`);
+ const serieIds = await entityQuery
+ .clone()
+ .select('series.id')
+ .distinct('series.id')
+ .whereNotNull('series.id')
+ .from('selected_entities')
+ .leftJoin('series', 'series.entity_id', 'selected_entities.id')
+ .pluck('series.id');
+
+ if (sceneIds.length === 0 && movieIds.length === 0 && serieIds.length === 0) {
+ logger.info(`No scenes, movies or series found to remove for ${entitySlugs}`);
return;
}
const confirmed = await inquirer.prompt([{
type: 'confirm',
name: 'flushEntities',
- message: `You are about to remove ${sceneIds.length} scenes and ${movieIds.length} movies for ${entitySlugs}. Are you sure?`,
+ message: `You are about to remove ${sceneIds.length} scenes, ${movieIds.length} movies and ${serieIds.length} series for ${entitySlugs}. Are you sure?`,
default: false,
}]);
if (!confirmed.flushEntities) {
- logger.warn(`Confirmation rejected, not flushing scenes or movies for: ${entitySlugs}`);
+ logger.warn(`Confirmation rejected, not flushing scenes, movies or series for: ${entitySlugs}`);
return;
}
- const [deletedScenesCount, deletedMoviesCount] = await Promise.all([
+ const [deletedScenesCount, deletedMoviesCount, deletedSeriesCount] = await Promise.all([
deleteScenes(sceneIds),
deleteMovies(movieIds),
+ deleteSeries(serieIds),
]);
- logger.info(`Removed ${deletedScenesCount} scenes and ${deletedMoviesCount} movies for ${entitySlugs}`);
+ logger.info(`Removed ${deletedScenesCount} scenes, ${deletedMoviesCount} movies and ${deletedSeriesCount} series for ${entitySlugs}`);
await flushOrphanedMedia();
}
diff --git a/src/media.js b/src/media.js
index 23e4b4e6..eaf8e6f0 100644
--- a/src/media.js
+++ b/src/media.js
@@ -7,7 +7,7 @@ const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const stream = require('stream');
-const nanoid = require('nanoid/non-secure');
+const { nanoid } = require('nanoid/non-secure');
const mime = require('mime');
// const fileType = require('file-type');
const ffmpeg = require('fluent-ffmpeg');
@@ -345,12 +345,13 @@ async function writeImage(image, media, info, filepath, isProcessed) {
return;
}
- if (isProcessed) {
- // convert to JPEG and write to permanent location
- await image
- .jpeg()
- .toFile(path.join(config.media.path, filepath));
- }
+ await image
+ .resize({
+ height: config.media.maxSize,
+ withoutEnlargement: true,
+ })
+ .jpeg({ quality: config.media.quality })
+ .toFile(path.join(config.media.path, filepath));
}
async function writeThumbnail(image, thumbpath) {
@@ -377,6 +378,7 @@ async function writeLazy(image, lazypath) {
async function storeImageFile(media, hashDir, hashSubDir, filename, filedir, filepath, options) {
logger.silly(`Storing permanent media files for ${media.id} from ${media.src} at ${filepath}`);
+ logger.debug(`Memory usage at image storage: ${process.memoryUsage.rss() / 1000000} MB (${media.src})`);
try {
const thumbdir = config.s3.enabled ? path.join(media.role, 'thumbs') : path.join(media.role, 'thumbs', hashDir, hashSubDir);
@@ -415,12 +417,14 @@ async function storeImageFile(media, hashDir, hashSubDir, filename, filedir, fil
});
}
+ await writeImage(image, media, info, filepath, isProcessed);
+
await Promise.all([
- writeImage(image, media, info, filepath, isProcessed),
writeThumbnail(image, thumbpath),
writeLazy(image, lazypath),
]);
+ /*
if (isProcessed) {
// file already stored, remove temporary file
await fsPromises.unlink(media.file.path);
@@ -428,6 +432,9 @@ async function storeImageFile(media, hashDir, hashSubDir, filename, filedir, fil
// image not processed, simply move temporary file to final location
await fsPromises.rename(media.file.path, path.join(config.media.path, filepath));
}
+ */
+
+ await fsPromises.unlink(media.file.path);
if (config.s3.enabled) {
await Promise.all([
@@ -580,6 +587,7 @@ async function fetchSource(source, baseMedia) {
const maxAttempts = source.attempts || 3;
logger.silly(`Fetching media from ${source.src}`);
+ logger.debug(`Memory usage before media fetch: ${process.memoryUsage.rss() / 1000000} MB (${source.src})`);
async function attempt(attempts = 1) {
const hasher = new blake2.Hash('blake2b', { digestLength: 24 });
@@ -746,7 +754,8 @@ async function storeMedias(baseMedias, options) {
const fetchedMedias = await Promise.map(
baseMedias,
async (baseMedia) => fetchMedia(baseMedia, { existingSourceMediaByUrl, existingExtractMediaByUrl }),
- { concurrency: 100 }, // don't overload disk (or network, although this has its own throttling)
+ // { concurrency: 100 }, // don't overload disk (or network, although this has its own throttling)
+ { concurrency: 10 }, // don't overload disk (or network, although this has its own throttling)
);
const { uniqueHashMedias, existingHashMedias } = await findHashDuplicates(fetchedMedias);
@@ -823,11 +832,12 @@ async function associateReleaseMedia(releases, type = 'release') {
.reduce((acc, [releaseId, releaseBaseMedias]) => {
releaseBaseMedias.forEach((baseMedia) => {
const media = storedMediasById[baseMedia.id];
+ const mediaId = media?.use || media?.entry?.id;
- if (media) {
+ if (mediaId) {
acc.push({
[`${type}_id`]: releaseId,
- media_id: media.use || media.entry.id,
+ media_id: mediaId,
});
}
});
@@ -840,7 +850,10 @@ async function associateReleaseMedia(releases, type = 'release') {
await bulkInsert(`${type}s_${role}`, associations, false);
}
} catch (error) {
- logger.error(util.inspect(error.entries, null, null, { color: true }));
+ if (error.entries) {
+ logger.error(util.inspect(error.entries, null, null, { color: true }));
+ }
+
logger.error(`Failed to store ${type} ${role}: ${error.message}`);
}
}, Promise.resolve());
@@ -948,9 +961,12 @@ async function flushOrphanedMedia() {
await deleteS3Objects(orphanedMedia.filter((media) => media.is_s3));
}
- await fsPromises.rmdir(path.join(config.media.path, 'temp'), { recursive: true });
-
- logger.info('Cleared temporary media directory');
+ try {
+ await fsPromises.rm(path.join(config.media.path, 'temp'), { recursive: true });
+ logger.info('Cleared temporary media directory');
+ } catch (error) {
+ logger.warn(`Failed to clear temporary media directory: ${error.message}`);
+ }
}
module.exports = {
diff --git a/src/releases.js b/src/releases.js
index ae2a6a52..29b4ece1 100644
--- a/src/releases.js
+++ b/src/releases.js
@@ -59,7 +59,7 @@ const releaseFields = `
slug
}
}
- poster: chaptersPosterByChapterId {
+ poster: chaptersPoster {
media {
id
path
@@ -82,7 +82,7 @@ const releaseFields = `
}
}
}
- poster: releasesPosterByReleaseId {
+ poster: releasesPoster {
media {
id
path
@@ -104,7 +104,7 @@ const releaseFields = `
size
}
}
- trailer: releasesTrailerByReleaseId @include (if: $full) {
+ trailer: releasesTrailer @include (if: $full) {
media {
id
path
@@ -325,6 +325,24 @@ async function deleteMovies(movieIds) {
return deleteCount;
}
+async function deleteSeries(serieIds) {
+ if (serieIds.length === 0) {
+ return 0;
+ }
+
+ await knex('series_scenes')
+ .whereIn('serie_id', serieIds)
+ .delete();
+
+ const deleteCount = await knex('series')
+ .whereIn('id', serieIds)
+ .delete();
+
+ logger.info(`Removed ${deleteCount}/${serieIds.length} series`);
+
+ return deleteCount;
+}
+
async function flushScenes() {
const sceneIds = await knex('releases').select('id').pluck('id');
@@ -367,6 +385,27 @@ async function flushMovies() {
logger.info(`Removed ${deleteCount}/${movieIds.length} movies`);
}
+async function flushSeries() {
+ const serieIds = await knex('series').select('id').pluck('id');
+
+ const confirmed = await inquirer.prompt([{
+ type: 'confirm',
+ name: 'flushSeries',
+ message: `You are about to remove ${serieIds.length} series. Are you sure?`,
+ default: false,
+ }]);
+
+ if (!confirmed.flushSeries) {
+ logger.warn('Confirmation rejected, not flushing series');
+ return;
+ }
+ const deleteCount = await deleteSeries(serieIds);
+
+ await flushOrphanedMedia();
+
+ logger.info(`Removed ${deleteCount}/${serieIds.length} series`);
+}
+
async function flushBatches(batchIds) {
const [sceneIds, movieIds] = await Promise.all([
knex('releases')
@@ -407,8 +446,10 @@ module.exports = {
fetchScenes,
flushBatches,
flushMovies,
+ flushSeries,
flushScenes,
searchScenes,
deleteScenes,
deleteMovies,
+ deleteSeries,
};
diff --git a/src/scrapers/adulttime.js b/src/scrapers/adulttime.js
index e0e9241c..158592b9 100644
--- a/src/scrapers/adulttime.js
+++ b/src/scrapers/adulttime.js
@@ -1,20 +1,20 @@
'use strict';
-const { fetchApiLatest, fetchApiUpcoming, fetchScene, fetchApiProfile } = require('./gamma');
+const { fetchApiLatest, fetchApiUpcoming, fetchSceneApi, fetchApiProfile } = require('./gamma');
function curateRelease(release, site) {
if (['bubblegumdungeon', 'ladygonzo'].includes(site.slug)) {
return {
...release,
- title: release.title.split(/:|\|/)[1].trim(),
+ title: release.title.split(/:|\|/)[1]?.trim(),
};
}
return release;
}
-async function networkFetchScene(url, site, release) {
- const scene = await fetchScene(url, site, release);
+async function networkFetchScene(url, site, release, options) {
+ const scene = await fetchSceneApi(url, site, release, options);
return curateRelease(scene, site);
}
diff --git a/src/scrapers/legalporno.js b/src/scrapers/analvids.js
similarity index 95%
rename from src/scrapers/legalporno.js
rename to src/scrapers/analvids.js
index 7d2b1baf..efe6d1b2 100644
--- a/src/scrapers/legalporno.js
+++ b/src/scrapers/analvids.js
@@ -34,13 +34,13 @@ function getPoster(posterElement, sceneId) {
if (typeof posterTimeRange === 'number') {
// poster time is already a single time value
- return `https://legalporno.com/casting/${sceneId}/${posterTimeRange}`;
+ return `https://analvids.com/casting/${sceneId}/${posterTimeRange}`;
}
const [max, min] = posterTimeRange.split('-');
const posterTime = Math.floor(Math.random() * (Number(max) - Number(min) + 1) + Number(min));
- return `https://legalporno.com/casting/${sceneId}/${posterTime}`;
+ return `https://analvids.com/casting/${sceneId}/${posterTime}`;
}
function scrapeAll(html) {
@@ -134,7 +134,7 @@ async function scrapeScene(html, url, site, useGallery) {
}
const studioName = $('.watchpage-studioname').first().text().trim();
- release.studio = slugify(studioName, '');
+ release.studio = slugify(studioName, '', { removePunctuation: true });
return release;
}
@@ -181,7 +181,7 @@ async function fetchScene(url, site) {
}
async function fetchProfile({ name: actorName }) {
- const res = await http.get(`https://www.legalporno.com/api/autocomplete/search?q=${actorName.replace(' ', '+')}`);
+ const res = await http.get(`https://www.analvids.com/api/autocomplete/search?q=${actorName.replace(' ', '+')}`);
const data = res.body;
const result = data.terms.find((item) => item.type === 'model');
diff --git a/src/scrapers/bang.js b/src/scrapers/bang.js
index 7fd2d75c..47f1dff3 100644
--- a/src/scrapers/bang.js
+++ b/src/scrapers/bang.js
@@ -5,6 +5,7 @@ const qu = require('../utils/qu');
const { extractDate } = require('../utils/qu');
const { inchesToCm } = require('../utils/convert');
const slugify = require('../utils/slugify');
+const capitalize = require('../utils/capitalize');
const clusterId = '617fb597b659459bafe6472470d9073a';
const authKey = 'YmFuZy1yZWFkOktqVDN0RzJacmQ1TFNRazI=';
@@ -15,6 +16,10 @@ const genderMap = {
};
function getScreenUrl(item, scene) {
+ if (!scene.dvd?.id || !item?.screenId) {
+ return null;
+ }
+
return `https://i.bang.com/screenshots/${scene.dvd.id}/${scene.type}/${scene.order}/${item.screenId}.jpg`;
}
@@ -57,7 +62,7 @@ async function fetchPhotos(scene) {
async function scrapeScene(scene, entity, options) {
const release = {
entryId: scene.id,
- title: scene.name,
+ title: scene.name || (scene.dvd?.name && scene.type === 'bonus' && capitalize(`${scene.dvd.name} - Bonus Scene ${scene.order || 1}`)) || null,
description: scene.description,
tags: scene.genres.concat(scene.actions).map((genre) => genre.name),
duration: scene.duration,
@@ -91,7 +96,7 @@ async function scrapeScene(scene, entity, options) {
}
}
- release.trailer = `https://i.bang.com/v/${scene.dvd.id}/${scene.identifier}/preview.mp4`;
+ release.teaser = `https://i.bang.com/v/${scene.dvd.id}/${scene.identifier}/preview.mp4`;
release.channel = scene.series.name
.replace(/[! .]/g, '')
@@ -352,6 +357,11 @@ async function fetchUpcoming(site, page = 1) {
}
async function fetchScene(url, entity, baseRelease, options) {
+ if (baseRelease?.entryId) {
+ // overview and deep data is the same, don't hit server unnecessarily
+ return baseRelease;
+ }
+
const encodedId = new URL(url).pathname.split('/')[2];
const entryId = decodeId(encodedId);
diff --git a/src/scrapers/bangbros.js b/src/scrapers/bangbros.js
index 01471286..30cf8b6f 100644
--- a/src/scrapers/bangbros.js
+++ b/src/scrapers/bangbros.js
@@ -8,6 +8,7 @@ const logger = require('../logger')(__filename);
const slugify = require('../utils/slugify');
const http = require('../utils/http');
const qu = require('../utils/qu');
+const args = require('../argv');
function scrape(html, site) {
const $ = cheerio.load(html, { normalizeWhitespace: true });
@@ -43,7 +44,7 @@ function scrape(html, site) {
});
}
-function scrapeLegacy(scenes, site) {
+function scrapeAllLegacy(scenes, site) {
return scenes.map(({ query }) => {
const release = {};
@@ -63,6 +64,38 @@ function scrapeLegacy(scenes, site) {
});
}
+function scrapeAllMembers(scenes, _channel) {
+ return scenes.map(({ query, el }) => {
+ const release = {};
+ const data = JSON.parse(query.q(el, null, 'data-shoot'));
+
+ release.entryId = data?.id || query.url('a.etLnk')?.match(/\d+$/)?.[0];
+ release.shootId = data?.code;
+ release.url = data.url ? qu.prefixUrl(data.url, 'https://members.bangbros.com') : query.url('a.etLnk');
+
+ release.title = data?.title || query.cnt('.etl-hdd');
+ release.description = data?.description || query.cnt('.etl-desc');
+
+ release.date = query.date('.etl-dt', 'MMM DD, YYYY', /\w{3} \d{1,2}, \d{4}/);
+ release.actors = data?.model.map((actor) => ({
+ name: actor.name,
+ url: qu.prefixUrl(actor.url, 'https://members.bangbros.com'),
+ }));
+
+ const rolloverUrl = query.q('.rollover-image', 'data-rollover-url');
+ release.poster = data?.image || query.img('.rollover-image', 'data-initial-image-url');
+
+ if (rolloverUrl) {
+ release.photos = Array.from({ length: 15 }, (value, index) => `${rolloverUrl}${index + 1}.jpg`);
+ }
+
+ release.trailer = data?.trailer;
+ release.tags = data?.tag.map((tag) => tag.name);
+
+ return release;
+ });
+}
+
/* no dates available, breaks database
function scrapeUpcoming(html, site) {
const { document } = ex(html);
@@ -147,6 +180,30 @@ function scrapeSceneLegacy({ query }, url) {
return release;
}
+function scrapeSceneMembers({ query }, url) {
+ const release = {};
+
+ release.entryId = new URL(url).pathname.match(/(\d+)\/?$/)[1];
+ release.shootId = query.img('.player img')?.match(/\/shoots\/(\w+)\//)?.[1];
+
+ release.title = query.cnt('.vdo-hdd1');
+ release.description = query.cnt('.ndcp');
+
+ release.actors = query.all('.vdsc a[href*="/model"]').map((actorEl) => ({
+ name: query.cnt(actorEl, 'span'),
+ url: query.url(actorEl, null, 'href', { origin: 'https://members.bangbros.com' }),
+ avatar: query.img(actorEl, 'img'),
+ }));
+
+ release.date = query.date('.ran:nth-child(2)', 'MMM DD, YYYY', /\w{3} \d{1,2}, \d{4}/);
+ release.duration = query.duration('.ran:nth-child(3)');
+
+ release.tags = query.cnts('.tag a[href*="/tags"]');
+ release.channel = slugify(query.cnt('.tag a[href*="/site"]'), '');
+
+ return release;
+}
+
function scrapeProfile(html, scope) {
const { query } = qu.ex(html);
const profile = {};
@@ -167,17 +224,6 @@ function scrapeProfileSearch(html, actorName) {
}
async function fetchLatest(site, page = 1) {
- if (site.parameters?.legacy) {
- const url = `${site.parameters?.latest || site.url}/videos/${page}`;
- const res = await qu.getAll(url, '.videoList');
-
- if (res.ok) {
- return scrapeLegacy(res.items, site);
- }
-
- return res.status;
- }
-
const res = await qu.get(`${site.parameters?.latest || site.url}/${page}`);
if (res.ok) {
@@ -187,6 +233,39 @@ async function fetchLatest(site, page = 1) {
return res.status;
}
+async function fetchLatestMembers(channel, page = 1, { parameters }) {
+ if (!parameters.product) {
+ throw new Error(`No member area product ID known for '${channel.name}'`);
+ }
+
+ if (!args.cookie) {
+ throw new Error(`Please specifiy --cookie "PHPSESSID=xxx" to access the '${channel.name}' members area.`);
+ }
+
+ const url = `https://members.bangbros.com/product/${parameters.product}/videos/latest/${page}`;
+
+ const res = await qu.getAll(url, '.thumbHolder .echThumb', {
+ cookie: args.cookie,
+ });
+
+ if (res.ok) {
+ return scrapeAllMembers(res.items, channel);
+ }
+
+ return res.status;
+}
+
+async function fetchLatestLegacy(site, page = 1) {
+ const url = `${site.parameters?.latest || site.url}/videos/${page}`;
+ const res = await qu.getAll(url, '.videoList');
+
+ if (res.ok) {
+ return scrapeAllLegacy(res.items, site);
+ }
+
+ return res.status;
+}
+
/*
async function fetchUpcoming(site) {
const res = await http.get('https://www.bangbros.com');
@@ -218,6 +297,26 @@ async function fetchScene(url, site, release) {
return scrapeScene(res.item.html, url, site);
}
+async function fetchSceneMembers(url, baseRelease, channel, { parameters }) {
+ if (!parameters.product) {
+ throw new Error(`No member area product ID known for '${channel.name}'`);
+ }
+
+ if (!args.cookie) {
+ throw new Error(`Please specifiy --cookie "PHPSESSID=xxx" to access the '${channel.name}' members area.`);
+ }
+
+ const res = await qu.get(url, null, {
+ cookie: args.cookie,
+ });
+
+ if (res.ok) {
+ return scrapeSceneMembers(res.item, url, channel);
+ }
+
+ return res.status;
+}
+
async function fetchProfile({ name: actorName }, scope) {
const actorSlug = slugify(actorName);
const url = `https://bangbros.com/search/${actorSlug}`;
@@ -242,5 +341,12 @@ module.exports = {
fetchLatest,
fetchScene,
fetchProfile,
+ legacy: {
+ fetchLatest: fetchLatestLegacy,
+ },
+ members: {
+ fetchLatest: fetchLatestMembers,
+ fetchScene: fetchSceneMembers,
+ },
// fetchUpcoming, no dates available
};
diff --git a/src/scrapers/gamma.js b/src/scrapers/gamma.js
index 73202b7d..7d586d42 100644
--- a/src/scrapers/gamma.js
+++ b/src/scrapers/gamma.js
@@ -3,7 +3,6 @@
const Promise = require('bluebird');
const util = require('util');
const { JSDOM } = require('jsdom');
-const cheerio = require('cheerio');
const moment = require('moment');
const format = require('template-format');
@@ -25,6 +24,19 @@ function getApiUrl(appId, apiKey) {
};
}
+function getAvatarFallbacks(avatar) {
+ if (!avatar) {
+ return null;
+ }
+
+ return [
+ avatar.replace(/\d+x\d+/, '500x750'),
+ avatar.replace(/\d+x\d+/, '240x360'),
+ avatar.replace(/\d+x\d+/, '200x300'),
+ avatar,
+ ];
+}
+
async function fetchApiCredentials(referer, site) {
if (site?.parameters?.appId && site?.parameters?.apiKey) {
return getApiUrl(site.parameters.appId, site.parameters.apiKey);
@@ -62,21 +74,19 @@ function getAlbumUrl(albumPath, site) {
}
async function fetchPhotos(url) {
- const res = await http.get(url);
+ const res = await qu.get(url);
- return res.body.toString();
+ return res.item;
}
-function scrapePhotos(html, includeThumbnails = true) {
- const $ = cheerio.load(html, { normalizeWhitespace: true });
-
- return $('.preview .imgLink, .pgFooterThumb a').toArray().map((linkEl) => {
- const url = $(linkEl).attr('href');
+function scrapePhotos({ query }, includeThumbnails = true) {
+ return query.all('.preview .imgLink, .pgFooterThumb a').map((linkEl) => {
+ const url = linkEl.href;
if (/\/join|\/createaccount/.test(url)) {
// URL links to join page instead of full photo, extract thumbnail
// /createaccount is used by e.g. Tricky Spa native site
- const src = $(linkEl).find('img').attr('src');
+ const src = query.img(linkEl);
if (/previews\//.test(src)) {
// resource often serves full photo at a modifier URL anyway, add as primary source
@@ -106,20 +116,18 @@ async function getPhotos(albumPath, site, includeThumbnails = true) {
}
try {
- const html = await fetchPhotos(albumUrl);
- const $ = cheerio.load(html, { normalizeWhitespace: true });
- const photos = scrapePhotos(html, includeThumbnails);
+ const item = await fetchPhotos(albumUrl);
+ const photos = scrapePhotos(item, includeThumbnails);
- const lastPage = $('.Gamma_Paginator a.last').attr('href')?.match(/\d+$/)[0];
+ const lastPage = item.query.url('.Gamma_Paginator a.last')?.match(/\d+$/)[0];
if (lastPage) {
const otherPages = Array.from({ length: Number(lastPage) }, (_value, index) => index + 1).slice(1);
const otherPhotos = await Promise.map(otherPages, async (page) => {
- const pageUrl = `${albumUrl}/${page}`;
- const pageHtml = await fetchPhotos(pageUrl);
+ const pageItem = await fetchPhotos(`${albumUrl}/${page}`);
- return scrapePhotos(pageHtml, includeThumbnails);
+ return scrapePhotos(pageItem, includeThumbnails);
}, {
concurrency: 2,
});
@@ -169,10 +177,15 @@ async function getThumbs(entryId, site, parameters) {
});
if (res.ok && res.body.results?.[0]?.hits[0]?.set_pictures) {
- return res.body.results[0].hits[0].set_pictures.map((img) => ([
- `https://transform.gammacdn.com/photo_set${img.thumb_path}`,
+ return res.body.results[0].hits[0].set_pictures.map((img) => img.thumb_path && ([
+ `https://images-fame.gammacdn.com/photo_set${img.thumb_path}`,
+ `https://images01-fame.gammacdn.com/photo_set${img.thumb_path}`,
+ `https://images02-fame.gammacdn.com/photo_set${img.thumb_path}`,
+ `https://images03-fame.gammacdn.com/photo_set${img.thumb_path}`,
+ `https://images04-fame.gammacdn.com/photo_set${img.thumb_path}`,
`https://images-evilangel.gammacdn.com/photo_set${img.thumb_path}`,
- ]));
+ `https://transform.gammacdn.com/photo_set${img.thumb_path}`,
+ ])).filter(Boolean);
}
return [];
@@ -187,6 +200,18 @@ async function getPhotosApi(entryId, site, parameters) {
return photos.concat(thumbs.slice(photos.length));
}
+function getImageSources(source) {
+ return [
+ `https://images-fame.gammacdn.com/movies${source}`,
+ `https://images01-fame.gammacdn.com/movies${source}`,
+ `https://images02-fame.gammacdn.com/movies${source}`,
+ `https://images03-fame.gammacdn.com/movies${source}`,
+ `https://images04-fame.gammacdn.com/movies${source}`,
+ `https://images-evilangel.gammacdn.com/movies${source}`,
+ `https://transform.gammacdn.com/movies${source}`,
+ ];
+}
+
async function scrapeApiReleases(json, site) {
return json.map((scene) => {
if (site.parameters?.extract && scene.sitename !== site.parameters.extract) {
@@ -225,9 +250,17 @@ async function scrapeApiReleases(json, site) {
],
}));
+ /* master categories include e.g. 'transgender' for non-trans Wicked scenes
release.tags = scene.master_categories
.concat(scene.categories?.map((category) => category.name))
.filter(Boolean); // some categories don't have a name
+ */
+
+ release.tags = scene.categories?.map((category) => category.name).filter(Boolean); // some categories don't have a name
+
+ if (scene.availableOnSite.length > 1) {
+ release.comment = `Also available on ${scene.availableOnSite.filter((sisterSite) => sisterSite !== site.slug).join(', ')}`;
+ }
const posterPath = scene.pictures.resized || (scene.pictures.nsfw?.top && Object.values(scene.pictures.nsfw.top)[0]);
@@ -244,41 +277,21 @@ async function scrapeApiReleases(json, site) {
}).filter(Boolean);
}
-function scrapeAll(html, site, networkUrl, hasTeaser = true) {
- const $ = cheerio.load(html, { normalizeWhitespace: true });
- const scenesElements = $('li[data-itemtype=scene], div[data-itemtype=scenes]').toArray();
-
- return scenesElements.map((element) => {
+function scrapeAll(scenes, site, networkUrl, hasTeaser = true) {
+ return scenes.map(({ query, el }) => {
const release = {};
- const sceneLinkElement = $(element).find('.sceneTitle a, .tlcTitle a');
+ release.url = query.url('.sceneTitle a, .tlcTitle a', 'href', { origin: networkUrl ? site.parent.url : site.url });
- if (site) release.url = `${networkUrl ? site.parent.url : site.url}${sceneLinkElement.attr('href')}`;
- else release.url = `${networkUrl}${sceneLinkElement.attr('href')}`;
+ release.title = query.cnt('.sceneTitle a', 'tlcTitle a', 'title');
+ release.entryId = el.dataset.itemid;
- release.title = sceneLinkElement.attr('title');
- release.entryId = $(element).attr('data-itemid');
+ release.date = query.date('.sceneDate, .tlcSpecsDate .tlcDetailsValue', ['MM-DD-YYYY', 'YYYY-MM-DD']);
+ release.actors = query.cnts('.sceneActors a, .tlcActors a', ' title');
- const dateEl = $(element).find('.sceneDate, .tlcSpecsDate .tlcDetailsValue').text() || null;
- if (dateEl) {
- release.date = moment
- .utc(dateEl, ['MM-DD-YYYY', 'YYYY-MM-DD'])
- .toDate();
- }
+ [release.likes, release.dislikes] = query.all('.value').map((likeEl) => query.number(likeEl));
- release.actors = $(element).find('.sceneActors a, .tlcActors a')
- .map((actorIndex, actorElement) => $(actorElement).attr('title'))
- .toArray();
-
- [release.likes, release.dislikes] = $(element).find('.value')
- .toArray()
- .map((value) => Number($(value).text()));
-
- const posterEl = $(element).find('.imgLink img, .tlcImageItem');
- if (posterEl) release.poster = posterEl.attr('data-original') || posterEl.attr('src');
-
- const channelEl = $(element).find('.fromSite a');
- if (channelEl.attr('title')) release.channel = channelEl.attr('title').replace('.com', '');
+ release.poster = query.img('.imgLink img, .tlcImageItem', 'data-original') || query.img('.imgLink img, .tlcImageItem');
if (hasTeaser) {
release.teaser = [
@@ -287,76 +300,66 @@ function scrapeAll(html, site, networkUrl, hasTeaser = true) {
];
}
+ release.channel = query.el('.fromSite a', 'title')?.replace('.com', '');
+
return release;
});
}
-async function scrapeScene(html, url, site, baseRelease, mobileHtml, options) {
- const $ = cheerio.load(html, { normalizeWhitespace: true });
- const m$ = mobileHtml && cheerio.load(mobileHtml, { normalizeWhitespace: true });
- const release = { $, url };
+async function scrapeScene({ query }, url, channel, baseRelease, mobileItem, options) {
+ const release = { query }; // used by XEmpire scraper to resolve channel-specific details
- const json = $('script[type="application/ld+json"]').html();
- const videoJson = $('script:contains("window.ScenePlayerOptions")').html();
+ const json = query.html('script[type="application/ld+json"]');
+ const videoJson = query.htmls('script').find((script) => /ScenePlayerOptions/i.test(script));
const [data, data2] = json ? JSON.parse(json) : [];
const videoData = videoJson && JSON.parse(videoJson.slice(videoJson.indexOf('{'), videoJson.indexOf('};') + 1));
release.entryId = (baseRelease?.path || new URL(url).pathname).match(/\/(\d{2,})(\/|$)/)?.[1];
release.title = videoData?.playerOptions?.sceneInfos.sceneTitle || data?.name;
+ release.description = data?.description;
- // date in data object is not the release date of the scene, but the date the entry was added; only use as fallback
- const dateString = $('.updatedDate').first().text().trim();
- const dateMatch = dateString.match(/\d{2,4}[-/]\d{2}[-/]\d{2,4}/)?.[0];
+ release.date = query.date('.updatedDate', ['MM-DD-YYYY', 'YYYY-MM-DD'])
+ || qu.extractDate(data?.dateCreated, 'YYYY-MM-DD')
+ || videoData?.playerOptions?.sceneInfos.sceneReleaseDate;
- if (dateMatch) release.date = moment.utc(dateMatch, ['MM-DD-YYYY', 'YYYY-MM-DD']).toDate();
- else if (data?.dateCreated) release.date = moment.utc(data.dateCreated, 'YYYY-MM-DD').toDate();
- else release.date = videoData?.playerOptions?.sceneInfos.sceneReleaseDate;
+ release.actors = (data?.actor || data2?.actor)?.map((actor) => ({
+ name: actor.name,
+ gender: actor.gender,
+ })) || [];
- if (data) {
- release.description = data.description;
- if (data.director?.[0]?.name) release.director = data.director[0].name;
- else if (data2?.director?.[0]?.name) release.director = data2.director[0].name;
+ release.duration = qu.durationToSeconds(data.duration);
+ release.director = data?.director?.[0]?.name || data2?.director?.[0]?.name;
- const stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5;
- if (stars) release.rating = { stars };
+ release.tags = data?.keywords?.split(', ') || data2?.keywords?.split(', ') || [];
+ release.stars = (data.aggregateRating.ratingValue / data.aggregateRating.bestRating) * 5 || null;
- release.duration = moment.duration(data.duration.slice(2)).asSeconds();
+ release.channel = slugify(data?.productionCompany?.name
+ || query.el('.studioLink a, .siteLink a', 'title')
+ || query.cnt('.siteNameSpan')?.toLowerCase().replace('.com', '')
+ || query.meta('meta[name="twitter:domain"]')?.replace('.com', ''), '');
+
+ if (videoData?.picPreview && new URL(videoData.picPreview).pathname.length > 1) {
+ // sometimes links to just https://images02-fame.gammacdn.com/
+ const poster = new URL(videoData.picPreview);
+
+ release.poster = [
+ videoData.picPreview, // prefer original URL with width and height parameters, without may give a square crop on e.g. XEmpire
+ `${poster.origin}${poster.pathname}`,
+ ];
}
- const actors = data?.actor || data2?.actor;
-
- if (actors) {
- release.actors = actors.map((actor) => ({
- name: actor.name,
- gender: actor.gender,
- }));
- }
-
- const hasTrans = release.actors?.some((actor) => actor.gender === 'shemale');
- const rawTags = data?.keywords?.split(', ') || data2?.keywords?.split(', ') || [];
- release.tags = hasTrans ? [...rawTags, 'transsexual'] : rawTags;
-
- const channel = data?.productionCompany?.name
- || $('.studioLink a, .siteLink a').attr('title')?.trim()
- || $('.siteNameSpan').text()
- ?.trim()
- .toLowerCase()
- .replace('.com', '')
- || $('meta[name="twitter:domain"]').attr('content')?.replace('.com', '');
-
- if (channel) release.channel = slugify(channel, '');
-
- if (videoData?.picPreview && new URL(videoData.picPreview).pathname.length > 1) release.poster = videoData.picPreview; // sometimes links to just https://images02-fame.gammacdn.com/
-
- const photoLink = $('.picturesItem a').attr('href');
- const mobilePhotos = m$ ? m$('.preview-displayer a img').map((photoIndex, photoEl) => $(photoEl).attr('src')).toArray() : [];
+ const photoLink = query.url('.picturesItem a');
+ const mobilePhotos = mobileItem?.query.imgs('.preview-displayer a img') || [];
if (photoLink && options.includePhotos) {
- const photos = await getPhotos(photoLink, site, mobilePhotos.length < 3); // only get thumbnails when less than 3 mobile photos are available
+ const photos = await getPhotos(photoLink, channel, mobilePhotos.length < 3); // only get thumbnails when less than 3 mobile photos are available
- if (photos.length < 7) release.photos = [...photos, ...mobilePhotos]; // probably only teaser photos available, supplement with mobile album
- else release.photos = photos;
+ if (photos.length < 7) {
+ release.photos = [...photos, ...mobilePhotos]; // probably only teaser photos available, supplement with mobile album
+ } else {
+ release.photos = photos;
+ }
} else {
release.photos = mobilePhotos;
}
@@ -397,28 +400,28 @@ async function scrapeScene(html, url, site, baseRelease, mobileHtml, options) {
];
}
- const movie = $('.dvdLink');
- const movieUrl = qu.prefixUrl(movie.attr('href'), site.url);
+ const movieUrl = query.url('.dvdLink', 'href', { origin: channel.url });
if (movieUrl) {
release.movie = {
url: movieUrl,
- title: movie.attr('title'),
+ title: query.el('.dvdLink', 'title'),
entryId: movieUrl.match(/\/(\d+)(\/|$)/)?.[1],
- covers: [movie.find('img').attr('src')],
+ covers: [qu.imgs('.dvdLink img')],
};
}
return release;
}
-async function scrapeSceneApi(data, site, options) {
+async function scrapeReleaseApi(data, site, options) {
const release = {};
- release.entryId = data.clip_id;
+ release.entryId = data.clip_id || data.movie_id;
release.title = data.title;
release.duration = data.length;
- release.date = new Date(data.date * 1000) || qu.parseDate(data.release_date, 'YYYY-MM-DD');
+ release.date = (data.date && new Date(data.date * 1000)) || qu.parseDate(data.release_date || data.last_modified, 'YYYY-MM-DD');
+ release.director = data.directors[0]?.name || null;
release.actors = data.actors.map((actor) => ({
entryId: actor.actor_id,
@@ -433,10 +436,9 @@ async function scrapeSceneApi(data, site, options) {
if (data.pictures) {
release.poster = [
- `https://transform.gammacdn.com/movies${data.pictures['1920x1080']}`,
- `https://images-evilangel.gammacdn.com/movies${data.pictures['1920x1080']}`,
- `https://transform.gammacdn.com/movies${data.pictures.resized}`,
- `https://images-evilangel.gammacdn.com/movies${data.pictures.resized}`,
+ ...(data.pictures['1920x1080'] ? getImageSources(data.pictures['1920x1080']) : []),
+ ...(data.pictures.resized ? getImageSources(data.pictures.resized) : []),
+ ...(data.pictures['960x544'] ? getImageSources(data.pictures['960x544']) : []),
];
}
@@ -444,15 +446,22 @@ async function scrapeSceneApi(data, site, options) {
release.photos = await getPhotosApi(data.photoset_id, site, options.parameters);
}
+ if (data.cover_path) {
+ release.covers = [
+ getImageSources(`${data.cover_path}_front_400x625.jpg?width=450&height=636&format=webp`),
+ getImageSources(`${data.cover_path}_back_400x625.jpg?width=450&height=636&format=webp`),
+ ];
+ }
+
if (data.trailers) {
release.trailer = Object.entries(data.trailers).map(([quality, source]) => ({ src: source, quality }));
}
- if (data.movie_id) {
+ if (data.movie_id && !data.movie_path) {
release.movie = {
entryId: data.movie_id,
title: data.movie_title,
- url: qu.prefixUrl(`/en/movie/${data.url_movie_title}/${data.movie_id}`, site.url),
+ url: qu.prefixUrl(`${data.url_movie_title}/${data.movie_id}`, options.parameters.movie ? options.parameters.movie : `${site.url}/en/movie`),
};
}
@@ -484,11 +493,16 @@ async function fetchMovieTrailer(release) {
return null;
}
-async function scrapeMovie({ query, html }, window, url, entity, options) {
+async function scrapeMovie({ query, el }, url, entity, baseRelease, options) {
const release = {};
- const data = window.dataLayer[0]?.dvdDetails;
- // const options = html.match(/options = {.*};/);
+ const { dataLayer } = query.exec('//script[contains(text(), "dataLayer")]', ['dataLayer']);
+ const rawData = dataLayer?.[0]?.dvdDetails;
+ const data = rawData?.dvdId && rawData; // dvdDetails is mostly empty in some cache states
+
+ if (query.exists('.NotFound-Title')) {
+ return null;
+ }
release.entryId = new URL(url).pathname.match(/\/(\d+)(\/|$)/)?.[1];
@@ -498,13 +512,20 @@ async function scrapeMovie({ query, html }, window, url, entity, options) {
];
release.description = query.cnt('.descriptionText');
- release.date = qu.extractDate(data.dvdReleaseDate);
- release.title = data.dvdName;
+ release.date = qu.extractDate(data?.dvdReleaseDate) || query.date('.updatedOn', 'YYYY-MM-DD');
+ release.title = data?.dvdName || query.cnt('.dvdTitle');
+ release.director = query.el('.directedBy a', 'title');
+
+ release.actors = data?.dvdActors.map((actor) => ({ name: actor.actorName, entryId: actor.actorId }))
+ || query.all('.actorCarousel a[href*="/pornstar"]').map((actorEl) => ({
+ entryId: query.url(actorEl, null).match(/\/(\d+)/)?.[1],
+ name: query.cnt(actorEl, 'span'),
+ href: query.url(actorEl, null, 'href', { origin: entity.url }),
+ avatar: getAvatarFallbacks(query.img(actorEl)),
+ }));
- release.actors = data.dvdActors.map((actor) => ({ name: actor.actorName, entryId: actor.actorId }));
release.tags = query.cnts('.dvdCol a');
-
- release.scenes = scrapeAll(html, entity, entity.url);
+ release.scenes = scrapeAll(qu.initAll(el, 'div[data-itemtype*=scene], li[data-itemtype*=scene]'), entity, entity.url);
if (options.includeTrailers) {
release.trailer = await fetchMovieTrailer(release);
@@ -547,10 +568,8 @@ async function fetchActorReleases(profileUrl, getActorReleasesUrl, page = 1, acc
return accReleases.concat(releases);
}
-async function scrapeProfile(html, url, actorName, _siteSlug, getActorReleasesUrl, withReleases, context) {
- const { query } = qu.extract(html);
-
- const avatar = query.el('img.actorPicture');
+async function scrapeProfile({ query }, url, actorName, _siteSlug, getActorReleasesUrl, withReleases, context) {
+ const avatar = query.img('img.actorPicture');
const hair = query.cnt('.actorProfile .attribute_hair_color');
const height = query.cnt('.actorProfile .attribute_height');
const weight = query.cnt('.actorProfile .attribute_weight');
@@ -563,12 +582,7 @@ async function scrapeProfile(html, url, actorName, _siteSlug, getActorReleasesUr
if (avatar) {
// larger sizes usually available, provide fallbacks
- const avatars = [
- avatar.src.replace(/\d+x\d+/, '500x750'),
- avatar.src.replace(/\d+x\d+/, '240x360'),
- avatar.src.replace(/\d+x\d+/, '200x300'),
- avatar.src,
- ];
+ const avatars = getAvatarFallbacks(avatar);
profile.avatar = avatars;
}
@@ -617,7 +631,7 @@ async function fetchLatestApi(site, page = 1, preData, include, upcoming = false
requests: [
{
indexName: 'all_scenes',
- params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? 1 : 0}"]]&filters=sitename:${site.slug} OR channels.id:${site.slug}`,
+ params: `query=&hitsPerPage=36&maxValuesPerFacet=100&page=${page - 1}&facetFilters=[["lesbian:"],["bisex:"],["shemale:"],["upcoming:${upcoming ? 1 : 0}"]]&filters=sitename:${site.slug}`, // OR channels.id:${site.slug}`,
},
],
}, {
@@ -664,8 +678,48 @@ async function fetchSceneApi(url, site, baseRelease, options) {
encodeJSON: true,
});
- if (res.status === 200 && res.body.results?.[0]?.hits) {
- return scrapeSceneApi(res.body.results[0].hits[0], site, options);
+ if (res.status === 200 && res.body.results?.[0]?.hits.length > 0) {
+ return scrapeReleaseApi(res.body.results[0].hits[0], site, options);
+ }
+
+ if (res.status === 200) {
+ return null;
+ }
+
+ return res.status;
+}
+
+async function fetchMovieApi(url, site, baseRelease, options) {
+ const referer = options.parameters?.referer || `${site.parameters?.networkReferer ? site.parent.url : site.url}/en/movies`;
+ const { apiUrl } = await fetchApiCredentials(referer, site);
+
+ const entryId = (baseRelease?.path || new URL(url).pathname).match(/\/(\d{2,})(\/|$)/)?.[1];
+
+ const res = await http.post(apiUrl, {
+ requests: [
+ {
+ indexName: 'all_movies',
+ params: `query=&page=0&facets=[]&tagFilters=&facetFilters=[["movie_id:${entryId}"]]`,
+ },
+ {
+ indexName: 'all_movies',
+ params: 'query=&page=0&hitsPerPage=1&attributesToRetrieve=[]&attributesToHighlight=[]&attributesToSnippet=[]&tagFilters=&analytics=false&clickAnalytics=false&facets=clip_id',
+ },
+ ],
+ }, {
+ headers: {
+ Referer: referer,
+ },
+ }, {
+ encodeJSON: true,
+ });
+
+ if (res.status === 200 && res.body.results?.[0]?.hits.length > 0) {
+ return scrapeReleaseApi(res.body.results[0].hits[0], site, options);
+ }
+
+ if (res.status === 200) {
+ return null;
}
return res.status;
@@ -699,10 +753,10 @@ function getUpcomingUrl(site) {
async function fetchLatest(site, page = 1) {
const url = getLatestUrl(site, page);
- const res = await http.get(url);
+ const res = await qu.getAll(url, 'li[data-itemtype=scene], div[data-itemtype*=scene]');
if (res.ok) {
- return scrapeAll(res.body.toString(), site);
+ return scrapeAll(res.items, site);
}
return res.status;
@@ -710,10 +764,10 @@ async function fetchLatest(site, page = 1) {
async function fetchUpcoming(site) {
const url = getUpcomingUrl(site);
- const res = await http.get(url);
+ const res = await qu.getAll(url, 'li[data-itemtype=scene], div[data-itemtype*=scene]');
if (res.ok) {
- return scrapeAll(res.body.toString(), site, null, false);
+ return scrapeAll(res.items, site, null, false);
}
return res.status;
@@ -749,12 +803,12 @@ async function fetchScene(url, site, baseRelease, options) {
}
const deepUrl = getDeepUrl(url, site, baseRelease);
- const mobileUrl = getDeepUrl(url, site, baseRelease, site.parameters?.mobile || site.parent?.parameters?.mobile);
+ const mobileUrl = options.includePhotos && getDeepUrl(url, site, baseRelease, site.parameters?.mobile || site.parent?.parameters?.mobile);
if (deepUrl) {
const [res, mobileRes] = await Promise.all([
- http.get(deepUrl),
- mobileUrl && http.get(mobileUrl, {
+ qu.get(deepUrl),
+ mobileUrl && qu.get(mobileUrl, null, {
headers: {
// don't redirect to main site
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Mobile Safari/537.36',
@@ -763,8 +817,8 @@ async function fetchScene(url, site, baseRelease, options) {
]);
if (res.status === 200) {
- const mobileBody = mobileRes?.status === 200 ? mobileRes.body.toString() : null;
- const scene = await scrapeScene(res.body.toString(), url, site, baseRelease, mobileBody, options);
+ const mobileItem = mobileRes?.status === 200 ? mobileRes.item : null;
+ const scene = await scrapeScene(res.item, url, site, baseRelease, mobileItem, options);
return { ...scene, deepUrl };
}
@@ -773,20 +827,6 @@ async function fetchScene(url, site, baseRelease, options) {
return null;
}
-async function fetchMovie(url, channel, baseRelease, options) {
- const res = await qu.get(url, null, null, {
- extract: {
- runScripts: 'dangerously',
- },
- });
-
- if (res.ok) {
- return scrapeMovie(res.item, res.window, url, channel, options);
- }
-
- return res.status;
-}
-
async function fetchActorScenes(actorName, apiUrl, siteSlug) {
const res = await http.post(apiUrl, {
requests: [
@@ -827,13 +867,13 @@ async function fetchProfile({ name: actorName }, context, include, altSearchUrl,
if (actorUrl) {
const url = `https://${siteSlug}.com${actorUrl}`;
- const actorRes = await http.get(url);
+ const actorRes = await qu.get(url);
if (actorRes.status !== 200) {
return null;
}
- return scrapeProfile(actorRes.body.toString(), url, actorName, siteSlug, getActorReleasesUrl, include.scenes, context);
+ return scrapeProfile(actorRes.item, url, actorName, siteSlug, getActorReleasesUrl, include.scenes, context);
}
return null;
@@ -881,7 +921,6 @@ module.exports = {
fetchApiUpcoming: fetchUpcomingApi,
fetchLatest,
fetchLatestApi,
- fetchMovie,
fetchProfile,
fetchScene,
fetchSceneApi,
@@ -893,12 +932,14 @@ module.exports = {
fetchProfile: fetchApiProfile,
// fetchScene,
fetchScene: fetchSceneApi,
- fetchMovie,
+ // scrapeMovie,
+ fetchMovie: fetchMovieApi,
},
getPhotos,
scrapeApiProfile,
scrapeApiReleases,
scrapeProfile,
scrapeAll,
+ scrapeMovie,
scrapeScene,
};
diff --git a/src/scrapers/julesjordan.js b/src/scrapers/julesjordan.js
index a1132bd6..1368640b 100644
--- a/src/scrapers/julesjordan.js
+++ b/src/scrapers/julesjordan.js
@@ -136,14 +136,18 @@ function getEntryId(html) {
function scrapeAll(scenes, site, entryIdFromTitle) {
return scenes.map(({ el, query }) => {
const release = {};
+ const title = query.cnt('.content_img div, .dvd_info > a, a.update_title, a[title] + a[title]') || query.cnt('a[title*=" "]');
- release.url = query.url('.update_title a, .dvd_info > a, a ~ a');
- release.title = query.q('.update_title a, .dvd_info > a, a ~ a', true);
+ release.title = title?.slice(0, title.match(/starring:/i)?.index || Infinity).trim();
+ release.url = query.url('.content_img a, .dvd_info > a, a.update_title, a[title*=" "]');
release.date = query.date('.update_date', 'MM/DD/YYYY');
release.entryId = (entryIdFromTitle && slugify(release.title)) || el.dataset.setid || query.q('.rating_box')?.dataset.id;
- release.actors = query.all('.update_models a', true);
+ release.actors = query.all('.content_img .update_models a, .update_models a').map((actorEl) => ({
+ name: query.cnt(actorEl),
+ url: query.url(actorEl, null),
+ }));
const dvdPhotos = query.imgs('.dvd_preview_thumb');
const photoCount = Number(query.q('a img.thumbs', 'cnt')) || 1;
@@ -183,9 +187,9 @@ function scrapeAll(scenes, site, entryIdFromTitle) {
}).filter(Boolean);
const teaserScript = query.html('script');
+
if (teaserScript) {
- const src = teaserScript.slice(teaserScript.indexOf('http'), teaserScript.indexOf('.mp4') + 4);
- if (src) release.teaser = { src };
+ release.teaser = teaserScript.slice(teaserScript.indexOf('http'), teaserScript.indexOf('.mp4') + 4);
}
return release;
@@ -235,17 +239,21 @@ function scrapeUpcoming(html, site) {
});
}
-async function scrapeScene({ html, query }, url, site, include) {
- const release = { url, site };
+async function scrapeScene({ html, query }, url, site, options) {
+ const release = {};
release.entryId = getEntryId(html);
- release.title = query.q('.title_bar_hilite', true);
- release.description = query.q('.update_description', true);
+ release.title = query.cnt('.title_bar_hilite');
+ release.description = query.cnt('.update_description');
release.date = query.date('.update_date', 'MM/DD/YYYY', null, 'innerHTML');
- release.actors = query.all('.backgroundcolor_info > .update_models a, .item .update_models a', true);
- release.tags = query.all('.update_tags a', true);
+ release.actors = query.all('.backgroundcolor_info > .update_models a, .item .update_models a').map((actorEl) => ({
+ name: query.cnt(actorEl),
+ url: query.url(actorEl, null),
+ }));
+
+ release.tags = query.cnts('.update_tags a');
const posterPath = html.match(/useimage = "(.*)"/)?.[1];
@@ -260,7 +268,7 @@ async function scrapeScene({ html, query }, url, site, include) {
}
}
- if (include.trailer && site.slug !== 'manuelferrara') {
+ if (options.includeTrailers && site.slug !== 'manuelferrara') {
const trailerLines = html.split('\n').filter((line) => /movie\["trailer\w*"\]\[/i.test(line));
if (trailerLines.length) {
@@ -277,19 +285,20 @@ async function scrapeScene({ html, query }, url, site, include) {
}
}
- if (include.photos) release.photos = await getPhotos(release.entryId, site);
+ if (options.includePhotos) {
+ release.photos = await getPhotos(release.entryId, site);
+ }
if (query.exists('.update_dvds a')) {
release.movie = {
url: query.url('.update_dvds a'),
- title: query.q('.update_dvds a', true),
+ title: query.cnt('.update_dvds a'),
};
release.movie.entryId = new URL(release.movie.url).pathname.split('/').slice(-1)[0]?.replace('.html', '');
}
- const stars = Number(query.q('.avg_rating', true)?.replace(/[\s|Avg Rating:]/g, ''));
- if (stars) release.stars = stars;
+ release.stars = query.number('.avg_rating');
return release;
}
@@ -298,7 +307,7 @@ function scrapeMovie({ el, query }, url, site) {
const movie = { url, site };
movie.entryId = new URL(url).pathname.split('/').slice(-1)[0]?.replace('.html', '');
- movie.title = query.q('.title_bar span', true);
+ movie.title = query.cnt('.title_bar span');
movie.covers = query.urls('#dvd-cover-flip > a');
movie.channel = slugify(query.q('.update_date a', true), '');
@@ -310,7 +319,7 @@ function scrapeMovie({ el, query }, url, site) {
?.map((scene) => ({ ...scene, movie }))
.sort((sceneA, sceneB) => sceneA.date - sceneB.date);
- movie.date = curatedScenes?.[0].date;
+ movie.date = curatedScenes?.[0]?.date;
return {
...movie,
diff --git a/src/scrapers/kink.js b/src/scrapers/kink.js
index ff26d16a..15eed3a5 100644
--- a/src/scrapers/kink.js
+++ b/src/scrapers/kink.js
@@ -140,16 +140,6 @@ async function fetchLatest(site, page = 1) {
return res.status;
}
-async function fetchScene(url, site) {
- const res = await qu.get(url);
-
- if (res.ok) {
- return scrapeScene(res.item, url, site);
- }
-
- return res.status;
-}
-
async function fetchProfile({ name: actorName }, entity, include) {
const searchRes = await qu.getAll(`https://kink.com/search?type=performers&q=${actorName}`, '.model');
@@ -176,6 +166,6 @@ async function fetchProfile({ name: actorName }, entity, include) {
module.exports = {
fetchLatest,
- fetchScene,
fetchProfile,
+ scrapeScene,
};
diff --git a/src/scrapers/mindgeek.js b/src/scrapers/mindgeek.js
index 8c9137eb..6ca0a319 100644
--- a/src/scrapers/mindgeek.js
+++ b/src/scrapers/mindgeek.js
@@ -11,6 +11,12 @@ const slugify = require('../utils/slugify');
const http = require('../utils/http');
const { inchesToCm, lbsToKg } = require('../utils/convert');
+function getBasePath(channel, path = '/scene') {
+ return channel.parameters?.scene
+ || ((channel.parameters?.native || channel.type === 'network') && `${channel.url}${path}`)
+ || `${channel.parent.url}${path}`;
+}
+
function getThumbs(scene) {
if (scene.images.poster) {
return Object.values(scene.images.poster) // can be { 0: {}, 1: {}, ... } instead of array
@@ -18,7 +24,7 @@ function getThumbs(scene) {
.map((image) => image.xl.url);
}
- if (scene.images.card_main_rect) {
+ if (Array.isArray(scene.images.card_main_rect)) {
return scene.images.card_main_rect
.concat(scene.images.card_secondary_rect || [])
.map((image) => image.xl.url.replace('.thumb', ''));
@@ -27,6 +33,28 @@ function getThumbs(scene) {
return [];
}
+function getCovers(images, target = 'cover') {
+ if (!images[target]) {
+ return [];
+ }
+
+ const covers = [
+ images[target][0].md?.url,
+ images[target][0].sm?.url,
+ images[target][0].xs?.url,
+ // bigger but usually upscaled
+ images[target][0].xx?.url,
+ images[target][0].xl?.url,
+ images[target][0].lg?.url,
+ ];
+
+ if (target === 'poster') {
+ return covers;
+ }
+
+ return [covers];
+}
+
function getVideos(data) {
const teaserSources = data.videos.mediabook?.files;
const trailerSources = data.children.find((child) => child.type === 'trailer')?.videos.full?.files;
@@ -51,9 +79,7 @@ function scrapeLatestX(data, site, filterChannel) {
description: data.description,
};
- const basepath = site.parameters?.scene
- || (site.parameters?.native && `${site.url}/scene`)
- || `${site.parent.url}/scene`;
+ const basepath = getBasePath(site);
release.url = `${basepath}/${release.entryId}/${slugify(release.title)}`;
release.date = new Date(data.dateReleased);
@@ -84,6 +110,9 @@ function scrapeLatestX(data, site, filterChannel) {
};
}
+ const siteName = data.collections[0]?.name || data.brand;
+ release.channel = slugify(siteName, '');
+
return release;
}
@@ -96,7 +125,7 @@ async function scrapeLatest(items, site, filterChannel) {
};
}
-function scrapeScene(data, url, _site, networkName) {
+function scrapeRelease(data, url, channel, networkName) {
const release = {};
const { id: entryId, title, description } = data;
@@ -129,6 +158,29 @@ function scrapeScene(data, url, _site, networkName) {
release.url = url || `https://www.${networkName || data.brand}.com/scene/${entryId}/`;
+ if (data.parent?.type === 'movie' || data.parent?.type === 'serie') {
+ release[data.parent.type] = {
+ entryId: data.parent.id,
+ url: `${getBasePath(channel, data.parent.type === 'movie' ? '/movie' : '/series')}/${data.parent.id}/${slugify(data.parent.title, '-', { removePunctuation: true })}`,
+ title: data.parent.title,
+ description: data.parent.description,
+ date: new Date(data.parent.dateReleased),
+ channel: slugify(data.parent.collections?.name || data.parent.brand),
+ poster: getCovers(data.parent.images, 'poster'),
+ shallow: true,
+ };
+ }
+
+ if (data.type === 'movie') {
+ release.covers = getCovers(data.images);
+ release.scenes = data.children?.map((scene) => ({
+ entryId: scene.id,
+ url: `${getBasePath(channel)}/${scene.id}/${slugify(scene.title)}`,
+ title: scene.title,
+ shallow: true,
+ }));
+ }
+
return release;
}
@@ -155,17 +207,24 @@ function getUrl(site) {
throw new Error(`Mind Geek site '${site.name}' (${site.url}) not supported`);
}
-async function getSession(site, parameters) {
+async function getSession(site, parameters, url) {
+ if (site.slug === 'mindgeek' || site.parameters?.parentSession === false) {
+ // most MG sites have a parent network to acquire a session from, don't try to acquire session from mindgeek.com for independent channels
+ return null;
+ }
+
const cookieJar = new CookieJar();
const session = http.session({ cookieJar });
- // const res = await session.get(url);
const sessionUrl = site.parameters?.siteId && !(site.parameters?.native || site.parameters?.childSession || site.parent?.parameters?.childSession)
? site.parent.url
- : site.url;
+ : (url || site.url);
const res = await http.get(sessionUrl, {
session,
+ headers: {
+ 'Accept-Language': 'en-US,en;', // somehow seems essential for some MG sites
+ },
interval: parameters?.interval,
concurrency: parameters?.concurrency,
parse: false,
@@ -175,7 +234,9 @@ async function getSession(site, parameters) {
const cookieString = await cookieJar.getCookieStringAsync(sessionUrl);
const { instance_token: instanceToken } = cookie.parse(cookieString);
- return { session, instanceToken };
+ if (instanceToken) {
+ return { session, instanceToken };
+ }
}
throw new Error(`Failed to acquire MindGeek session (${res.statusCode})`);
@@ -224,7 +285,7 @@ function scrapeProfile(data, html, releases = [], networkName) {
profile.naturalBoobs = false;
}
- profile.releases = releases.map((release) => scrapeScene(release, null, null, networkName));
+ profile.releases = releases.map((release) => scrapeRelease(release, null, null, networkName));
return profile;
}
@@ -234,7 +295,9 @@ async function fetchLatest(site, page = 1, options) {
const { searchParams } = new URL(url);
const siteId = searchParams.get('site');
- const { session, instanceToken } = options.beforeNetwork || await getSession(site, options.parameters);
+ const { session, instanceToken } = options.beforeNetwork?.headers?.Instance
+ ? options.beforeNetwork
+ : await getSession(site, options.parameters, url);
const beforeDate = moment().add('1', 'day').format('YYYY-MM-DD');
const limit = 24;
@@ -250,6 +313,7 @@ async function fetchLatest(site, page = 1, options) {
Instance: instanceToken,
Origin: site.url,
Referer: url,
+ 'Accept-Language': 'en-US,en;', // somehow seems essential for some MG sites
},
});
@@ -274,6 +338,7 @@ async function fetchUpcoming(site, page, options) {
Instance: instanceToken,
Origin: site.url,
Referer: url,
+ 'Accept-Language': 'en-US,en;', // somehow seems essential for some MG sites
},
});
@@ -284,8 +349,8 @@ async function fetchUpcoming(site, page, options) {
return res.statusCode;
}
-async function fetchScene(url, site, baseScene, options) {
- if (baseScene?.entryId) {
+async function fetchRelease(url, site, baseScene, options) {
+ if (baseScene?.entryId && !baseScene.shallow && !options.parameters.forceDeep) {
// overview and deep data is the same, don't hit server unnecessarily
return baseScene;
}
@@ -299,12 +364,13 @@ async function fetchScene(url, site, baseScene, options) {
concurrency: options.parameters.concurrency,
headers: {
Instance: instanceToken,
+ 'Accept-Language': 'en-US,en;', // somehow seems essential for some MG sites
},
});
if (res.status === 200 && res.body.result) {
return {
- scene: scrapeScene(res.body.result, url, site),
+ scene: scrapeRelease(res.body.result, url, site),
};
}
@@ -321,6 +387,7 @@ async function fetchProfile({ name: actorName, slug: actorSlug }, { entity, para
concurrency: parameters.concurrency,
headers: {
Instance: instanceToken,
+ 'Accept-Language': 'en-US,en;', // somehow seems essential for some MG sites
},
});
@@ -362,9 +429,11 @@ async function fetchProfile({ name: actorName, slug: actorSlug }, { entity, para
module.exports = {
beforeNetwork: getSession,
beforeFetchScenes: getSession,
+ requireBeforeNetwork: false,
scrapeLatestX,
fetchLatest,
fetchUpcoming,
- fetchScene,
+ fetchScene: fetchRelease,
+ fetchMovie: fetchRelease,
fetchProfile,
};
diff --git a/src/scrapers/purgatoryx.js b/src/scrapers/purgatoryx.js
new file mode 100644
index 00000000..34d9656c
--- /dev/null
+++ b/src/scrapers/purgatoryx.js
@@ -0,0 +1,172 @@
+'use strict';
+
+const qu = require('../utils/qu');
+const http = require('../utils/http');
+const slugify = require('../utils/slugify');
+const { feetInchesToCm, lbsToKg } = require('../utils/convert');
+
+function scrapeAll(scenes) {
+ return scenes.map(({ query }) => {
+ const release = {};
+
+ release.title = query.cnt('.title');
+ release.url = query.url('.title a');
+ release.entryId = new URL(release.url).pathname.match(/\/view\/(\d+)/)[1];
+
+ release.date = query.date('.pub-date', 'MMM DD, YYYY');
+ release.duration = query.duration('.video-duration');
+
+ release.actors = query.all('.models a').map((el) => ({
+ name: query.cnt(el),
+ url: query.url(el, null),
+ }));
+
+ if (query.exists('.thumb-big')) { // updates page
+ release.poster = query.img('.thumb-big', 'data-image') || JSON.parse(query.el('.thumbnails-wrap a', 'data-images'));
+ release.photos = [query.img('.thumb-top', 'data-image'), query.img('.thumb-bottom', 'data-image')];
+ }
+
+ if (query.exists('.thumbnails-wrap')) { // actor page
+ try {
+ const images = JSON.parse(query.el('.thumbnails-wrap a', 'data-images'));
+
+ release.poster = images.slice(0, 1)[0];
+ release.photos = images.slice(1);
+ } catch (error) {
+ // images probably not available
+ }
+ }
+
+ return release;
+ });
+}
+
+function scrapeUpcoming({ query }) {
+ const release = {};
+
+ release.url = query.url('.bottom-info a');
+ release.entryId = new URL(release.url).pathname.match(/\/view\/(\d+)/)?.[1];
+ release.title = query.cnt('.title');
+
+ release.actors = query.all('.model-wrap li').map((el) => ({
+ name: query.cnt(el, 'h5'),
+ url: query.url(el, '.model-thumb a'),
+ avatar: query.img(el, '.model-thumb img'),
+ }));
+
+ return release;
+}
+
+function scrapeScene({ query }, url) {
+ const release = {};
+
+ release.title = query.cnt('.title');
+ release.entryId = new URL(url).pathname.match(/\/view\/(\d+)/)[1];
+ release.date = query.date('.date', 'MMMM DD, YYYY', /\w+ \d{1,2}, \d{4}/);
+
+ release.description = query.cnt('.description p');
+ release.duration = query.duration('.total-time');
+
+ release.actors = query.all('.model-wrap li').map((el) => ({
+ name: query.cnt(el, 'h5'),
+ url: query.url(el, 'a'),
+ avatar: query.img(el),
+ }));
+
+ release.poster = query.poster();
+ release.photos = query.urls('.photos-slider a');
+ release.trailer = query.video();
+
+ release.comment = query.cnt('.series');
+
+ return release;
+}
+
+async function fetchLatest(channel, page) {
+ const res = await qu.getAll(`${channel.url}/episodes?page=${page}`, '.content-item');
+
+ if (res.ok) {
+ return scrapeAll(res.items, channel);
+ }
+
+ return res.status;
+}
+
+async function fetchUpcoming(channel) {
+ const res = await qu.get(channel.url, '.upcoming-info-wrap');
+
+ if (res.ok && res.item) {
+ return [scrapeUpcoming(res.item, channel)];
+ }
+
+ return res.status;
+}
+
+function scrapeProfile({ query }, url) {
+ const profile = { url };
+
+ const bio = Object.fromEntries(query.all('.model-desc li').map((el) => [slugify(query.cnt(el, 'span'), '_'), query.text(el)]));
+
+ profile.description = bio.bio;
+
+ profile.dateOfBirth = qu.extractDate(bio.birthdate, 'YYYY-MM-DD');
+ profile.birthPlace = bio.birthplace;
+
+ profile.hairColor = bio.hair_color;
+ profile.eyes = bio.eye_color;
+
+ profile.height = feetInchesToCm(bio.height);
+ profile.weight = lbsToKg(bio.weight);
+ profile.measurements = bio.measurements;
+
+ profile.avatar = query.img('.model-pic img');
+
+ profile.scenes = scrapeAll(qu.initAll(query.all('.content-item')));
+
+ return profile;
+}
+
+async function searchActor(baseActor, channel) {
+ const searchRes = await http.post(`${channel.url}/search-preview`, { q: slugify(baseActor.name, ' ') }, {
+ encodeJSON: false,
+ headers: {
+ 'Accept-Language': 'en-US,en;',
+ },
+ });
+
+ if (searchRes.ok) {
+ const actorUrl = searchRes.body.find((item) => item.type === 'model' && slugify(item.title) === baseActor.slug)?.url;
+
+ return actorUrl || null;
+ }
+
+ return null;
+}
+
+async function fetchProfile(baseActor, context, include, retry = false) {
+ const actorUrl = (!retry && baseActor.url) || await searchActor(baseActor, context.entity);
+
+ if (!actorUrl) {
+ return null;
+ }
+
+ const res = await qu.get(actorUrl);
+
+ if (res.ok) {
+ return scrapeProfile(res.item, actorUrl);
+ }
+
+ if (baseActor.url) {
+ return fetchProfile(baseActor, context, include, true);
+ }
+
+ return res.status;
+}
+
+module.exports = {
+ fetchLatest,
+ fetchProfile,
+ fetchUpcoming,
+ scrapeAll,
+ scrapeScene,
+};
diff --git a/src/scrapers/radical.js b/src/scrapers/radical.js
new file mode 100644
index 00000000..a49e3187
--- /dev/null
+++ b/src/scrapers/radical.js
@@ -0,0 +1,143 @@
+'use strict';
+
+const http = require('../utils/http');
+const qu = require('../utils/qu');
+const slugify = require('../utils/slugify');
+const { lbsToKg, feetInchesToCm } = require('../utils/convert');
+
+function scrapeSceneMetadata(data, channel) {
+ const release = {};
+
+ release.entryId = data.id;
+ release.url = `${channel.url}/tour/videos/${data.id}/${slugify(data.title, '-', { removePunctuation: true })}`;
+
+ release.title = data.title;
+ release.description = data.description;
+
+ release.date = new Date(data.release_date);
+ release.duration = qu.durationToSeconds(data.videos_duration);
+
+ release.actors = data.models.map((model) => ({
+ entryId: model.id,
+ name: model.name,
+ gender: model.gender,
+ avatar: model.thumb,
+ url: `${channel.url}/tour/models/${model.id}/${slugify(model.name, '-', { removePunctuation: true })}`,
+ }));
+
+ release.poster = data.trailer?.poster || [data.thumb?.replace('mobile.jpg', '.jpg'), data.thumb];
+ release.photos = [
+ data.extra_thumbs?.find((url) => /portrait1.jpg/.test(url)),
+ data.extra_thumbs?.find((url) => /scene.jpg/.test(url)),
+ data.extra_thumbs?.find((url) => /portrait2.jpg/.test(url)),
+ ]; // ordered by chronology: portrait1.jpg and scene.jpg are usually pre-shoot poses, portrait2.jpg is the cumshot aftermath
+
+ release.trailer = data.trailer && {
+ src: data.trailer.src,
+ type: data.trailer.type,
+ };
+
+ release.teaser = data.special_thumbs;
+
+ release.tags = [].concat(data.tags?.map((tag) => tag.name));
+ release.qualities = data.downloads && Object.values(data.downloads)?.map((download) => download.meta_data.height);
+ release.stars = data.rating;
+
+ return release;
+}
+
+function scrapeAllMetadata(scenes, channel) {
+ return scenes.map((data) => scrapeSceneMetadata(data, channel));
+}
+
+function scrapeProfileMetadata(data, channel) {
+ const profile = {};
+
+ profile.entryId = data.id;
+ profile.url = `${channel.url}/tour/models/${data.id}/${slugify(data.name, '-', { removePunctuation: true })}`;
+
+ profile.description = data.attributes.bio?.value;
+ profile.dateOfBirth = qu.parseDate(data.attributes.birthdate?.value, 'YYYY-MM-DD');
+ profile.gender = data.gender;
+ profile.age = data.attributes.age?.value;
+ profile.birthPlace = data.attributes.born?.value;
+
+ profile.measurements = data.attributes.measurements?.value;
+ profile.height = feetInchesToCm(data.attributes.height?.value);
+ profile.weight = lbsToKg(data.attributes.weight?.value);
+
+ profile.eyes = data.attributes.eyes?.value;
+ profile.hairColor = data.attributes.hair?.value;
+
+ profile.avatar = data.thumb;
+ profile.date = new Date(data.publish_date);
+
+ return profile;
+}
+
+async function fetchLatestMetadata(channel, page = 1) {
+ const url = `${channel.url}/tour/videos?page=${page}`;
+ const res = await http.get(url, {
+ parse: true,
+ extract: {
+ runScripts: 'dangerously',
+ },
+ });
+
+ if (res.ok && res.window.__DATA__) {
+ return scrapeAllMetadata(res.window.__DATA__.videos.items, channel);
+ }
+
+ if (res.ok) {
+ return res.window.__DATA__?.error || null;
+ }
+
+ return res.status;
+}
+
+async function fetchSceneMetadata(url, channel) {
+ const res = await http.get(url, {
+ parse: true,
+ extract: {
+ runScripts: 'dangerously',
+ },
+ });
+
+ if (res.ok && res.window.__DATA__?.video) {
+ return scrapeSceneMetadata(res.window.__DATA__.video, channel);
+ }
+
+ if (res.ok) {
+ return res.window.__DATA__?.error || null;
+ }
+
+ return res.status;
+}
+
+async function fetchProfileMetadata(actor, channel) {
+ const res = await http.get(`${channel.url}/tour/search-preview/${actor.name}`, {
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ },
+ });
+
+ if (res.ok) {
+ const model = res.body.models?.items.find((modelX) => slugify(modelX.name) === actor.slug);
+
+ if (model) {
+ return scrapeProfileMetadata(model, channel);
+ }
+
+ return null;
+ }
+
+ return res.status;
+}
+
+module.exports = {
+ metadata: {
+ fetchLatest: fetchLatestMetadata,
+ fetchScene: fetchSceneMetadata,
+ fetchProfile: fetchProfileMetadata,
+ },
+};
diff --git a/src/scrapers/scrapers.js b/src/scrapers/scrapers.js
index f80afe28..69ca5d37 100644
--- a/src/scrapers/scrapers.js
+++ b/src/scrapers/scrapers.js
@@ -35,7 +35,7 @@ const karups = require('./karups');
const kellymadison = require('./kellymadison');
const killergram = require('./killergram');
const kink = require('./kink');
-const legalporno = require('./legalporno');
+const analvids = require('./analvids');
const littlecapricedreams = require('./littlecapricedreams');
const mikeadriano = require('./mikeadriano');
const mindgeek = require('./mindgeek');
@@ -51,6 +51,8 @@ const pascalssubsluts = require('./pascalssubsluts'); // reserved keyword
const pierrewoodman = require('./pierrewoodman');
const pinkyxxx = require('./pinkyxxx');
const privateNetwork = require('./private'); // reserved keyword
+const purgatoryx = require('./purgatoryx'); // reserved keyword
+const radical = require('./radical');
const score = require('./score');
const spizoo = require('./spizoo');
const teamskeet = require('./teamskeet');
@@ -116,7 +118,7 @@ const scrapers = {
killergram,
kink,
kinkvr: badoink,
- legalporno,
+ analvids,
letsdoeit: porndoe,
littlecapricedreams,
mamacitaz: porndoe,
@@ -136,6 +138,8 @@ const scrapers = {
porncz,
pornpros: whalemember,
private: privateNetwork,
+ purgatoryx,
+ radical,
score,
sexyhub: mindgeek,
spizoo,
@@ -206,6 +210,7 @@ const scrapers = {
gaywire: bangbros,
girlfaction: fullpornnetwork,
gloryholesecrets: aziani,
+ gotfilled: radical,
hergape: fullpornnetwork,
hitzefrei,
homemadeanalwhores: fullpornnetwork,
@@ -214,6 +219,7 @@ const scrapers = {
hushpass: hush,
hussiepass: hush,
iconmale: mindgeek,
+ inserted: radical,
interracialpass: hush,
interracialpovs: hush,
inthecrack,
@@ -224,7 +230,7 @@ const scrapers = {
killergram,
kink,
kinkvr: badoink,
- legalporno,
+ analvids,
letsdoeit: porndoe,
littlecapricedreams,
mamacitaz: porndoe,
@@ -255,6 +261,7 @@ const scrapers = {
povperverts: fullpornnetwork,
povpornstars: hush,
private: privateNetwork,
+ purgatoryx,
realitykings: mindgeek,
realvr: badoink,
roccosiffredi: famedigital,
diff --git a/src/scrapers/spizoo.js b/src/scrapers/spizoo.js
index 169ccf71..c995182d 100644
--- a/src/scrapers/spizoo.js
+++ b/src/scrapers/spizoo.js
@@ -14,13 +14,16 @@ function scrapeAll(scenes) {
release.url = query.url('a');
release.entryId = getEntryId(release.url);
- release.title = query.cnt('.title-label a');
- release.actors = query.all('.update_models a').map((el) => ({
+ release.title = query.cnt('.title-label a, .thumb-title a, .p-7, .text h3');
+ release.date = query.date('.date-label', 'MM/DD/YYYY');
+
+ release.actors = query.all(['.update_models a', '.tour_update_models a', '.pornstar-label span']).map((el) => ({
name: query.cnt(el),
url: query.url(el, null),
}));
release.poster = query.img('a img');
+ release.teaser = query.video('.leVideo source');
return release;
});
@@ -30,21 +33,21 @@ function scrapeScene({ query }, url) {
const release = {};
release.entryId = getEntryId(url);
- release.title = query.cnt('#media-holder .title');
+ release.title = query.cnt(['#media-holder .title', '.content-holder h1', '#scene h1', 'h2.titular', 'title'])?.replace(/\s+-$/, '');
- release.date = query.date('#sceneInfo .date', 'YYYY-MM-DD');
- release.duration = query.duration('#sceneInfo .data-others', /\d+:\d+/);
+ release.date = query.date('#sceneInfo .date, #trailer-data .date', 'YYYY-MM-DD');
+ release.duration = query.duration('#sceneInfo .data-others, #trailer-data', /\d+:\d+/);
- release.description = query.cnt('#sceneInfo .description');
+ release.description = query.cnt('#sceneInfo .description, #trailer-data > div:first-child p');
- release.actors = query.all('#sceneInfo .data-others a[href*="/models"]').map((el) => ({
+ release.actors = query.all('#sceneInfo .data-others a[href*="/models"], #trailer-data a[href*="/models"]').map((el) => ({
name: query.el(el, null, 'title'),
url: query.url(el, null),
}));
- release.tags = query.cnts('.categories-holder a');
+ release.tags = query.cnts('.categories-holder a, #sceneInfo a[href*="/categories"], #trailer-data a[href*="/categories"]');
- const poster = query.img('#video-holder .update_thumb') || query.poster('#trailervideo');
+ const poster = query.img(['#video-holder .update_thumb', '#noMore .update_thumb', '#hpromo .update_thumb', '.trailer-thumb']) || query.poster('#trailervideo');
const posterPathname = poster && new URL(poster)?.pathname;
release.poster = [poster, poster?.replace(/imgw=\w+/, 'imgw=680')];
@@ -56,7 +59,8 @@ function scrapeScene({ query }, url) {
src,
]);
- release.trailer = query.video('#trailervideo source');
+ release.trailer = query.video('#trailervideo source[type="video/mp4"], #FulsSizeVideo source[type="video/mp4"]'); // sic
+ release.teaser = query.video('#trailer-video source[src*="/videothumbs"]');
return release;
}
@@ -127,7 +131,7 @@ function scrapeProfile({ query, el }) {
}
async function fetchLatest(channel, page) {
- const res = await qu.getAll(`${channel.url}/categories/movies_${page}_d.html`, '.thumb-big');
+ const res = await qu.getAll(`${channel.url}/categories/movies_${page}_d.html`, '.thumb-big, .thumb-video, .thumbnail, .thumbnail-popular, .full-thumbnail');
if (res.ok) {
return scrapeAll(res.items, channel);
diff --git a/src/scrapers/teamskeet.js b/src/scrapers/teamskeet.js
index e0399e1d..c1026a0d 100644
--- a/src/scrapers/teamskeet.js
+++ b/src/scrapers/teamskeet.js
@@ -35,7 +35,7 @@ function scrapeScene(scene, channel) {
}));
release.poster = [
- scene.img.replace('med.jpg', 'hi.jpg'),
+ // scene.img.replace('med.jpg', 'hi.jpg'), // this image is not always from the same scene! for example on Petite Teens 18
scene.img,
];
@@ -129,6 +129,11 @@ async function fetchLatest(channel, page = 1, { parameters }) {
}
async function fetchScene(url, channel, baseScene, { parameters }) {
+ if (baseScene?.entryId) {
+ // overview and deep data is the same, don't hit server unnecessarily
+ return baseScene;
+ }
+
const sceneSlug = new URL(url).pathname.match(/\/([\w-]+$)/)[1];
const res = await http.get(`${parameters.videos}/${sceneSlug}`);
diff --git a/src/scrapers/traxxx.js b/src/scrapers/traxxx.js
index b6fcb489..dcd1aedd 100644
--- a/src/scrapers/traxxx.js
+++ b/src/scrapers/traxxx.js
@@ -4,7 +4,7 @@
const config = require('config');
const faker = require('faker');
-const nanoid = require('nanoid');
+const { nanoid } = require('nanoid');
const moment = require('moment');
const knex = require('../knex');
@@ -232,7 +232,7 @@ function actors(release) {
}
async function fetchLatest(entity, page, options) {
- return Promise.all(Array.from({ length: 100 }, async (value, index) => {
+ return Promise.all(Array.from({ length: 10000 }, async (value, index) => {
const release = {};
release.entryId = nanoid();
@@ -249,7 +249,8 @@ async function fetchLatest(entity, page, options) {
.where('is_sfw', true)
.pluck('path')
.orderByRaw('random()')
- .limit(Math.floor(Math.random() * 10) + 1);
+ // .limit(Math.floor(Math.random() * 10) + 1)
+ .limit(100);
// const poster = 'sfw/kittens/thumbs/iNEXVlX-RLs.jpeg';
@@ -261,7 +262,7 @@ async function fetchLatest(entity, page, options) {
.select('name')
.where('priority', '>', 7)
.orderByRaw('random()')
- .limit(faker.random.number({ min: 2, max: 15 }))
+ .limit(faker.datatype.number({ min: 15, max: 25 }))
.pluck('name');
release.actors = [...actors(release), null]; // include empty actor to ensure proper handling
diff --git a/src/scrapers/vixen.js b/src/scrapers/vixen.js
index 0f3ef35e..ce4d95db 100644
--- a/src/scrapers/vixen.js
+++ b/src/scrapers/vixen.js
@@ -4,6 +4,7 @@
const Promise = require('bluebird');
const moment = require('moment');
+const logger = require('../logger')(__filename);
const http = require('../utils/http');
const slugify = require('../utils/slugify');
@@ -141,24 +142,69 @@ async function getTrailer(scene, channel, url) {
return null;
}
-async function getPhotos(url) {
+/*
+async function getPhotosLegacy(url) {
const htmlRes = await http.get(url, {
extract: {
runScripts: 'dangerously',
},
});
- const state = htmlRes?.window.__APOLLO_STATE__;
- const key = Object.values(state.ROOT_QUERY).find((query) => query?.__ref)?.__ref;
- const data = state[key];
+ try {
+ const state = htmlRes?.window?.__APOLLO_STATE__;
- console.log(data);
+ if (!state) {
+ return [];
+ }
- if (!data) {
+ const key = Object.values(state?.ROOT_QUERY).find((query) => query?.__ref)?.__ref;
+ const data = state[key];
+
+ if (!data) {
+ return [];
+ }
+
+ return data.carousel.slice(1).map((photo) => photo.main?.[0].src).filter(Boolean);
+ } catch (error) {
+ logger.warn(`Failed to retrieve Vixen images: ${error.message}`);
return [];
}
+}
+*/
- return data.carousel.slice(1).map((photo) => photo.main?.[0].src).filter(Boolean);
+async function getPhotos(url) {
+ const htmlRes = await http.get(url, {
+ parse: true,
+ extract: {
+ runScripts: 'dangerously',
+ },
+ });
+
+ try {
+ const state = htmlRes?.window?.__APOLLO_STATE__;
+
+ console.log('state', state);
+
+ if (!state) {
+ return [];
+ }
+
+ const key = Object.values(state?.ROOT_QUERY).find((query) => query?.__ref)?.__ref;
+ const data = state[key];
+
+ console.log('data', data);
+
+ if (!data) {
+ return [];
+ }
+
+ console.log(data.carousel);
+
+ return data.carousel.slice(1).map((photo) => photo.main?.[0].src).filter(Boolean);
+ } catch (error) {
+ logger.warn(`Failed to retrieve Vixen images: ${error.message}`);
+ return [];
+ }
}
function scrapeAll(scenes, site, origin) {
diff --git a/src/scrapers/whalemember.js b/src/scrapers/whalemember.js
index 25bb1e5a..af5f0fa2 100644
--- a/src/scrapers/whalemember.js
+++ b/src/scrapers/whalemember.js
@@ -7,7 +7,7 @@ const http = require('../utils/http');
function scrapeLatest(html, site) {
const { document } = new JSDOM(html).window;
- const { origin } = new URL(site.url);
+ const { origin } = new URL(site.parameters?.latest || site.url);
const videos = Array.from(document.querySelectorAll('.video-releases-list')).slice(-1)[0];
@@ -119,7 +119,7 @@ function scrapeScene(html, site, url) {
}
async function fetchLatest(site, page = 1) {
- const url = `${site.url}?page=${page}`;
+ const url = `${site.parameters?.latest || site.url}?page=${page}`;
const res = await http.get(url);
if (res.statusCode === 200) {
diff --git a/src/scrapers/xempire.js b/src/scrapers/xempire.js
index cbe44132..e74cb576 100644
--- a/src/scrapers/xempire.js
+++ b/src/scrapers/xempire.js
@@ -1,14 +1,13 @@
'use strict';
const { fetchLatest, fetchUpcoming, scrapeScene, fetchProfile } = require('./gamma');
-const http = require('../utils/http');
+const qu = require('../utils/qu');
async function fetchScene(url, site, baseRelease, options) {
- const res = await http.get(url);
+ const res = await qu.get(url);
+ const release = await scrapeScene(res.item, url, site, baseRelease, null, options);
- const release = await scrapeScene(res.body.toString(), url, site, baseRelease, null, options);
-
- const siteDomain = release.$('meta[name="twitter:domain"]').attr('content') || 'allblackx.com'; // only AllBlackX has no twitter domain, no other useful hints available
+ const siteDomain = release.query.el('meta[name="twitter:domain"]', 'content') || 'allblackx.com'; // only AllBlackX has no twitter domain, no other useful hints available
const siteSlug = siteDomain && siteDomain.split('.')[0].toLowerCase();
// const siteUrl = siteDomain && `https://www.${siteDomain}`;
diff --git a/src/store-releases.js b/src/store-releases.js
index 04f0a334..f51fab83 100644
--- a/src/store-releases.js
+++ b/src/store-releases.js
@@ -1,6 +1,7 @@
'use strict';
const config = require('config');
+const Promise = require('bluebird');
const argv = require('./argv');
const logger = require('./logger')(__filename);
@@ -8,6 +9,7 @@ const knex = require('./knex');
const slugify = require('./utils/slugify');
const bulkInsert = require('./utils/bulk-insert');
const resolvePlace = require('./utils/resolve-place');
+const chunk = require('./utils/chunk');
const { formatDate } = require('./utils/qu');
const { associateActors, associateDirectors, scrapeActors, toBaseActors } = require('./actors');
const { associateReleaseTags } = require('./tags');
@@ -134,7 +136,7 @@ async function attachStudios(releases) {
return releasesWithStudio;
}
-function attachReleaseIds(releases, storedReleases) {
+function attachReleaseIds(releases, storedReleases, batchId) {
const storedReleaseIdsByEntityIdAndEntryId = storedReleases.reduce((acc, release) => {
if (!acc[release.entity_id]) acc[release.entity_id] = {};
acc[release.entity_id][release.entry_id] = release.id;
@@ -144,7 +146,7 @@ function attachReleaseIds(releases, storedReleases) {
const releasesWithId = releases.map((release) => {
if (!release.entity) {
- logger.error(`No entitity available for ${release.url}`);
+ logger.error(`No entity available for ${release.url}`);
return null;
}
@@ -155,6 +157,7 @@ function attachReleaseIds(releases, storedReleases) {
return {
...release,
id,
+ batchId,
};
}
@@ -192,13 +195,16 @@ function filterInternalDuplicateReleases(releases) {
async function filterDuplicateReleases(releases) {
const internalUniqueReleases = filterInternalDuplicateReleases(releases);
+ const internalUniqueReleaseChunks = chunk(internalUniqueReleases);
- const duplicateReleaseEntries = await knex('releases')
- .whereIn(['entry_id', 'entity_id'], internalUniqueReleases.map((release) => [release.entryId, release.entity.id]))
- .orWhereIn(['entry_id', 'entity_id'], internalUniqueReleases
+ const duplicateReleaseEntryChunks = await Promise.map(internalUniqueReleaseChunks, async (internalUniqueReleasesChunk) => knex('releases')
+ .whereIn(['entry_id', 'entity_id'], internalUniqueReleasesChunk.map((release) => [release.entryId, release.entity.id]))
+ .orWhereIn(['entry_id', 'entity_id'], internalUniqueReleasesChunk
// scene IDs shared across network, mark as duplicate so scene can be updated with channel if only available on release day (i.e. Perv City)
.filter((release) => release.entity.parent?.parameters?.networkEntryIds)
- .map((release) => [release.entryId, release.entity.parent.id]));
+ .map((release) => [release.entryId, release.entity.parent.id])), { concurrency: 10 });
+
+ const duplicateReleaseEntries = duplicateReleaseEntryChunks.flat();
const duplicateReleasesByEntityIdAndEntryId = duplicateReleaseEntries.reduce((acc, release) => {
if (!acc[release.entity_id]) acc[release.entity_id] = {};
@@ -229,6 +235,7 @@ async function updateSceneSearch(releaseIds) {
TO_TSVECTOR(
'english',
COALESCE(releases.title, '') || ' ' ||
+ releases.entry_id || ' ' ||
entities.name || ' ' ||
entities.slug || ' ' ||
COALESCE(array_to_string(entities.alias, ' '), '') || ' ' ||
@@ -308,12 +315,148 @@ async function storeChapters(releases) {
await associateReleaseMedia(chaptersWithId, 'chapter');
}
-async function storeScenes(releases) {
+async function associateMovieScenes(movies, movieScenes) {
+ const moviesByEntityIdAndEntryId = movies.reduce((acc, movie) => ({
+ ...acc,
+ [movie.entity.id]: {
+ ...acc[movie.entity.id],
+ [movie.entryId]: movie,
+ },
+ }), {});
+
+ const associations = movieScenes.map((scene) => {
+ if (!scene.movie) {
+ return null;
+ }
+
+ const sceneMovie = moviesByEntityIdAndEntryId[scene.entity.id]?.[scene.movie.entryId]
+ || moviesByEntityIdAndEntryId[scene.entity.parent?.id]?.[scene.movie.entryId];
+
+ if (sceneMovie?.id) {
+ return {
+ movie_id: sceneMovie.id,
+ scene_id: scene.id,
+ };
+ }
+
+ return null;
+ }).filter(Boolean);
+
+ await bulkInsert('movies_scenes', associations, false);
+}
+
+async function associateSerieScenes(series, serieScenes) {
+ const seriesByEntityIdAndEntryId = series.reduce((acc, serie) => ({
+ ...acc,
+ [serie.entity.id]: {
+ ...acc[serie.entity.id],
+ [serie.entryId]: serie,
+ },
+ }), {});
+
+ const associations = serieScenes.map((scene) => {
+ if (!scene.serie) {
+ return null;
+ }
+
+ const sceneSerie = seriesByEntityIdAndEntryId[scene.entity.id]?.[scene.serie.entryId]
+ || seriesByEntityIdAndEntryId[scene.entity.parent?.id]?.[scene.serie.entryId];
+
+ if (sceneSerie?.id) {
+ return {
+ serie_id: sceneSerie.id,
+ scene_id: scene.id,
+ };
+ }
+
+ return null;
+ }).filter(Boolean);
+
+ await bulkInsert('series_scenes', associations, false);
+}
+
+async function updateMovieSearch(movieIds, target = 'movie') {
+ logger.info(`Updating search documents for ${movieIds ? movieIds.length : 'all' } ${target}s`);
+
+ const documents = await knex.raw(`
+ SELECT
+ ${target}s.id AS ${target}_id,
+ TO_TSVECTOR(
+ 'english',
+ COALESCE(${target}s.title, '') || ' ' ||
+ entities.name || ' ' ||
+ entities.slug || ' ' ||
+ COALESCE(array_to_string(entities.alias, ' '), '') || ' ' ||
+ COALESCE(parents.name, '') || ' ' ||
+ COALESCE(parents.slug, '') || ' ' ||
+ COALESCE(array_to_string(parents.alias, ' '), '') || ' ' ||
+ COALESCE(TO_CHAR(${target}s.date, 'YYYY YY MM FMMM FMMonth mon DD FMDD'), '') || ' ' ||
+ STRING_AGG(COALESCE(releases.title, ''), ' ') || ' ' ||
+ STRING_AGG(COALESCE(actors.name, ''), ' ') || ' ' ||
+ STRING_AGG(COALESCE(tags.name, ''), ' ')
+ ) as document
+ FROM ${target}s
+ LEFT JOIN entities ON ${target}s.entity_id = entities.id
+ LEFT JOIN entities AS parents ON parents.id = entities.parent_id
+ LEFT JOIN ${target}s_scenes ON ${target}s_scenes.${target}_id = ${target}s.id
+ LEFT JOIN releases ON releases.id = ${target}s_scenes.scene_id
+ LEFT JOIN releases_actors ON releases_actors.release_id = ${target}s_scenes.scene_id
+ LEFT JOIN releases_tags ON releases_tags.release_id = releases.id
+ LEFT JOIN actors ON actors.id = releases_actors.actor_id
+ LEFT JOIN tags ON tags.id = releases_tags.tag_id
+ ${movieIds ? `WHERE ${target}s.id = ANY(?)` : ''}
+ GROUP BY ${target}s.id, entities.name, entities.slug, entities.alias, parents.name, parents.slug, parents.alias;
+ `, movieIds && [movieIds]);
+
+ if (documents.rows?.length > 0) {
+ await bulkInsert(`${target}s_search`, documents.rows, [`${target}_id`]);
+ }
+}
+
+async function storeMovies(movies, useBatchId) {
+ if (!movies || movies.length === 0) {
+ return [];
+ }
+
+ const { uniqueReleases } = await filterDuplicateReleases(movies);
+ const [batchId] = useBatchId ? [useBatchId] : await knex('batches').insert({ comment: null }).returning('id');
+
+ const curatedMovieEntries = await Promise.all(uniqueReleases.map((release) => curateReleaseEntry(release, batchId, null, 'movie')));
+
+ const storedMovies = await bulkInsert('movies', curatedMovieEntries, ['entity_id', 'entry_id'], true);
+ const moviesWithId = attachReleaseIds(movies, storedMovies);
+
+ await updateMovieSearch(moviesWithId.map((movie) => movie.id));
+ await associateReleaseMedia(moviesWithId, 'movie');
+
+ return moviesWithId;
+}
+
+async function storeSeries(series, useBatchId) {
+ if (!series || series.length === 0) {
+ return [];
+ }
+
+ const { uniqueReleases } = await filterDuplicateReleases(series);
+ const [batchId] = useBatchId ? [useBatchId] : await knex('batches').insert({ comment: null }).returning('id');
+
+ const curatedSerieEntries = await Promise.all(uniqueReleases.map((release) => curateReleaseEntry(release, batchId, null, 'serie')));
+
+ const storedSeries = await bulkInsert('series', curatedSerieEntries, ['entity_id', 'entry_id'], true);
+ const seriesWithId = attachReleaseIds(series, storedSeries);
+
+ await updateMovieSearch(seriesWithId.map((serie) => serie.id), 'serie');
+ await associateReleaseMedia(seriesWithId, 'serie');
+
+ return seriesWithId;
+}
+
+async function storeScenes(releases, useBatchId) {
if (!releases || releases.length === 0) {
return [];
}
- const [batchId] = await knex('batches').insert({ comment: null }).returning('id');
+ const [batchId] = useBatchId ? [useBatchId] : await knex('batches').insert({ comment: null }).returning('id');
const releasesWithChannels = await attachChannelEntities(releases);
const releasesWithBaseActors = releasesWithChannels.map((release) => ({ ...release, actors: toBaseActors(release.actors) }));
@@ -327,8 +470,8 @@ async function storeScenes(releases) {
const storedReleaseEntries = Array.isArray(storedReleases) ? storedReleases : [];
- const uniqueReleasesWithId = attachReleaseIds(uniqueReleases, storedReleaseEntries);
- const duplicateReleasesWithId = attachReleaseIds(duplicateReleases, duplicateReleaseEntries);
+ const uniqueReleasesWithId = attachReleaseIds(uniqueReleases, storedReleaseEntries, batchId);
+ const duplicateReleasesWithId = attachReleaseIds(duplicateReleases, duplicateReleaseEntries, batchId);
const releasesWithId = uniqueReleasesWithId.concat(duplicateReleasesWithId);
const updated = await knex.raw(`
@@ -348,12 +491,14 @@ async function storeScenes(releases) {
scenes: JSON.stringify(duplicateReleasesWithId),
});
- const [actors] = await Promise.all([
+ const [actors, storedSeries] = await Promise.all([
associateActors(releasesWithId, batchId),
+ storeSeries(releasesWithId.map((release) => release.serie && { ...release.serie, entity: release.entity }).filter(Boolean), batchId),
associateReleaseTags(releasesWithId),
storeChapters(releasesWithId),
]);
+ await associateSerieScenes(storedSeries, releasesWithId);
await associateDirectors(releasesWithId, batchId); // some directors may also be actors, don't associate at the same time
await updateSceneSearch(releasesWithId.map((release) => release.id));
@@ -371,92 +516,6 @@ async function storeScenes(releases) {
return releasesWithId;
}
-async function associateMovieScenes(movies, movieScenes) {
- const moviesByEntityIdAndEntryId = movies.reduce((acc, movie) => ({
- ...acc,
- [movie.entity.id]: {
- ...acc[movie.entity.id],
- [movie.entryId]: movie,
- },
- }), {});
-
- const associations = movieScenes.map((scene) => {
- if (!scene.movie) {
- return null;
- }
-
- const sceneMovie = moviesByEntityIdAndEntryId[scene.entity.id]?.[scene.movie.entryId];
-
- if (sceneMovie?.id) {
- return {
- movie_id: sceneMovie.id,
- scene_id: scene.id,
- };
- }
-
- return null;
- }).filter(Boolean);
-
- await bulkInsert('movies_scenes', associations, false);
-}
-
-async function updateMovieSearch(movieIds) {
- logger.info(`Updating search documents for ${movieIds ? movieIds.length : 'all' } movies`);
-
- const documents = await knex.raw(`
- SELECT
- movies.id AS movie_id,
- TO_TSVECTOR(
- 'english',
- COALESCE(movies.title, '') || ' ' ||
- entities.name || ' ' ||
- entities.slug || ' ' ||
- COALESCE(array_to_string(entities.alias, ' '), '') || ' ' ||
- COALESCE(parents.name, '') || ' ' ||
- COALESCE(parents.slug, '') || ' ' ||
- COALESCE(array_to_string(parents.alias, ' '), '') || ' ' ||
- COALESCE(TO_CHAR(movies.date, 'YYYY YY MM FMMM FMMonth mon DD FMDD'), '') || ' ' ||
- STRING_AGG(COALESCE(releases.title, ''), ' ') || ' ' ||
- STRING_AGG(COALESCE(actors.name, ''), ' ') || ' ' ||
- STRING_AGG(COALESCE(tags.name, ''), ' ')
- ) as document
- FROM movies
- LEFT JOIN entities ON movies.entity_id = entities.id
- LEFT JOIN entities AS parents ON parents.id = entities.parent_id
- LEFT JOIN movies_scenes ON movies_scenes.movie_id = movies.id
- LEFT JOIN releases ON releases.id = movies_scenes.scene_id
- LEFT JOIN releases_actors ON releases_actors.release_id = movies_scenes.scene_id
- LEFT JOIN releases_tags ON releases_tags.release_id = releases.id
- LEFT JOIN actors ON actors.id = releases_actors.actor_id
- LEFT JOIN tags ON tags.id = releases_tags.tag_id
- ${movieIds ? 'WHERE movies.id = ANY(?)' : ''}
- GROUP BY movies.id, entities.name, entities.slug, entities.alias, parents.name, parents.slug, parents.alias;
- `, movieIds && [movieIds]);
-
- if (documents.rows?.length > 0) {
- await bulkInsert('movies_search', documents.rows, ['movie_id']);
- }
-}
-
-async function storeMovies(movies) {
- if (!movies || movies.length === 0) {
- return [];
- }
-
- const { uniqueReleases } = await filterDuplicateReleases(movies);
- const [batchId] = await knex('batches').insert({ comment: null }).returning('id');
-
- const curatedMovieEntries = await Promise.all(uniqueReleases.map((release) => curateReleaseEntry(release, batchId, null, 'movie')));
-
- const storedMovies = await bulkInsert('movies', curatedMovieEntries, ['entity_id', 'entry_id'], true);
- const moviesWithId = attachReleaseIds(movies, storedMovies);
-
- await updateMovieSearch(moviesWithId.map((movie) => movie.id));
- await associateReleaseMedia(moviesWithId, 'movie');
-
- return moviesWithId;
-}
-
module.exports = {
associateMovieScenes,
storeScenes,
diff --git a/src/tags.js b/src/tags.js
index 4a66782a..7bdbd4e0 100644
--- a/src/tags.js
+++ b/src/tags.js
@@ -99,14 +99,20 @@ async function matchReleaseTags(releases) {
async function getEntityTags(releases) {
const entityIds = releases.map((release) => release.entity?.id).filter(Boolean);
- const entityTags = await knex('entities_tags').whereIn('entity_id', entityIds);
+ const entityTags = await knex('entities_tags')
+ .select('id', 'name', 'entity_id')
+ .whereIn('entity_id', entityIds)
+ .leftJoin('tags', 'tags.id', 'entities_tags.tag_id');
const entityTagIdsByEntityId = entityTags.reduce((acc, entityTag) => {
if (!acc[entityTag.entity_id]) {
acc[entityTag.entity_id] = [];
}
- acc[entityTag.entity_id].push(entityTag.tag_id);
+ acc[entityTag.entity_id].push({
+ id: entityTag.id,
+ name: entityTag.name,
+ });
return acc;
}, {});
@@ -117,7 +123,7 @@ async function getEntityTags(releases) {
function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type) {
const tagAssociations = releases
.map((release) => {
- const entityTagIds = entityTagIdsByEntityId[release.entity?.id]?.map((tag) => ({ id: tag.id, origin: tag.name })) || [];
+ const entityTagIds = entityTagIdsByEntityId[release.entity?.id]?.map((tag) => ({ id: tag.id, original: tag.name })) || [];
const releaseTags = release.tags?.filter(Boolean) || [];
const releaseTagsWithIds = releaseTags.every((tag) => typeof tag === 'number')
@@ -152,9 +158,9 @@ async function associateReleaseTags(releases, type = 'release') {
}
const tagIdsBySlug = await matchReleaseTags(releases);
- const EntityTagIdsByEntityId = await getEntityTags(releases);
+ const entityTagIdsByEntityId = await getEntityTags(releases);
- const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, EntityTagIdsByEntityId, type);
+ const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type);
await bulkInsert(`${type}s_tags`, tagAssociations, false);
}
diff --git a/src/tools/analvids.js b/src/tools/analvids.js
new file mode 100644
index 00000000..ef583c5d
--- /dev/null
+++ b/src/tools/analvids.js
@@ -0,0 +1,1721 @@
+'use strict';
+
+const fs = require('fs').promises;
+
+const oldStudios = [
+ {
+ slug: 'gonzocom',
+ name: 'Gonzo.com',
+ alias: ['sz'],
+ url: 'https://www.legalporno.com/studios/gonzo_com',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'giorgiograndi',
+ name: 'Giorgio Grandi',
+ url: 'https://www.legalporno.com/studios/giorgio-grandi',
+ alias: ['gio'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'hardpornworld',
+ name: 'Hard Porn World',
+ url: 'https://www.legalporno.com/studios/hard-porn-world',
+ alias: ['gp'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'interracialvision',
+ name: 'Interracial Vision',
+ url: 'https://www.legalporno.com/studios/interracial-vision',
+ alias: ['iv'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'giorgioslab',
+ name: 'Giorgio\'s Lab',
+ url: 'https://www.legalporno.com/studios/giorgio--s-lab',
+ alias: ['gl'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'americananal',
+ name: 'American Anal',
+ url: 'https://www.legalporno.com/studios/american-anal',
+ alias: ['aa'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'assablanca',
+ name: 'Assablanca',
+ url: 'https://www.legalporno.com/studios/assablanca',
+ alias: ['ab'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'focus',
+ name: 'Focus',
+ url: 'https://www.legalporno.com/studios/focus',
+ alias: ['fs'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'anal4her',
+ name: 'Anal 4 Her',
+ url: 'https://www.legalporno.com/studios/anal-4-her',
+ alias: ['af', 'anal forever'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'gonzoinbrazil',
+ name: 'Gonzo in Brazil',
+ url: 'https://www.legalporno.com/studios/gonzo-in-brazil',
+ alias: ['bz'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'mranal',
+ name: 'Mr Anal',
+ url: 'https://www.legalporno.com/studios/mr-anal',
+ alias: ['ma'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'tarrawhite',
+ name: 'Tarra White',
+ url: 'https://www.legalporno.com/studios/tarra-white',
+ alias: ['tw'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'sineplexsos',
+ name: 'Sineplex SOS',
+ url: 'https://www.legalporno.com/studios/sineplex-sos',
+ alias: ['rs'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'fmodels',
+ name: 'F Models',
+ url: 'https://www.legalporno.com/studios/f-models',
+ alias: ['fm'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'sineplexcz',
+ name: 'Sineplex CZ',
+ url: 'https://www.legalporno.com/studios/sineplex-cz',
+ alias: ['sz'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'gg',
+ name: 'GG',
+ url: 'https://www.legalporno.com/studios/gg',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'firstgape',
+ name: 'First Gape',
+ url: 'https://www.legalporno.com/studios/first-gape',
+ alias: ['sal'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'omargalantiproductions',
+ name: 'Omar Galanti Productions',
+ url: 'https://www.legalporno.com/studios/omar-galanti-productions',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'marywet',
+ name: 'Marywet',
+ url: 'https://www.legalporno.com/studios/marywet',
+ alias: ['ots'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'norestfortheass',
+ name: 'No Rest For The Ass',
+ url: 'https://www.legalporno.com/studios/no-rest-for-the-ass',
+ alias: ['nr'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'hairygonzo',
+ name: 'Hairy Gonzo',
+ url: 'https://www.legalporno.com/studios/hairy-gonzo',
+ alias: ['hg'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'sineplexclassic',
+ name: 'Sineplex Classic',
+ url: 'https://www.legalporno.com/studios/sineplex-classic',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'sinemale',
+ name: 'Sinemale',
+ url: 'https://www.legalporno.com/studios/sinemale',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'outsidethestudio',
+ name: 'Outside The Studio',
+ url: 'https://www.legalporno.com/studios/outside-the-studio',
+ alias: ['ots'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'kinkysex',
+ name: 'Kinky Sex',
+ url: 'https://www.legalporno.com/studios/kinky-sex',
+ alias: ['ks'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'sexyangelproductions',
+ name: 'Sexy Angel Productions',
+ url: 'https://www.legalporno.com/studios/sexy-angel-productions',
+ alias: ['sa'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'nfstudio',
+ name: 'N&F Studio',
+ url: 'https://www.legalporno.com/studios/nf-studio',
+ alias: ['nf'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'natashateenproductions',
+ name: 'Natasha Teen Productions',
+ url: 'https://www.legalporno.com/studios/natasha-teen-productions',
+ alias: ['nt'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'mixedstudios',
+ name: 'Mixed Studios',
+ url: 'https://www.legalporno.com/studios/mixed-studios',
+ alias: ['ms'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'claudiasclips',
+ name: 'Claudia\'s Clips',
+ url: 'https://www.legalporno.com/studios/claudia--s-clips',
+ alias: ['cm'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'rebeccasclips',
+ name: 'Rebecca\'s Clips',
+ url: 'https://www.legalporno.com/studios/rebecca--s-clips',
+ alias: ['rv'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'private',
+ name: 'Private',
+ url: 'https://www.legalporno.com/studios/private',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'privatecastings',
+ name: 'Private Castings',
+ url: 'https://www.legalporno.com/studios/private-castings',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'privateblack',
+ name: 'Private Black',
+ url: 'https://www.legalporno.com/studios/private-black',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'nrxstudio',
+ name: 'NRX Studio',
+ url: 'https://www.legalporno.com/studios/nrx-studio',
+ alias: ['nrx'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'lpggg',
+ name: 'GGG by John Thompson',
+ url: 'https://www.legalporno.com/studios/ggg-by-john-thompson',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'yummyestudio',
+ name: 'Yummy Estudio',
+ url: 'https://www.legalporno.com/studios/yummy-estudio',
+ alias: ['ye'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'bustedtgirls',
+ name: 'Busted T-Girls',
+ url: 'https://www.legalporno.com/studios/busted-t-girls',
+ alias: ['btg'],
+ tags: ['transsexual'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbangbros',
+ name: 'Bang Bros',
+ url: 'https://www.legalporno.com/studios/bang-bros',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'vkstudio',
+ name: 'VK Studio',
+ url: 'https://www.legalporno.com/studios/vk-studio',
+ alias: ['vk'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analmaniacs',
+ name: 'Anal Maniacs by Lady Dee',
+ url: 'https://www.legalporno.com/studios/anal-maniacs-by-lady-dee',
+ alias: ['ld'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'pineapplestestkitchen',
+ name: 'Pineapple\'s Test Kitchen',
+ url: 'https://www.legalporno.com/studios/pineapple--s-test-kitchen',
+ alias: ['ax'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'adelinelafouinestudio',
+ name: 'Adeline Lafouine Studio',
+ url: 'https://www.legalporno.com/studios/adeline-lafouine-studio',
+ alias: ['al'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'jeanmariecordastudio',
+ name: 'Jean Marie Corda Studio',
+ url: 'https://www.legalporno.com/studios/jean-marie-corda-studio',
+ alias: ['jmc'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'brianabanderasstudio',
+ name: 'Briana Banderas Studio',
+ url: 'https://www.legalporno.com/studios/briana-banderas-studio',
+ alias: ['brb'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'rickangelstudio',
+ name: 'Rick Angel Studio',
+ url: 'https://www.legalporno.com/studios/rick-angel-studio',
+ alias: ['ra'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'mamasitasavage',
+ name: 'Mamasita Savage',
+ url: 'https://www.legalporno.com/studios/mamasita-savage',
+ alias: ['msv'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'badbardotclub',
+ name: 'Bad Bardot Club',
+ url: 'https://www.legalporno.com/studios/bad-bardot-club',
+ alias: ['bbc'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'katerichstudio',
+ name: 'Kate Rich Studio',
+ url: 'https://www.legalporno.com/studios/kate-rich-studio',
+ alias: ['krs'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'daddyenjoy',
+ name: 'DaddyEnjoy',
+ url: 'https://www.legalporno.com/studios/daddyenjoy',
+ alias: ['de'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'laradesantisstudio',
+ name: 'Lara De Santis Studio',
+ url: 'https://www.legalporno.com/studios/lara-de-santis-studio',
+ alias: ['lds'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'lutrosworld',
+ name: 'Lutro\'s World',
+ url: 'https://www.legalporno.com/studios/lutro--s-world',
+ alias: ['lw'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'timeabellaproduction',
+ name: 'Timea Bella Production',
+ url: 'https://www.legalporno.com/studios/timea-bella-production',
+ alias: ['tb'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'angelwickysproduction',
+ name: 'Angel Wicky\'s Production',
+ url: 'https://www.legalporno.com/studios/angel-wicky%E2%80%99s-production',
+ alias: ['aw'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'bbcmaster',
+ name: 'BBC Master',
+ url: 'https://www.legalporno.com/studios/bbc-master',
+ alias: ['jl'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'maxrajoysquad',
+ name: 'Max Rajoy Squad',
+ url: 'https://www.legalporno.com/studios/max-rajoy-squad',
+ alias: ['mrs'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'queeneugenia',
+ name: 'Queen Eugenia',
+ url: 'https://www.legalporno.com/studios/queen-eugenia-studio',
+ alias: ['qe'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'helenamoellerstudio',
+ name: 'Helena Moeller Studio',
+ url: 'https://www.legalporno.com/studios/helena-moeller-studio',
+ alias: ['hms'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'allaboutsweetbunny',
+ name: 'All About Sweet Bunny',
+ url: 'https://www.legalporno.com/studios/all-about-sweet-bunny',
+ alias: ['swb'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'clubcandyalexa',
+ name: 'Club Candy Alexa',
+ url: 'https://www.legalporno.com/studios/club-candy-alexa',
+ alias: ['cca'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'stacybloomstudio',
+ name: 'Stacy Bloom Studio',
+ url: 'https://www.legalporno.com/studios/stacy-bloom-studio',
+ alias: ['sbs'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'harleenvanhyntenstudio',
+ name: 'Harleen Van Hynten Studio',
+ url: 'https://www.legalporno.com/studios/harleen-van-hynten-studio',
+ alias: ['hvh'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'cherryaleksastudio',
+ name: 'Cherry Aleksa Studio',
+ url: 'https://www.legalporno.com/studios/cherry-aleksa-studio',
+ alias: ['ca'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'possiblyneighbours',
+ name: 'Possibly Neighbours',
+ url: 'https://www.legalporno.com/studios/possibly-neighbours',
+ alias: ['pn'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'honourmaysmanorhouse',
+ name: 'Honour Mays\' Manor House',
+ url: 'https://www.legalporno.com/studios/honour-mays---manor-house',
+ alias: ['hm'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'adaralovestudio',
+ name: 'Adara Love Studio',
+ url: 'https://www.legalporno.com/studios/adara-love-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'faplex',
+ name: 'Faplex',
+ url: 'https://www.legalporno.com/studios/faplex',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analpornworld',
+ name: 'Anal Porn World',
+ url: 'https://www.legalporno.com/studios/anal-porn-world',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'xfreax',
+ name: 'XfreaX',
+ url: 'https://www.legalporno.com/studios/xfreax',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'rpsnstudio',
+ name: 'RPSN Studio',
+ url: 'https://www.legalporno.com/studios/rpsn-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'queeneugeniastudio',
+ name: 'Queen Eugenia Studio',
+ url: 'https://www.legalporno.com/studios/queen-eugenia-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'blessexxx',
+ name: 'Blessexxx',
+ url: 'https://www.legalporno.com/studios/blessexxx',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'thekrisskissexperience',
+ name: 'The Kriss Kiss Experience',
+ url: 'https://www.legalporno.com/studios/the-kriss-kiss-experience',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsxxxpawn',
+ name: 'XXXPawn',
+ url: 'https://www.legalporno.com/studios/xxxpawn',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsgaywire',
+ name: 'Gaywire',
+ url: 'https://www.legalporno.com/studios/gaywire',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsfilthyfamily',
+ name: 'Filthy Family',
+ url: 'https://www.legalporno.com/studios/filthy-family',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbrandibelle',
+ name: 'Brandi Belle',
+ url: 'https://www.legalporno.com/studios/brandi-belle',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidspublicinvasion',
+ name: 'Public Invasion',
+ url: 'https://www.legalporno.com/studios/public-invasion',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'kittyblairstudio',
+ name: 'Kitty Blair Studio',
+ url: 'https://www.legalporno.com/studios/kitty-blair-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'bustyadventures',
+ name: 'Busty Adventures',
+ url: 'https://www.legalporno.com/studios/busty-adventures',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidscollegerules',
+ name: 'College Rules',
+ url: 'https://www.legalporno.com/studios/college-rules',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'rockcorp',
+ name: 'Rock Corp',
+ url: 'https://www.legalporno.com/studios/rock-corp',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbarebackattack',
+ name: 'Bareback Attack',
+ url: 'https://www.legalporno.com/studios/bareback-attack',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbarebackcasting',
+ name: 'Bareback Casting',
+ url: 'https://www.legalporno.com/studios/bareback-casting',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsthughunter',
+ name: 'Thug Hunter',
+ url: 'https://www.legalporno.com/studios/thughunter',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsrubhim',
+ name: 'Rub Him',
+ url: 'https://www.legalporno.com/studios/rub-him',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsgaypawn',
+ name: 'Gay Pawn',
+ url: 'https://www.legalporno.com/studios/gay-pawn',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidspoundhisass',
+ name: 'PoundHisAss',
+ url: 'https://www.legalporno.com/studios/poundhisass',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsfuckyoucracker',
+ name: 'Fuck You Cracker',
+ url: 'https://www.legalporno.com/studios/fuck-you-cracker',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidstroopcandy',
+ name: 'Troop Candy',
+ url: 'https://www.legalporno.com/studios/troop-candy',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsgrabass',
+ name: 'Grab Ass',
+ url: 'https://www.legalporno.com/studios/grab-ass',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbutterloads',
+ name: 'Butter Loads',
+ url: 'https://www.legalporno.com/studios/butter-loads',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsprojectcitybus',
+ name: 'Project City Bus',
+ url: 'https://www.legalporno.com/studios/project-city-bus',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsexbf',
+ name: 'ExBF',
+ url: 'https://www.legalporno.com/studios/exbf',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsungloryhole',
+ name: 'UngloryHole',
+ url: 'https://www.legalporno.com/studios/ungloryhole',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'editafantasystudio',
+ name: 'Edita Fantasy Studio',
+ url: 'https://www.legalporno.com/studios/edita-fantasy-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsgaypatrol',
+ name: 'Gay Patrol',
+ url: 'https://www.legalporno.com/studios/gay-patrol',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsbaitbus',
+ name: 'Bait Bus',
+ url: 'https://www.legalporno.com/studios/bait-bus',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsoutinpublic',
+ name: 'Out In Public',
+ url: 'https://www.legalporno.com/studios/out-in-public',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'hornybelle',
+ name: 'Horny Belle',
+ url: 'https://www.legalporno.com/studios/horny-belle',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'suzieqstudio',
+ name: 'Suzie Q Studio',
+ url: 'https://www.legalporno.com/studios/suzie-q-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'cherryontop',
+ name: 'Cherry On Top',
+ url: 'https://www.legalporno.com/studios/cherry-on-top',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsimmorallive',
+ name: 'ImmoralLive',
+ url: 'https://www.legalporno.com/studios/immorallive',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'viragoldfilms',
+ name: 'Vira Gold Films',
+ url: 'https://www.legalporno.com/studios/vira-gold-films',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'mamboperv',
+ name: 'Mambo Perv',
+ url: 'https://www.legalporno.com/studios/mambo-perv',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'blackinwhite',
+ name: 'Black In White',
+ url: 'https://www.legalporno.com/studios/black-in-white',
+ alias: ['biw'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'pissinganalfantasy',
+ name: 'Pissing & Anal Fantasy',
+ url: 'https://www.legalporno.com/studios/pissing-anal-fantasy',
+ alias: ['paf'],
+ parent: 'legalporno',
+ },
+ {
+ slug: 'siswetanalambassador',
+ name: 'Siswet Anal Ambassador',
+ url: 'https://www.legalporno.com/studios/siswet-anal-ambassador',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'lizzylaynezentertainment',
+ name: 'Lizzy Laynez Entertainment',
+ url: 'https://www.legalporno.com/studios/lizzy-laynez-entertainment',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'gallastudio',
+ name: 'Galla Studio',
+ url: 'https://www.legalporno.com/studios/galla-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'shinaryenspurepleasure',
+ name: 'Shinaryen\'s Pure Pleasure',
+ url: 'https://www.legalporno.com/studios/shinaryen--s-pure-pleasure',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsculioneros',
+ name: 'Culioneros',
+ url: 'https://www.legalporno.com/studios/culioneros',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'hentaied',
+ name: 'Hentaied',
+ url: 'https://www.legalporno.com/studios/hentaied',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'lvtstudio',
+ name: 'LVT Studio',
+ url: 'https://www.legalporno.com/studios/lvt-studio',
+ parent: 'legalporno',
+ },
+ {
+ slug: 'analvidsitsgonnahurt',
+ name: 'It\'s Gonna Hurt',
+ url: 'https://www.legalporno.com/studios/its-gonna-hurt',
+ parent: 'legalporno',
+ },
+];
+
+const newStudios = [
+ {
+ name: 'Giorgio Grandi',
+ slug: 'giorgiograndi',
+ url: 'https://www.analvids.com/studios/giorgio-grandi',
+ },
+ {
+ name: 'Gonzo.com',
+ slug: 'gonzocom',
+ url: 'https://www.analvids.com/studios/gonzo_com',
+ },
+ {
+ name: 'NRX-Studio',
+ slug: 'nrxstudio',
+ url: 'https://www.analvids.com/studios/nrx-studio',
+ },
+ {
+ name: 'Porn World',
+ slug: 'pornworld',
+ url: 'https://www.analvids.com/studios/porn-world',
+ },
+ {
+ name: 'Anal Porn World',
+ slug: 'analpornworld',
+ url: 'https://www.analvids.com/studios/anal-porn-world',
+ },
+ {
+ name: "Giorgio's Lab",
+ slug: 'giorgioslab',
+ url: 'https://www.analvids.com/studios/giorgio--s-lab',
+ },
+ {
+ name: 'VK Studio',
+ slug: 'vkstudio',
+ url: 'https://www.analvids.com/studios/vk-studio',
+ },
+ {
+ name: 'Busted T-Girls',
+ slug: 'bustedtgirls',
+ url: 'https://www.analvids.com/studios/busted-t-girls',
+ },
+ {
+ name: 'Bang Bros',
+ slug: 'bangbros',
+ url: 'https://www.analvids.com/studios/bang-bros',
+ },
+ {
+ name: 'Yummy estudio',
+ slug: 'yummyestudio',
+ url: 'https://www.analvids.com/studios/yummy-estudio',
+ },
+ {
+ name: 'Angelo Godshack Original',
+ slug: 'angelogodshackoriginal',
+ url: 'https://www.analvids.com/studios/angelo-godshack-original',
+ },
+ {
+ name: 'XfreaX',
+ slug: 'xfreax',
+ url: 'https://www.analvids.com/studios/xfreax',
+ },
+ {
+ name: 'N F studio',
+ slug: 'nfstudio',
+ url: 'https://www.analvids.com/studios/nf-studio',
+ },
+ {
+ name: 'Black in White',
+ slug: 'blackinwhite',
+ url: 'https://www.analvids.com/studios/black-in-white',
+ },
+ {
+ name: 'LATIN TEENS productions',
+ slug: 'latinteensproductions',
+ url: 'https://www.analvids.com/studios/latin-teens-productions',
+ },
+ {
+ name: 'Vira Gold Films',
+ slug: 'viragoldfilms',
+ url: 'https://www.analvids.com/studios/vira-gold-films',
+ },
+ {
+ name: 'GGG BY JOHN THOMPSON',
+ slug: 'gggbyjohnthompson',
+ url: 'https://www.analvids.com/studios/ggg-by-john-thompson',
+ },
+ {
+ name: 'Private',
+ slug: 'private',
+ url: 'https://www.analvids.com/studios/private',
+ },
+ {
+ name: 'TheWonderToys Training Studio',
+ slug: 'thewondertoystrainingstudio',
+ url: 'https://www.analvids.com/studios/thewondertoys-training-studio',
+ },
+ {
+ name: 'Natasha Teen Productions',
+ slug: 'natashateenproductions',
+ url: 'https://www.analvids.com/studios/natasha-teen-productions',
+ },
+ {
+ name: 'Interracial Vision',
+ slug: 'interracialvision',
+ url: 'https://www.analvids.com/studios/interracial-vision',
+ },
+ {
+ name: 'PISSING E ANAL FANTASY',
+ slug: 'pissingeanalfantasy',
+ url: 'https://www.analvids.com/studios/pissing-anal-fantasy',
+ },
+ {
+ name: 'Rock Corp',
+ slug: 'rockcorp',
+ url: 'https://www.analvids.com/studios/rock-corp',
+ },
+ {
+ name: 'Mambo Perv',
+ slug: 'mamboperv',
+ url: 'https://www.analvids.com/studios/mambo-perv',
+ },
+ {
+ name: 'Stalker Prodz',
+ slug: 'stalkerprodz',
+ url: 'https://www.analvids.com/studios/stalker_prodz',
+ },
+ {
+ name: 'American Anal',
+ slug: 'americananal',
+ url: 'https://www.analvids.com/studios/american-anal',
+ },
+ {
+ name: 'LVT studio',
+ slug: 'lvtstudio',
+ url: 'https://www.analvids.com/studios/lvt-studio',
+ },
+ {
+ name: 'FAPLEX',
+ slug: 'faplex',
+ url: 'https://www.analvids.com/studios/faplex',
+ },
+ {
+ name: 'Toby Dick',
+ slug: 'tobydick',
+ url: 'https://www.analvids.com/studios/toby_dick',
+ },
+ {
+ name: 'Sineplex SOS',
+ slug: 'sineplexsos',
+ url: 'https://www.analvids.com/studios/sineplex-sos',
+ },
+ {
+ name: "Mr Anderson's Anal Academy ",
+ slug: 'mrandersonsanalacademy',
+ url: 'https://www.analvids.com/studios/mr-anderson--s-anal-academy',
+ },
+ {
+ name: 'Only3x Network',
+ slug: 'only3xnetwork',
+ url: 'https://www.analvids.com/studios/only3x_network_store',
+ },
+ {
+ name: 'Sineplex CZ',
+ slug: 'sineplexcz',
+ url: 'https://www.analvids.com/studios/sineplex-cz',
+ },
+ {
+ name: 'Galla Studio',
+ slug: 'gallastudio',
+ url: 'https://www.analvids.com/studios/galla-studio',
+ },
+ {
+ name: 'Rick Angel Studio',
+ slug: 'rickangelstudio',
+ url: 'https://www.analvids.com/studios/rick-angel-studio',
+ },
+ {
+ name: 'Siswet Anal Ambassador',
+ slug: 'siswetanalambassador',
+ url: 'https://www.analvids.com/studios/siswet-anal-ambassador',
+ },
+ {
+ name: 'BBC Master Joss Lescaf',
+ slug: 'bbcmasterjosslescaf',
+ url: 'https://www.analvids.com/studios/bbc-master',
+ },
+ {
+ name: 'Anal Maniacs by Lady Dee',
+ slug: 'analmaniacsbyladydee',
+ url: 'https://www.analvids.com/studios/anal-maniacs-by-lady-dee',
+ },
+ {
+ name: 'Freddy Gong ',
+ slug: 'freddygong',
+ url: 'https://www.analvids.com/studios/freddy_gong_',
+ },
+ {
+ name: 'Rebel Rhyder Productions',
+ slug: 'rebelrhyderproductions',
+ url: 'https://www.analvids.com/studios/rebel_rhyder_production',
+ },
+ {
+ name: 'RPSN Studio',
+ slug: 'rpsnstudio',
+ url: 'https://www.analvids.com/studios/rpsn-studio',
+ },
+ {
+ name: 'StacyBloomStudio',
+ slug: 'stacybloomstudio',
+ url: 'https://www.analvids.com/studios/stacy-bloom-studio',
+ },
+ {
+ name: 'SamCanRam',
+ slug: 'samcanram',
+ url: 'https://www.analvids.com/studios/samcanram',
+ },
+ {
+ name: 'Mamasita Savage',
+ slug: 'mamasitasavage',
+ url: 'https://www.analvids.com/studios/mamasita-savage',
+ },
+ {
+ name: 'Private Black',
+ slug: 'privateblack',
+ url: 'https://www.analvids.com/studios/private-black',
+ },
+ {
+ name: 'Studio PL',
+ slug: 'studiopl',
+ url: 'https://www.analvids.com/studios/studio_pl',
+ },
+ {
+ name: 'ssnatashateen',
+ slug: 'ssnatashateen',
+ url: 'https://www.analvids.com/studios/ssnatashateen',
+ },
+ {
+ name: 'Immorallive',
+ slug: 'immorallive',
+ url: 'https://www.analvids.com/studios/immorallive',
+ },
+ {
+ name: 'Andy Casanova',
+ slug: 'andycasanova',
+ url: 'https://www.analvids.com/studios/andy-casanova',
+ },
+ {
+ name: 'Adeline Lafouine Studio',
+ slug: 'adelinelafouinestudio',
+ url: 'https://www.analvids.com/studios/adeline-lafouine-studio',
+ },
+ {
+ name: 'Monika Fox',
+ slug: 'monikafox',
+ url: 'https://www.analvids.com/studios/monika_fox',
+ },
+ {
+ name: 'GG',
+ slug: 'gg',
+ url: 'https://www.analvids.com/studios/gg',
+ },
+ {
+ name: 'Kate Rich Studio',
+ slug: 'katerichstudio',
+ url: 'https://www.analvids.com/studios/kate-rich-studio',
+ },
+ {
+ name: 'Porn Force',
+ slug: 'pornforce',
+ url: 'https://www.analvids.com/studios/porn-force',
+ },
+ {
+ name: 'Lydia Black Studio',
+ slug: 'lydiablackstudio',
+ url: 'https://www.analvids.com/studios/lydia_black_',
+ },
+ {
+ name: "Claudia's Clips",
+ slug: 'claudiasclips',
+ url: 'https://www.analvids.com/studios/claudia--s-clips',
+ },
+ {
+ name: 'Sineplex Classic',
+ slug: 'sineplexclassic',
+ url: 'https://www.analvids.com/studios/sineplex-classic',
+ },
+ {
+ name: 'Erika Korti Studio',
+ slug: 'erikakortistudio',
+ url: 'https://www.analvids.com/studios/erika_korti_studio',
+ },
+ {
+ name: 'MLR production ',
+ slug: 'mlrproduction',
+ url: 'https://www.analvids.com/studios/mlr_production_',
+ },
+ {
+ name: 'Mugur Porn',
+ slug: 'mugurporn',
+ url: 'https://www.analvids.com/studios/mugur_porn',
+ },
+ {
+ name: 'Bad Bardot Club',
+ slug: 'badbardotclub',
+ url: 'https://www.analvids.com/studios/bad-bardot-club',
+ },
+ {
+ name: 'Mya Quinn Studio',
+ slug: 'myaquinnstudio',
+ url: 'https://www.analvids.com/studios/mya_quinn_studio',
+ },
+ {
+ name: 'dreamtranny',
+ slug: 'dreamtranny',
+ url: 'https://www.analvids.com/studios/dreamtranny',
+ },
+ {
+ name: 'Z-Filmz',
+ slug: 'zfilmz',
+ url: 'https://www.analvids.com/studios/z-films',
+ },
+ {
+ name: 'Anal 4 her',
+ slug: 'anal4her',
+ url: 'https://www.analvids.com/studios/anal-4-her',
+ },
+ {
+ name: 'Hentaied',
+ slug: 'hentaied',
+ url: 'https://www.analvids.com/studios/hentaied',
+ },
+ {
+ name: 'Kinky Sex',
+ slug: 'kinkysex',
+ url: 'https://www.analvids.com/studios/kinky-sex',
+ },
+ {
+ name: 'Briana Banderas Studio',
+ slug: 'brianabanderasstudio',
+ url: 'https://www.analvids.com/studios/briana-banderas-studio',
+ },
+ {
+ name: 'analgonzo',
+ slug: 'analgonzo',
+ url: 'https://www.analvids.com/studios/analgonzo',
+ },
+ {
+ name: 'dankreamer',
+ slug: 'dankreamer',
+ url: 'https://www.analvids.com/studios/dankreamer',
+ },
+ {
+ name: 'cherryflowerxxx',
+ slug: 'cherryflowerxxx',
+ url: 'https://www.analvids.com/studios/cherryflowerxxx',
+ },
+ {
+ name: 'Suzie Q Studio',
+ slug: 'suzieqstudio',
+ url: 'https://www.analvids.com/studios/suzie-q-studio',
+ },
+ {
+ name: 'Trunk AC Studio',
+ slug: 'trunkacstudio',
+ url: 'https://www.analvids.com/studios/oliver_trunk_ac_studio',
+ },
+ {
+ name: 'GothCharlotte',
+ slug: 'gothcharlotte',
+ url: 'https://www.analvids.com/studios/gothcharlotte',
+ },
+ {
+ name: 'SpicyLab Production',
+ slug: 'spicylabproduction',
+ url: 'https://www.analvids.com/studios/spicy_lab_productio',
+ },
+ {
+ name: 'MAX RAJOY SQUAD',
+ slug: 'maxrajoysquad',
+ url: 'https://www.analvids.com/studios/max-rajoy-squad',
+ },
+ {
+ name: 'Amor en equipo',
+ slug: 'amorenequipo',
+ url: 'https://www.analvids.com/studios/adara-love-studio',
+ },
+ {
+ name: 'Eden does',
+ slug: 'edendoes',
+ url: 'https://www.analvids.com/studios/eden-does',
+ },
+ {
+ name: 'Assablanca',
+ slug: 'assablanca',
+ url: 'https://www.analvids.com/studios/assablanca',
+ },
+ {
+ name: 'Mr Anal',
+ slug: 'mranal',
+ url: 'https://www.analvids.com/studios/mr-anal',
+ },
+ {
+ name: 'Cris Angelo',
+ slug: 'crisangelo',
+ url: 'https://www.analvids.com/studios/cris_angelo_-_bigdaddyproductions',
+ },
+ {
+ name: 'Vince Karter ',
+ slug: 'vincekarter',
+ url: 'https://www.analvids.com/studios/vince_karter_',
+ },
+ {
+ name: 'Pineapples Studio',
+ slug: 'pineapplesstudio',
+ url: 'https://www.analvids.com/studios/pineapple--s-test-kitchen',
+ },
+ {
+ name: 'Bazinga',
+ slug: 'bazinga',
+ url: 'https://www.analvids.com/studios/bazinga',
+ },
+ {
+ name: 'Harleen Van Hynten',
+ slug: 'harleenvanhynten',
+ url: 'https://www.analvids.com/studios/harleen-van-hynten-studio',
+ },
+ {
+ name: 'Sweetyx',
+ slug: 'sweetyx',
+ url: 'https://www.analvids.com/studios/jean-marie-corda-studio',
+ },
+ {
+ name: 'PanPorn',
+ slug: 'panporn',
+ url: 'https://www.analvids.com/studios/panporn-production',
+ },
+ {
+ name: 'Alt Perversion',
+ slug: 'altperversion',
+ url: 'https://www.analvids.com/studios/alt_perversion',
+ },
+ {
+ name: "Lutro's World",
+ slug: 'lutrosworld',
+ url: 'https://www.analvids.com/studios/lutro--s-world',
+ },
+ {
+ name: 'Mixed studios',
+ slug: 'mixedstudios',
+ url: 'https://www.analvids.com/studios/mixed-studios',
+ },
+ {
+ name: 'Lizzy Laynez Entertainment',
+ slug: 'lizzylaynezentertainment',
+ url: 'https://www.analvids.com/studios/lizzy-laynez-entertainment',
+ },
+ {
+ name: 'Melina May',
+ slug: 'melinamay',
+ url: 'https://www.analvids.com/studios/melina_may',
+ },
+ {
+ name: 'Outside the Studio',
+ slug: 'outsidethestudio',
+ url: 'https://www.analvids.com/studios/outside-the-studio',
+ },
+ {
+ name: 'Alix Lynx',
+ slug: 'alixlynx',
+ url: 'https://www.analvids.com/studios/alix--s-dream-world',
+ },
+ {
+ name: 'Argendana Official',
+ slug: 'argendanaofficial',
+ url: 'https://www.analvids.com/studios/argendana_official',
+ },
+ {
+ name: "YumYum's Studio",
+ slug: 'yumyumsstudio',
+ url: 'https://www.analvids.com/studios/yumyum_s_studio',
+ },
+ {
+ name: 'Culioneros',
+ slug: 'culioneros',
+ url: 'https://www.analvids.com/studios/culioneros',
+ },
+ {
+ name: "Shinaryen's Pure Pleasure",
+ slug: 'shinaryenspurepleasure',
+ url: 'https://www.analvids.com/studios/shinaryen--s-pure-pleasure',
+ },
+ {
+ name: 'johnpricexo',
+ slug: 'johnpricexo',
+ url: 'https://www.analvids.com/studios/johnpricexoxo',
+ },
+ {
+ name: 'Lara De Santis studio',
+ slug: 'laradesantisstudio',
+ url: 'https://www.analvids.com/studios/lara-de-santis-studio',
+ },
+ {
+ name: 'Cherry Aleksa ',
+ slug: 'cherryaleksa',
+ url: 'https://www.analvids.com/studios/cherry-aleksa-studio',
+ },
+ {
+ name: 'First Gape',
+ slug: 'firstgape',
+ url: 'https://www.analvids.com/studios/first-gape',
+ },
+ {
+ name: 'kaiiaeve',
+ slug: 'kaiiaeve',
+ url: 'https://www.analvids.com/studios/kaiiaeve',
+ },
+ {
+ name: 'PossiblyNeighbours',
+ slug: 'possiblyneighbours',
+ url: 'https://www.analvids.com/studios/possibly-neighbours',
+ },
+ {
+ name: 'Dorian Del Isla',
+ slug: 'doriandelisla',
+ url: 'https://www.analvids.com/studios/dorian_del_isla',
+ },
+ {
+ name: 'Sarah Slave Studio',
+ slug: 'sarahslavestudio',
+ url: 'https://www.analvids.com/studios/sarah_slave_studio',
+ },
+ {
+ name: 'Focus',
+ slug: 'focus',
+ url: 'https://www.analvids.com/studios/focus',
+ },
+ {
+ name: 'No Rest For The Ass',
+ slug: 'norestfortheass',
+ url: 'https://www.analvids.com/studios/no-rest-for-the-ass',
+ },
+ {
+ name: 'Atomic Porn Studio',
+ slug: 'atomicpornstudio',
+ url: 'https://www.analvids.com/studios/mlr_studio',
+ },
+ {
+ name: 'Alexa Moore',
+ slug: 'alexamoore',
+ url: 'https://www.analvids.com/studios/alexa_moore',
+ },
+ {
+ name: 'Jessae Rosae x Savory Father',
+ slug: 'jessaerosaexsavoryfather',
+ url: 'https://www.analvids.com/studios/jessae_rosae_x_savory_father',
+ },
+ {
+ name: 'Proton Videos',
+ slug: 'protonvideos',
+ url: 'https://www.analvids.com/studios/proton-videos',
+ },
+ {
+ name: 'Timea Bella Production',
+ slug: 'timeabellaproduction',
+ url: 'https://www.analvids.com/studios/timea-bella-production',
+ },
+ {
+ name: 'Eros Mastery',
+ slug: 'erosmastery',
+ url: 'https://www.analvids.com/studios/eros_mastery',
+ },
+ {
+ name: 'MaryWet',
+ slug: 'marywet',
+ url: 'https://www.analvids.com/studios/marywet',
+ },
+ {
+ name: 'Cassie Del Isla',
+ slug: 'cassiedelisla',
+ url: 'https://www.analvids.com/studios/del-isla',
+ },
+ {
+ name: 'DaddyEnjoy',
+ slug: 'daddyenjoy',
+ url: 'https://www.analvids.com/studios/daddyenjoy',
+ },
+ {
+ name: 'Gonzo in Brazil',
+ slug: 'gonzoinbrazil',
+ url: 'https://www.analvids.com/studios/gonzo-in-brazil',
+ },
+ {
+ name: 'Private Castings',
+ slug: 'privatecastings',
+ url: 'https://www.analvids.com/studios/private-castings',
+ },
+ {
+ name: 'XXXPawn',
+ slug: 'xxxpawn',
+ url: 'https://www.analvids.com/studios/xxxpawn',
+ },
+ {
+ name: 'MyBangVan',
+ slug: 'mybangvan',
+ url: 'https://www.analvids.com/studios/mybangvan',
+ },
+ {
+ name: 'Cherry on Top',
+ slug: 'cherryontop',
+ url: 'https://www.analvids.com/studios/cherry-on-top',
+ },
+ {
+ name: 'Gaywire',
+ slug: 'gaywire',
+ url: 'https://www.analvids.com/studios/gaywire',
+ },
+ {
+ name: 'College Rules',
+ slug: 'collegerules',
+ url: 'https://www.analvids.com/studios/college-rules',
+ },
+ {
+ name: 'onlyjewelzblu Studio',
+ slug: 'onlyjewelzblustudio',
+ url: 'https://www.analvids.com/studios/onlyjewelzblu_studio',
+ },
+ {
+ name: 'Kitty Blair',
+ slug: 'kittyblair',
+ url: 'https://www.analvids.com/studios/kitty-blair-studio',
+ },
+ {
+ name: 'Queen Eugenia Studio',
+ slug: 'queeneugeniastudio',
+ url: 'https://www.analvids.com/studios/queen-eugenia-studio',
+ },
+ {
+ name: 'Studio GD',
+ slug: 'studiogd',
+ url: 'https://www.analvids.com/studios/studio-dg',
+ },
+ {
+ name: 'Tarra White',
+ slug: 'tarrawhite',
+ url: 'https://www.analvids.com/studios/tarra-white',
+ },
+ {
+ name: 'Filthy Family',
+ slug: 'filthyfamily',
+ url: 'https://www.analvids.com/studios/filthy-family',
+ },
+ {
+ name: 'CHERRYxLUCKY',
+ slug: 'cherryxlucky',
+ url: 'https://www.analvids.com/studios/cherryxlucky',
+ },
+ {
+ name: 'Busty Adventures',
+ slug: 'bustyadventures',
+ url: 'https://www.analvids.com/studios/busty-adventures',
+ },
+ {
+ name: 'Helena Moeller Studio',
+ slug: 'helenamoellerstudio',
+ url: 'https://www.analvids.com/studios/helena-moeller-studio',
+ },
+ {
+ name: "man's cave",
+ slug: 'manscave',
+ url: 'https://www.analvids.com/studios/mancave',
+ },
+ {
+ name: 'Jamie French Productions',
+ slug: 'jamiefrenchproductions',
+ url: 'https://www.analvids.com/studios/jamie_french_productions',
+ },
+ {
+ name: 'Public Invasion',
+ slug: 'publicinvasion',
+ url: 'https://www.analvids.com/studios/public-invasion',
+ },
+ {
+ name: 'Omar Galanti Productions',
+ slug: 'omargalantiproductions',
+ url: 'https://www.analvids.com/studios/omar-galanti-productions',
+ },
+ {
+ name: 'InfiltrateProxy',
+ slug: 'infiltrateproxy',
+ url: 'https://www.analvids.com/studios/infiltrateproxy',
+ },
+ {
+ name: 'All About Sweet Bunny',
+ slug: 'allaboutsweetbunny',
+ url: 'https://www.analvids.com/studios/all-about-sweet-bunny',
+ },
+ {
+ name: 'Anal pantyhose addicts ',
+ slug: 'analpantyhoseaddicts',
+ url: 'https://www.analvids.com/studios/anal_pantyhose_addicts_',
+ },
+ {
+ name: 'Nelly Kent Studio',
+ slug: 'nellykentstudio',
+ url: 'https://www.analvids.com/studios/nelly-kent-production',
+ },
+ {
+ name: 'The Kriss Kiss Experience',
+ slug: 'thekrisskissexperience',
+ url: 'https://www.analvids.com/studios/the-kriss-kiss-experience',
+ },
+ {
+ name: 'F Models',
+ slug: 'fmodels',
+ url: 'https://www.analvids.com/studios/f-models',
+ },
+ {
+ name: 'Luna Sapphire ',
+ slug: 'lunasapphire',
+ url: 'https://www.analvids.com/studios/luna_sapphire_',
+ },
+ {
+ name: 'Tiffany Leiddi ',
+ slug: 'tiffanyleiddi',
+ url: 'https://www.analvids.com/studios/tiffany_leiddi_',
+ },
+ {
+ name: 'Blessexxx',
+ slug: 'blessexxx',
+ url: 'https://www.analvids.com/studios/blessexxx',
+ },
+ {
+ name: 'CarryLight',
+ slug: 'carrylight',
+ url: 'https://www.analvids.com/studios/carrylight',
+ },
+ {
+ name: 'Horny Belle',
+ slug: 'hornybelle',
+ url: 'https://www.analvids.com/studios/horny-belle',
+ },
+ {
+ name: 'GOSTOSAS VIDEO ',
+ slug: 'gostosasvideo',
+ url: 'https://www.analvids.com/studios/gostosas_video_',
+ },
+ {
+ name: "Rebecca's Clips",
+ slug: 'rebeccasclips',
+ url: 'https://www.analvids.com/studios/rebecca--s-clips',
+ },
+ {
+ name: 'Brandi Belle',
+ slug: 'brandibelle',
+ url: 'https://www.analvids.com/studios/brandi-belle',
+ },
+ {
+ name: 'Club Candy Alexa',
+ slug: 'clubcandyalexa',
+ url: 'https://www.analvids.com/studios/club-candy-alexa',
+ },
+ {
+ name: 'Bareback Attack',
+ slug: 'barebackattack',
+ url: 'https://www.analvids.com/studios/bareback-attack',
+ },
+ {
+ name: 'bdsmmanga',
+ slug: 'bdsmmanga',
+ url: 'https://www.analvids.com/studios/bdsmmanga',
+ },
+ {
+ name: 'ViSpace',
+ slug: 'vispace',
+ url: 'https://www.analvids.com/studios/vispace',
+ },
+ {
+ name: 'Sophie Ladder',
+ slug: 'sophieladder',
+ url: 'https://www.analvids.com/studios/sophie_ladder',
+ },
+ {
+ name: 'vangoren',
+ slug: 'vangoren',
+ url: 'https://www.analvids.com/studios/vangoren',
+ },
+ {
+ name: 'DreamInSkies',
+ slug: 'dreaminskies',
+ url: 'https://www.analvids.com/studios/dreaminskies',
+ },
+ {
+ name: 'Its Gonna Hurt',
+ slug: 'itsgonnahurt',
+ url: 'https://www.analvids.com/studios/its-gonna-hurt',
+ },
+ {
+ name: 'CRUNCHBOY',
+ slug: 'crunchboy',
+ url: 'https://www.analvids.com/studios/crunchboy',
+ },
+ {
+ name: 'BigDaddy Raw',
+ slug: 'bigdaddyraw',
+ url: 'https://www.analvids.com/studios/bigdaddy_raw',
+ },
+ {
+ name: 'Hairy Gonzo',
+ slug: 'hairygonzo',
+ url: 'https://www.analvids.com/studios/hairy-gonzo',
+ },
+ {
+ name: 'Rub Him',
+ slug: 'rubhim',
+ url: 'https://www.analvids.com/studios/rub-him',
+ },
+ {
+ name: 'UngloryHole',
+ slug: 'ungloryhole',
+ url: 'https://www.analvids.com/studios/ungloryhole',
+ },
+ {
+ name: 'Bareback Casting',
+ slug: 'barebackcasting',
+ url: 'https://www.analvids.com/studios/bareback-casting',
+ },
+ {
+ name: 'JuliaKissy',
+ slug: 'juliakissy',
+ url: 'https://www.analvids.com/studios/juliasquirt',
+ },
+ {
+ name: "Honour Mays' Manor House",
+ slug: 'honourmaysmanorhouse',
+ url: 'https://www.analvids.com/studios/honour-mays---manor-house',
+ },
+ {
+ name: 'StaceyAlexisPawg',
+ slug: 'staceyalexispawg',
+ url: 'https://www.analvids.com/studios/staceyalexispawg',
+ },
+ {
+ name: 'Gay Patrol',
+ slug: 'gaypatrol',
+ url: 'https://www.analvids.com/studios/gay-patrol',
+ },
+ {
+ name: 'Thug Hunter',
+ slug: 'thughunter',
+ url: 'https://www.analvids.com/studios/thug-hunter',
+ },
+ {
+ name: 'SexyNEBBW',
+ slug: 'sexynebbw',
+ url: 'https://www.analvids.com/studios/sexynebbw',
+ },
+ {
+ name: 'Out In Public',
+ slug: 'outinpublic',
+ url: 'https://www.analvids.com/studios/out-in-public',
+ },
+ {
+ name: 'Poundhisass',
+ slug: 'poundhisass',
+ url: 'https://www.analvids.com/studios/poundhisass',
+ },
+ {
+ name: 'Bait Bus',
+ slug: 'baitbus',
+ url: 'https://www.analvids.com/studios/bait-bus-',
+ },
+ {
+ name: 'Diamonds Production',
+ slug: 'diamondsproduction',
+ url: 'https://www.analvids.com/studios/diamonds_production',
+ },
+ {
+ name: 'Sinemale',
+ slug: 'sinemale',
+ url: 'https://www.analvids.com/studios/sinemale',
+ },
+ {
+ name: 'Fuck You Cracker',
+ slug: 'fuckyoucracker',
+ url: 'https://www.analvids.com/studios/fuck-you-cracker',
+ },
+ {
+ name: 'Dark_Lady77',
+ slug: 'darklady77',
+ url: 'https://www.analvids.com/studios/dark_lady77',
+ },
+ {
+ name: 'Butter Loads',
+ slug: 'butterloads',
+ url: 'https://www.analvids.com/studios/butter-loads',
+ },
+ {
+ name: 'Edita Fantasy Studio',
+ slug: 'editafantasystudio',
+ url: 'https://www.analvids.com/studios/edita-fantasy-studio',
+ },
+ {
+ name: 'Gay Pawn',
+ slug: 'gaypawn',
+ url: 'https://www.analvids.com/studios/gay-pawn',
+ },
+ {
+ name: 'Troop Candy',
+ slug: 'troopcandy',
+ url: 'https://www.analvids.com/studios/troop-candy',
+ },
+ {
+ name: 'Grab Ass',
+ slug: 'grabass',
+ url: 'https://www.analvids.com/studios/grab-ass',
+ },
+ {
+ name: 'sydneyscreams4u',
+ slug: 'sydneyscreams4u',
+ url: 'https://www.analvids.com/studios/sydneyscreams4u',
+ },
+ {
+ name: 'Nade Nasty ',
+ slug: 'nadenasty',
+ url: 'https://www.analvids.com/studios/nade_nasty_',
+ },
+ {
+ name: 'ExBF',
+ slug: 'exbf',
+ url: 'https://www.analvids.com/studios/exbf',
+ },
+ {
+ name: 'Project City Bus',
+ slug: 'projectcitybus',
+ url: 'https://www.analvids.com/studios/project-city-bus',
+ },
+ {
+ name: 'Nylon Lingerie Studio',
+ slug: 'nylonlingeriestudio',
+ url: 'https://www.analvids.com/studios/nylon_lingerie_studio',
+ },
+];
+
+async function init() {
+ const studios = newStudios.map((studio) => {
+ const newStudio = { ...studio, parent: 'analvids' };
+ const oldStudio = oldStudios.find((oldStudioX) => oldStudioX.slug === studio.slug);
+
+ if (oldStudio?.alias) {
+ newStudio.alias = oldStudio.alias;
+ }
+
+ if (oldStudio?.tags) {
+ newStudio.tags = oldStudio.tags;
+ }
+
+ return newStudio;
+ });
+
+ await fs.writeFile('./analvids-studios.json', JSON.stringify(studios, null, 4));
+
+ console.log(studios);
+}
+
+init();
diff --git a/src/tools/knex-update.js b/src/tools/knex-update.js
new file mode 100644
index 00000000..6ade1a80
--- /dev/null
+++ b/src/tools/knex-update.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const knex = require('../knex');
+
+async function update() {
+ const query = knex('bans')
+ .update('type', {
+ type: 'mute',
+ username_original: 'charles',
+ })
+ .where('id', 2754);
+
+ console.log(query.toSQL());
+
+ await query;
+}
+
+update();
diff --git a/src/tools/realitykings.js b/src/tools/realitykings.js
new file mode 100644
index 00000000..91b4a09d
--- /dev/null
+++ b/src/tools/realitykings.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const fetch = require('node-fetch');
+const express = require('express');
+
+async function init() {
+ const res = await fetch('https://www.realitykings.com/scenes?site=45', {
+ method: 'HEAD',
+ headers: {
+ 'user-agent': 'HTTPie/2.6.0',
+ 'accept-encoding': 'gzip, deflate, br',
+ accept: '*/*',
+ connection: 'keep-alive',
+ },
+ });
+
+ console.log(res.status, res.headers);
+
+ const app = express();
+
+ app.get('/', (appReq, appRes) => {
+ console.log(appReq.headers);
+ appRes.status(204).send();
+ });
+
+ app.listen(8000, () => {
+ console.log('Listening on port 8000');
+
+ fetch('http://127.0.0.1:8000', {
+ headers: {
+ 'user-agent': 'HTTPie/2.6.0',
+ 'accept-encoding': 'gzip, deflate, br',
+ accept: '*/*',
+ connection: 'keep-alive',
+ },
+ });
+ });
+}
+
+init();
diff --git a/src/updates.js b/src/updates.js
index 5cff3b9b..7540b47f 100644
--- a/src/updates.js
+++ b/src/updates.js
@@ -8,6 +8,7 @@ const argv = require('./argv');
const logger = require('./logger')(__filename);
const knex = require('./knex');
const { curateRelease } = require('./releases');
+const chunk = require('./utils/chunk');
const include = require('./utils/argv-include')(argv);
const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve');
const { fetchIncludedEntities } = require('./entities');
@@ -38,22 +39,27 @@ function filterLocalUniqueReleases(releases, accReleases) {
}
async function filterUniqueReleases(releases) {
- const releaseIdentifiers = releases
- .map((release) => [release.entity.id, release.entryId]);
+ const releaseIdentifierChunks = chunk(releases.map((release) => [release.entity.id, release.entryId.toString()]));
- const duplicateReleaseEntries = await knex('releases')
- .select(knex.raw('releases.*, row_to_json(entities) as entity'))
- .leftJoin('entities', 'entities.id', 'releases.entity_id')
- .whereIn(['entity_id', 'entry_id'], releaseIdentifiers)
- .where((builder) => {
- // check if previously upcoming scenes can be excluded from duplicates to be rescraped for release day updates
- builder
- .where('deep', true) // scene is already deep scraped
- .orWhereNull('date')
- .orWhereNotIn('date_precision', ['day', 'minute']) // don't worry about scenes without (accurate) dates for now
- .orWhere(knex.raw('date > NOW() - INTERVAL \'12 hours\'')) // scene is still upcoming, with a rough offset to wait for the end of the day west of UTC
- .orWhere(knex.raw('updated_at - date > INTERVAL \'1 day\'')); // scene was updated after the release date, no updates expected
- });
+ const duplicateReleaseEntryChunks = await Promise.map(releaseIdentifierChunks, async (releaseIdentifiers) => {
+ const duplicateReleaseEntriesQuery = knex('releases')
+ .select(knex.raw('releases.*, row_to_json(entities) as entity'))
+ .leftJoin('entities', 'entities.id', 'releases.entity_id')
+ .whereIn(['entity_id', 'entry_id'], releaseIdentifiers)
+ .where((builder) => {
+ // check if previously upcoming scenes can be excluded from duplicates to be rescraped for release day updates
+ builder
+ .where('deep', true) // scene is already deep scraped
+ .orWhereNull('date')
+ .orWhereNotIn('date_precision', ['day', 'minute']) // don't worry about scenes without (accurate) dates for now
+ .orWhere(knex.raw('date > NOW() - INTERVAL \'12 hours\'')) // scene is still upcoming, with a rough offset to wait for the end of the day west of UTC
+ .orWhere(knex.raw('updated_at - date > INTERVAL \'1 day\'')); // scene was updated after the release date, no updates expected
+ });
+
+ return duplicateReleaseEntriesQuery;
+ }, { concurrency: 10 });
+
+ const duplicateReleaseEntries = duplicateReleaseEntryChunks.flat();
const duplicateReleases = duplicateReleaseEntries.map((release) => curateRelease(release));
const duplicateReleasesByEntityIdAndEntryId = duplicateReleases.reduce(mapReleasesToEntityIdAndEntryId, {});
@@ -261,8 +267,21 @@ async function scrapeNetworkSequential(networkEntity) {
return releases.uniqueReleases;
}
+async function getBeforeNetwork(networkEntity) {
+ try {
+ const parameters = getRecursiveParameters(networkEntity);
+ return await networkEntity.scraper?.beforeNetwork?.(networkEntity, parameters);
+ } catch (error) {
+ if (networkEntity.scraper?.requireBeforeNetwork === false) {
+ return null;
+ }
+
+ throw error;
+ }
+}
+
async function scrapeNetworkParallel(networkEntity) {
- const beforeNetwork = await networkEntity.scraper.beforeNetwork?.(networkEntity);
+ const beforeNetwork = await getBeforeNetwork(networkEntity);
return Promise.map(
networkEntity.includedChildren,
diff --git a/src/utils/http-windows.js b/src/utils/http-windows.js
new file mode 100644
index 00000000..4fd928aa
--- /dev/null
+++ b/src/utils/http-windows.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = new Map();
diff --git a/src/utils/http.js b/src/utils/http.js
index 17c4289e..98e4f186 100644
--- a/src/utils/http.js
+++ b/src/utils/http.js
@@ -3,12 +3,15 @@
const config = require('config');
const Promise = require('bluebird');
const bhttp = require('bhttp');
+const fs = require('fs').promises;
const util = require('util');
const stream = require('stream');
const tunnel = require('tunnel');
const Bottleneck = require('bottleneck');
const { JSDOM, toughCookie } = require('jsdom');
+const windows = require('./http-windows');
+
const logger = require('../logger')(__filename);
const virtualConsole = require('./virtual-console')(__filename);
const argv = require('../argv');
@@ -78,8 +81,6 @@ function getLimiter(options = {}, url) {
});
}
- limiters[interval][concurrency].on('queued', () => logger.silly(`Queued ${url}`));
-
return limiters[interval][concurrency];
}
@@ -116,12 +117,23 @@ async function finalizeResult(res, options) {
if (Buffer.isBuffer(res.body)) {
const html = res.body.toString();
const window = options?.parse ? new JSDOM(html, { virtualConsole, ...options.extract }).window : null;
+ const pathname = new URL(res.request.url).pathname.replace(/\//g, '_');
+
+ // allow window.close to be called after scraping is done, only for deep scrapes where the URL is known outside the scraper
+ if (window && /fetchScene|fetchMovie/.test(new Error().stack)) {
+ windows.set(pathname, window);
+ }
+
+ if (argv.saveHtml) {
+ await fs.writeFile(`./html/${pathname}.html`, html);
+ }
return {
...res,
body: html,
html,
status: res.statusCode,
+ headers: res.headers,
document: window?.document || null,
window,
ok: res.statusCode >= 200 && res.statusCode <= 299,
@@ -132,6 +144,7 @@ async function finalizeResult(res, options) {
...res,
body: res.body,
status: res.statusCode,
+ headers: res.headers,
ok: res.statusCode >= 200 && res.statusCode <= 299,
};
}
diff --git a/src/utils/jsdom-perf.js b/src/utils/jsdom-perf.js
new file mode 100644
index 00000000..3a31a722
--- /dev/null
+++ b/src/utils/jsdom-perf.js
@@ -0,0 +1,38 @@
+'use strict';
+
+const util = require('util');
+const fs = require('fs').promises;
+const Promise = require('bluebird');
+const { JSDOM } = require('jsdom');
+
+const waitImmediate = util.promisify(setImmediate);
+
+async function init() {
+ let peak = 0;
+ const files = await fs.readdir('./html');
+
+ // const dom = new JSDOM('', { runScripts: 'dangerously' });
+
+ await Promise.map(Array.from({ length: 10 }).map(() => files).flat(), async (filename) => {
+ const html = await fs.readFile(`./html/${filename}`, 'utf8');
+ const dom = new JSDOM(html);
+
+ // dom.window.document.body.innerHTML = html;
+ dom.window.close();
+
+ const usage = process.memoryUsage.rss() / 1000000;
+ peak = Math.max(usage, peak);
+
+ console.log(`Memory usage: ${usage.toFixed(2)} MB, peak ${peak.toFixed(2)} MB`);
+
+ await waitImmediate;
+ }, {
+ concurrency: 1,
+ });
+
+ await Promise.delay(2000);
+
+ console.log(`Final memory usage: ${(process.memoryUsage.rss() / 1000000).toFixed(2)} MB, max ${peak.toFixed(2)} MB`);
+}
+
+init();
diff --git a/src/utils/media.js b/src/utils/media.js
index 2bb08888..b5355ce1 100644
--- a/src/utils/media.js
+++ b/src/utils/media.js
@@ -6,7 +6,7 @@ const fsPromises = require('fs').promises;
const Promise = require('bluebird');
const blake2 = require('blake2');
const sharp = require('sharp');
-const nanoid = require('nanoid');
+const { nanoid } = require('nanoid');
const { PassThrough } = require('stream');
const http = require('./http');
diff --git a/src/utils/qu.js b/src/utils/qu.js
index 2951994b..302674b3 100644
--- a/src/utils/qu.js
+++ b/src/utils/qu.js
@@ -16,6 +16,10 @@ function trim(str) {
}
function extractDate(dateString, format, match) {
+ if (!dateString) {
+ return null;
+ }
+
if (match) {
const dateStamp = trim(dateString).match(match);
@@ -80,32 +84,69 @@ function prefixUrl(urlValue, origin, protocol = 'https') {
return urlValue;
}
-function q(context, selector, attrArg, applyTrim = true) {
- if (!selector && context.nodeName === '#document') {
+function iterateXPathResult(iterator, results = []) {
+ const element = iterator.iterateNext();
+
+ if (element) {
+ return iterateXPathResult(iterator, [...results, element]);
+ }
+
+ return results;
+}
+
+function getElements(context, selector, first = false) {
+ if (!selector) {
+ return context;
+ }
+
+ if (/^\/\//.test(selector)) {
+ // XPath selector
+ const iterator = globalWindow.document.evaluate(selector, context, null, globalWindow.XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+
+ if (first) {
+ return iterator.iterateNext();
+ }
+
+ return iterateXPathResult(iterator);
+ }
+
+ if (first) {
+ return context.querySelector(selector);
+ }
+
+ return Array.from(context.querySelectorAll(selector));
+}
+
+function q(context, selectors, attrArg, applyTrim = true) {
+ if (!selectors && context.nodeName === '#document') {
return null;
}
const attr = attrArg === true ? 'textContent' : attrArg;
+ const element = [].concat(selectors).reduce((acc, selector) => acc || getElements(context, selector, true), null);
+
+ if (!element) {
+ return null;
+ }
if (attr) {
- const value = selector
- ? context.querySelector(selector)?.[attr] || context.querySelector(selector)?.attributes[attr]?.value
- : context[attr] || context.getAttribute(attr);
+ const value = element[attr] || element.getAttribute(attr);
return applyTrim && typeof value === 'string' ? trim(value) : value;
}
- return selector ? context.querySelector(selector) : context;
+ return element;
}
-function all(context, selector, attrArg, applyTrim = true) {
+function all(context, selectors, attrArg, applyTrim = true) {
const attr = attrArg === true ? 'textContent' : attrArg;
+ const elements = [].concat(selectors).reduce((acc, selector) => acc || getElements(context, selector), null);
if (attr) {
- return Array.from(context.querySelectorAll(selector), (el) => q(el, null, attr, applyTrim));
+ return elements.map((el) => q(el, null, attr, applyTrim));
}
- return Array.from(context.querySelectorAll(selector));
+ return elements;
}
function exists(context, selector) {
@@ -130,6 +171,42 @@ function html(context, selector) {
return el && el.innerHTML;
}
+function htmls(context, selector) {
+ const els = all(context, selector, null, true);
+
+ return els.map((el) => el.innerHTML);
+}
+
+function execute(context, selector = 'script') {
+ const scripts = htmls(context, selector);
+ const originalGlobal = Object.fromEntries(Object.entries(global));
+
+ const errors = scripts?.reduce((accErrors, script) => {
+ try {
+ Function(script)(); /* eslint-disable-line no-new-func */
+
+ return accErrors;
+ } catch (error) {
+ // the script failed
+ return [...accErrors, error];
+ }
+ }, []);
+
+ const data = Object.fromEntries(Object.entries(global).filter(([key, value]) => {
+ if (originalGlobal[key] !== value) {
+ delete global[key];
+ return true;
+ }
+
+ return false;
+ }));
+
+ return {
+ ...data,
+ errors,
+ };
+}
+
function json(context, selector) {
const el = q(context, selector, null, true);
@@ -152,12 +229,6 @@ function jsons(context, selector) {
});
}
-function htmls(context, selector) {
- const els = all(context, selector, null, true);
-
- return els.map((el) => el.innerHTML);
-}
-
function texts(context, selector, applyTrim = true, filter = true) {
const el = q(context, selector, null, applyTrim);
if (!el) return null;
@@ -425,6 +496,8 @@ const quFuncs = {
duration,
el: q,
element: q,
+ execute,
+ exec: execute,
exists,
html,
htmls,
diff --git a/src/utils/s3.js b/src/utils/s3.js
index 6b2690a0..e4c77a2b 100644
--- a/src/utils/s3.js
+++ b/src/utils/s3.js
@@ -3,7 +3,7 @@
const config = require('config');
const AWS = require('aws-sdk');
const fs = require('fs');
-const nanoid = require('nanoid');
+const { nanoid } = require('nanoid');
async function init() {
const filepath = './public/img/sfw/animals/j0iiByCxGfA.jpeg';
diff --git a/src/utils/upsert.js b/src/utils/upsert.js
index a78297fa..88f53297 100644
--- a/src/utils/upsert.js
+++ b/src/utils/upsert.js
@@ -33,8 +33,8 @@ async function upsert(table, items, identifier = ['id'], _knex) {
logger.debug(`${table}: Updating ${update.length}`);
const [inserted, updated] = await Promise.all([
- knex(table).returning('*').insert(insert),
- knex.transaction(async (trx) => Promise.all(update.map((item) => {
+ insert.length > 0 ? knex(table).returning('*').insert(insert) : [],
+ update.length > 0 ? knex.transaction(async (trx) => Promise.all(update.map((item) => {
const clause = identifiers.reduce((acc, identifierX) => ({ ...acc, [identifierX]: item[identifierX] }), {});
return trx
@@ -42,7 +42,7 @@ async function upsert(table, items, identifier = ['id'], _knex) {
.update(item)
.into(table)
.returning('*');
- }))),
+ }))) : [],
]);
return {