Merge branch 'experimental'
|  | @ -43,7 +43,7 @@ | |||
| 			target="_blank" | ||||
| 			rel="noopener noreferrer" | ||||
| 			class="date" | ||||
| 		>{{ formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) }}</a> | ||||
| 		><Icon icon="share2" />{{ formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) }}</a> | ||||
| 
 | ||||
| 		<a | ||||
| 			v-else | ||||
|  | @ -104,22 +104,16 @@ export default { | |||
|     padding: .5rem; | ||||
|     color: var(--text-light); | ||||
|     text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| .date { | ||||
|     &.new:before { | ||||
|         content: ''; | ||||
|         background: var(--primary); | ||||
|         width: .5rem; | ||||
|         display: inline-block; | ||||
|         position: absolute; | ||||
|         top: 0; | ||||
|         bottom: 0; | ||||
|         left: -.5rem; | ||||
|     } | ||||
| 	.icon { | ||||
| 		fill: var(--lighten-weak); | ||||
| 		margin: 0 .25rem 0 0; | ||||
| 	} | ||||
| 
 | ||||
| 	&:hover .icon { | ||||
| 		fill: var(--text-light); | ||||
| 	} | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| .site-link { | ||||
|     display: flex; | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ | |||
| 			> | ||||
| 				<h3 | ||||
| 					v-if="release.title" | ||||
| 					v-tooltip.top="release.title" | ||||
| 					v-tooltip.bottom="release.title" | ||||
| 					:title="release.title" | ||||
| 					class="title" | ||||
| 				>{{ release.title }}</h3> | ||||
|  |  | |||
|  | @ -89,6 +89,7 @@ async function mounted() { | |||
| 		], | ||||
| 		roleplay: [ | ||||
| 			'family', | ||||
| 			'parody', | ||||
| 			'schoolgirl', | ||||
| 			'nurse', | ||||
| 			'maid', | ||||
|  |  | |||
|  | @ -1,10 +1,13 @@ | |||
| <template> | ||||
| 	<router-link | ||||
| 		:to="{ name: 'tag', params: { tagSlug: tag.slug, range: 'latest' } }" | ||||
| 		:title="tag.name" | ||||
| 	<div | ||||
| 		v-if="tag.poster" | ||||
| 		class="tile" | ||||
| 	> | ||||
| 		<template v-if="tag.poster"> | ||||
| 		<router-link | ||||
| 			:to="{ name: 'tag', params: { tagSlug: tag.slug, range: 'latest' } }" | ||||
| 			:title="tag.name" | ||||
| 			class="poster-link" | ||||
| 		> | ||||
| 			<img | ||||
| 				v-if="!lazy && !sfw" | ||||
| 				:src="`/img/${tag.poster.thumbnail}`" | ||||
|  | @ -38,10 +41,19 @@ | |||
| 				:alt="tag.name" | ||||
| 				class="poster" | ||||
| 			> | ||||
| 		</template> | ||||
| 		</router-link> | ||||
| 
 | ||||
| 		<span class="title">{{ tag.name }}</span> | ||||
| 	</router-link> | ||||
| 		<router-link | ||||
| 			class="title" | ||||
| 			:to="{ name: 'tag', params: { tagSlug: tag.slug, range: 'latest' } }" | ||||
| 			:title="tag.name" | ||||
| 		>{{ tag.name }}</router-link> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<span | ||||
| 		v-else | ||||
| 		class="title" | ||||
| 	>{{ tag.name }}</span> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
|  | @ -70,51 +82,35 @@ export default { | |||
| @import 'theme'; | ||||
| 
 | ||||
| .tile { | ||||
|     color: var(--text-light); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: flex-end; | ||||
|     box-sizing: border-box; | ||||
|     position: relative; | ||||
| 	text-decoration: none; | ||||
|     box-shadow: 0 0 3px var(--darken-weak); | ||||
| 	overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .poster { | ||||
|     width: 100%; | ||||
| 	height: 100%; | ||||
|     object-fit: cover; | ||||
|     object-position: 50% 100%; | ||||
|     box-shadow: 0 0 3px var(--darken-weak); | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
| 	display: inline-block; | ||||
|     box-sizing: border-box; | ||||
|     padding: .5rem 1rem; | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     background: var(--darken); | ||||
|     padding: .25rem .5rem .25rem 1rem; | ||||
| 	overflow: hidden; | ||||
| 	white-space: nowrap; | ||||
|     font-size: 1rem; | ||||
|     color: var(--shadow-strong); | ||||
| 	text-decoration: none; | ||||
|     font-weight: bold; | ||||
|     text-transform: capitalize; | ||||
|     text-shadow: 0 0 3px var(--darken-strong); | ||||
| 	text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| @media(max-width: $breakpoint) { | ||||
| 	.tile { | ||||
| 		position: initial; | ||||
| 	} | ||||
| .poster-link:hover + .title, | ||||
| .title:hover { | ||||
| 	color: var(--primary) | ||||
| } | ||||
| 
 | ||||
| 	.title { | ||||
| 		position: initial; | ||||
| 		color: var(--text); | ||||
| 		background: var(--background); | ||||
| 		text-shadow: none; | ||||
| 	} | ||||
| .poster-link:hover .poster { | ||||
| 	box-shadow: 0 0 3px var(--darken); | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -249,6 +249,8 @@ exports.up = knex => Promise.resolve() | |||
| 			.references('id') | ||||
| 			.inTable('entities'); | ||||
| 
 | ||||
| 		table.text('entry_id'); | ||||
| 
 | ||||
| 		table.integer('alias_for', 12) | ||||
| 			.references('id') | ||||
| 			.inTable('actors'); | ||||
|  | @ -802,7 +804,7 @@ exports.up = knex => Promise.resolve() | |||
| 	.then(() => { // eslint-disable-line arrow-body-style
 | ||||
| 		// allow vim fold
 | ||||
| 		return knex.raw(` | ||||
| 			CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, entity_id); | ||||
| 			CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, entity_id, entry_id); | ||||
| 			CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug, (entity_id IS NULL)); | ||||
| 
 | ||||
| 			CREATE UNIQUE INDEX releases_search_unique ON releases_search (release_id); | ||||
|  |  | |||
| After Width: | Height: | Size: 1.3 MiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 612 KiB | 
| Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 6.2 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 22 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 29 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 29 KiB | 
| After Width: | Height: | Size: 882 KiB | 
| After Width: | Height: | Size: 9.1 KiB | 
| After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 929 KiB After Width: | Height: | Size: 1.8 MiB | 
| Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 30 KiB | 
| After Width: | Height: | Size: 471 KiB | 
| After Width: | Height: | Size: 5.6 KiB | 
| Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 24 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 803 KiB | 
| After Width: | Height: | Size: 413 KiB | 
| After Width: | Height: | Size: 764 KiB | 
| After Width: | Height: | Size: 6.0 KiB | 
| After Width: | Height: | Size: 8.1 KiB | 
| After Width: | Height: | Size: 27 KiB | 
| After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 802 KiB After Width: | Height: | Size: 872 KiB | 
| Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.4 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 637 KiB After Width: | Height: | Size: 355 KiB | 
| Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 6.8 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 26 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 5.7 MiB After Width: | Height: | Size: 6.9 MiB | 
| Before Width: | Height: | Size: 652 KiB | 
| Before Width: | Height: | Size: 655 KiB | 
| Before Width: | Height: | Size: 613 KiB | 
| Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8.1 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 212 KiB | 
| After Width: | Height: | Size: 4.7 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 30 KiB | 
| After Width: | Height: | Size: 18 KiB | 
| Before Width: | Height: | Size: 645 KiB After Width: | Height: | Size: 1.3 MiB | 
| After Width: | Height: | Size: 759 KiB | 
| After Width: | Height: | Size: 446 KiB | 
| Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 8.0 KiB | 
| After Width: | Height: | Size: 7.3 KiB | 
| After Width: | Height: | Size: 6.9 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 36 KiB | 
| Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 36 KiB | 
| After Width: | Height: | Size: 30 KiB | 
| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 557 KiB | 
| After Width: | Height: | Size: 8.6 KiB | 
| After Width: | Height: | Size: 39 KiB | 
|  | @ -1295,6 +1295,10 @@ const aliases = [ | |||
| 		for: 'femdom', | ||||
| 		secondary: true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'dp', | ||||
| 		for: 'dp', | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'double penetration (dp)', | ||||
| 		for: 'dp', | ||||
|  |  | |||
|  | @ -585,19 +585,20 @@ const sfw = Object.entries({ | |||
| 	.flat(); | ||||
| 
 | ||||
| const tagPosters = [ | ||||
| 	['69', 0, 'Abby Lee Brazil and Ramon Nomar for Wicked'], | ||||
| 	['airtight', 6, 'Remy Lacroix in "Ass Worship 14" for Jules Jordan'], | ||||
| 	['anal', 0, 'Adriana Chechik in "Manuel Creampies Their Asses 3" for Jules Jordan'], | ||||
| 	['anal-creampie', 1, 'Aleska Diamond in "Aleska Wants More" for Asshole Fever'], | ||||
| 	['ass-eating', 0, 'Angelica Heart and Leanna Sweet in "ATM Bitches" for Asshole Fever'], | ||||
| 	['asian', 0, 'Alina Li in "Slut Puppies 8" for Jules Jordan'], | ||||
| 	['asian', 0, 'Jade Kush for Erotica X'], | ||||
| 	['atm', 2, 'Jureka Del Mar in "Stretched Out" for Her Limit'], | ||||
| 	['atogm', 0, 'Alysa Gap and Logan in "Anal Buffet 4" for Evil Angel'], | ||||
| 	['bdsm', 0, 'Dani Daniels in "The Traning of Dani Daniels, Day 2" for The Training of O at Kink'], | ||||
| 	['behind-the-scenes', 0, 'Janice Griffith in "Day With A Pornstar: Janice" for Brazzers'], | ||||
| 	['blonde', 0, 'Anikka Albrite in "Black Owned 4" for Jules Jordan'], | ||||
| 	['blonde', 1, 'Marsha May in "Once You Go Black 7" for Jules Jordan'], | ||||
| 	['blowbang', 0, 'Lacy Lennon in "Lacy Lennon\'s First Blowbang" for HardX'], | ||||
| 	['blowjob', 0, 'Adriana Chechik in "The Dinner Party" for Real Wife Stories (Brazzers)'], | ||||
| 	['brunette', 0, 'Nicole Black in GIO971 for LegalPorno'], | ||||
| 	['brunette', 0, 'Liv Wild in "Dirty Talk 9" for Manuel Ferrara'], | ||||
| 	['bukkake', 0, 'Jaye Summers in "Facialized 5" for HardX'], | ||||
| 	['caucasian', 0, 'Remy Lacroix for HardX'], | ||||
| 	['creampie', 'poster', 'ALina Lopez in "Making Yourself Unforgettable" for Blacked'], | ||||
|  | @ -612,27 +613,28 @@ const tagPosters = [ | |||
| 	['dp', 2, 'Megan Rain in "DP Masters 4" for Jules Jordan'], | ||||
| 	['dvp', 'poster', 'Riley Reid in "Pizza That Ass" for Reid My Lips'], | ||||
| 	['dv-tp', 'poster', 'Juelz Ventura in "Gangbanged 5" for Elegant Angel'], | ||||
| 	['ebony', 1, 'Ana Foxxx in "DP Me 4" for HardX'], | ||||
| 	['ebony', 2, 'Nia Nacci for Sweetheart Video'], | ||||
| 	['facefucking', 2, 'Jynx Maze for Throated'], | ||||
| 	['facial', 0, 'Brooklyn Gray in "All About Ass 4" for Evil Angel'], | ||||
| 	['fake-boobs', 2, 'Gia Milana in "Hot Anal Latina" for HardX'], | ||||
| 	['fake-boobs', 4, 'Capri Cavanni for Big Tits in Sports'], | ||||
| 	['family', 0, 'Teanna Trump in "A Family Appear: Part One" for Brazzers'], | ||||
| 	['femdom', 0, 'Alina Li in "Asian Domination… She Holds Jules Jordan\'s Cock Hostage!" for Jules Jordan'], | ||||
| 	['gangbang', 5, 'Carter Cruise\'s first gangbang in "Slut Puppies 9" for Jules Jordan'], | ||||
| 	['gaping', 1, 'Vina Sky in "Vina Sky Does Anal" for HardX'], | ||||
| 	['indian', 0, 'Resha in "Casting Resha" for Watch 4 Beauty'], | ||||
| 	['interracial', 0, 'Jaye Summers and Prince Yahshua in "Platinum Pussy 3" for Jules Jordan'], | ||||
| 	['latina', 1, 'Jynx Maze in "Big Anal Asses 2" for HardX'], | ||||
| 	['latina', 0, 'Vienna Black for Spizoo'], | ||||
| 	['lesbian', 0, 'Jenna Sativa and Alina Lopez in "Opposites Attract" for Girl Girl'], | ||||
| 	['maid', 0, 'Whitney Wright in "Dredd Up Your Ass 2" for Jules Jordan'], | ||||
| 	['milf', 0, 'Olivia Austin in "Dredd 3" for Jules Jordan'], | ||||
| 	['mff', 1, 'Anikka Albrite, Kelsi Monroe and Mick Blue for HardX'], | ||||
| 	['mfm', 6, 'Honey Gold in "Slut Puppies 12" for Jules Jordan'], | ||||
| 	['natural-boobs', 0, 'Autumn Falls in "Manuel Ferrara\'s Ripe 7" for Jules Jordan'], | ||||
| 	['mfm', 0, 'Vina Sky in "Jules Jordan\'s Three Ways" for Jules Jordan'], | ||||
| 	['natural-boobs', 1, 'Nia Nacci for First Class POV'], | ||||
| 	['nurse', 0, 'Sarah Vandella in "Cum For Nurse Sarah" for Brazzers'], | ||||
| 	['oil', 0, 'Jada Stevens in "Jada Stevens Anal Ass Oiled Up For James Deen\'s Cock" for Jules Jordan'], | ||||
| 	['oil', 2, 'Jade Kush for Passion HD'], | ||||
| 	['oral-creampie', 0, 'Henessy in "B(ass)t Friends" for Asshole Fever'], | ||||
| 	['orgy', 1, 'Megan Rain (DP), Morgan Lee (anal), Jessa Rhodes, Melissa Moore and Kimmy Granger in "Orgy Masters 8" for Jules Jordan'], | ||||
| 	['parody', 0, 'Capri Cavanni and Dani Daniels in "The Whore of Wall Street" for Brazzers'], | ||||
| 	['piercings', 0, 'Kaegune in "When The Sun Goes Down" for Suicide Girls'], | ||||
| 	['piss-drinking', 0, 'Scarlet Domingo in LegalPorno GL227'], | ||||
| 	['pussy-eating', 1, 'Anikka Albrite and Riley Reid for In The Crack'], | ||||
|  | @ -665,7 +667,6 @@ const tagPhotos = [ | |||
| 	['atm', 3, 'Natasha Teen in "Work That Ass!" for Her Limit'], | ||||
| 	['atm', 0, 'Roxy Lips in "Under Her Coat" for 21 Naturals'], | ||||
| 	['atm', 6, 'Jane Wilde in "Teen Anal" for Evil Angel'], | ||||
| 	['asian', 'poster', 'Vina Sky in "Slut Puppies 15" for Jules Jordan'], | ||||
| 	// ['asian', 1, 'Alina Li in "Oil Overload 11" for Jules Jordan'],
 | ||||
| 	// ['anal', 'poster', 'Jynx Maze in "Anal Buffet 6" for Evil Angel'],
 | ||||
| 	['anal', 4, 'Lana Roy in "Anal In The Club" for 21Naturals'], | ||||
|  | @ -673,6 +674,7 @@ const tagPhotos = [ | |||
| 	// ['anal', 1, 'Veronica Leal and Tina Kay in "Agents On Anal Mission" for Asshole Fever'],
 | ||||
| 	// ['anal', 0, 'Veronica Leal'],
 | ||||
| 	['behind-the-scenes', 1, 'Madison Ivy in "Day With A Pornstar" for Brazzers'], | ||||
| 	['blonde', 0, 'Anikka Albrite in "Black Owned 4" for Jules Jordan'], | ||||
| 	['blowbang', 'poster', 'Marsha May in "Feeding Frenzy 12" for Jules Jordan'], | ||||
| 	// ['bukkake', 'poster', 'Mia Malkova in "Facialized 2" for HardX'],
 | ||||
| 	['caucasian', 1, 'Sheena Shaw for Brazzers'], | ||||
|  | @ -696,25 +698,28 @@ const tagPhotos = [ | |||
| 	['dvp', 0, 'Aaliyah Hadid in "Squirting From Double Penetration With Anal" for Bang Bros'], | ||||
| 	['dv-tp', 1, 'Adriana Chechik in "Adriana\'s Triple Anal Penetration!"'], | ||||
| 	['dv-tp', 0, 'Luna Rival in LegalPorno SZ1490'], | ||||
| 	['ebony', 1, 'Ana Foxxx in "DP Me 4" for HardX'], | ||||
| 	['facial', 1, 'Ella Knox in "Mr Saltys Adult Emporium Adventure 2" for Aziani'], | ||||
| 	['facial', 'poster', 'Jynx Maze'], | ||||
| 	['facefucking', 3, 'Adriana Chechik in "Performing Magic Butt Tricks With Jules Jordan. What Will Disappear In Her Ass?" for Jules Jordan'], | ||||
| 	['facefucking', 1, 'Carrie for Young Throats'], | ||||
| 	['fake-boobs', 2, 'Gia Milana in "Hot Anal Latina" for HardX'], | ||||
| 	['fake-boobs', 1, 'Lela Star in "Thick" for Jules Jordan'], | ||||
| 	['fake-boobs', 3, 'Ashly Anderson for Passion HD'], | ||||
| 	// ['fake-boobs', 0, 'Marsha May in "Once You Go Black 7" for Jules Jordan'],
 | ||||
| 	['gangbang', 'poster', 'Kristen Scott in "Interracial Gangbang!" for Jules Jordan'], | ||||
| 	['gangbang', 0, '"4 On 1 Gangbangs" for Doghouse Digital'], | ||||
| 	['gangbang', 4, 'Marley Brinx in "The Gangbang of Marley Brinx" for Jules Jordan'], | ||||
| 	['gangbang', 1, 'Ginger Lynn in "Gangbang Mystique", a photoset shot by Suze Randall for Puritan No. 10, 1984. This photo pushed the boundaries of pornography at the time, as depicting a woman \'fully occupied\' was unheard of.'], | ||||
| 	['gaping', 'poster', 'Zoey Monroe in "Manuel DPs Them All 5" for Jules Jordan'], | ||||
| 	['gaping', 2, 'Alex Grey in "DP Masters 5" for Jules Jordan'], | ||||
| 	['latina', 0, 'Abby Lee Brazil for Bang Bros'], | ||||
| 	['latina', 1, 'Jynx Maze in "Big Anal Asses 2" for HardX'], | ||||
| 	['latina', 2, 'Alexis Love for Penthouse'], | ||||
| 	['mff', 0, 'Madison Ivy, Adriana Chechik and Keiran Lee in "Day With A Pornstar" for Brazzers'], | ||||
| 	['mfm', 4, 'Vina Sky in "Jules Jordan\'s Three Ways" for Jules Jordan'], | ||||
| 	['mfm', 1, 'Jynx Maze in "Don\'t Make Me Beg 4" for Evil Angel'], | ||||
| 	['oil', 1, 'Emily Willis in "Emily Willis Has A Squirting Anal Orgasm" for Jules Jordan'], | ||||
| 	['mfm', 6, 'Honey Gold in "Slut Puppies 12" for Jules Jordan'], | ||||
| 	['natural-boobs', 0, 'Autumn Falls in "Manuel Ferrara\'s Ripe 7" for Jules Jordan'], | ||||
| 	['oil', 3, 'Vienna Black for Passion HD'], | ||||
| 	['oil', 0, 'Jada Stevens in "Jada Stevens Anal Ass Oiled Up For James Deen\'s Cock" for Jules Jordan'], | ||||
| 	['oil', 1, 'Kissa Sins in "Oil Overload 14" for JulesJordan'], | ||||
| 	['orgy', 'poster', 'Zoey Mornoe (DP), Jillian Janson (sex), Frida Sante, Katerina Kay and  Natasha Starr in "Orgy Masters 6" for Jules Jordan'], | ||||
| 	['pussy-eating', 0, 'Kali Roses licking Emily Willis\' pussy in "Peeping On My Neighbor" for Girl Girl'], | ||||
| 	['redhead', 0, 'Penny Pax in "The Submission of Emma Marx: Boundaries" for New Sensations'], | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ async function init() { | |||
| 	const actors = actorNames.length > 0 && await scrapeActors(actorNames); | ||||
| 	const actorBaseScenes = argv.actors && argv.actorScenes && actors.map(actor => actor.releases).flat().filter(Boolean); | ||||
| 
 | ||||
| 	const updateBaseScenes = (argv.all || argv.channels || argv.networks) && await fetchUpdates(); | ||||
| 	const updateBaseScenes = (argv.all || argv.channels || argv.networks || argv.movies) && await fetchUpdates(); | ||||
| 
 | ||||
| 	const scenesFromFile = argv.scenesFile && await getFileEntries(argv.scenesFile); | ||||
| 	const sceneUrls = (argv.scenes || []).concat(scenesFromFile || []); | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ const { argv } = yargs | |||
| 		type: 'array', | ||||
| 		alias: 'channel', | ||||
| 	}) | ||||
| 	.option('movies', { | ||||
| 		describe: 'Scrape movies from channels', | ||||
| 		type: 'array', | ||||
| 	}) | ||||
| 	.option('actors', { | ||||
| 		describe: 'Scrape actors by name or slug', | ||||
| 		type: 'array', | ||||
|  | @ -91,10 +95,10 @@ const { argv } = yargs | |||
| 		type: 'boolean', | ||||
| 		default: true, | ||||
| 	}) | ||||
| 	.option('redownload', { | ||||
| 	.option('force', { | ||||
| 		describe: 'Don\'t ignore duplicates, update existing entries', | ||||
| 		type: 'boolean', | ||||
| 		alias: 'force', | ||||
| 		alias: 'redownload', | ||||
| 	}) | ||||
| 	.option('after', { | ||||
| 		describe: 'Don\'t fetch scenes older than', | ||||
|  |  | |||
|  | @ -112,8 +112,8 @@ async function scrapeRelease(baseRelease, entities, type = 'scene') { | |||
| 		logger.verbose(`Fetching ${type} ${baseRelease.url}`); | ||||
| 
 | ||||
| 		const scrapedRelease = type === 'scene' | ||||
| 			? await scraper.fetchScene(baseRelease.url, entity, baseRelease, null, include) | ||||
| 			: await scraper.fetchMovie(baseRelease.url, entity, baseRelease, null, include); | ||||
| 			? await scraper.fetchScene(baseRelease.url, entity, baseRelease, include, null) | ||||
| 			: await scraper.fetchMovie(baseRelease.url, entity, baseRelease, include, null); | ||||
| 
 | ||||
| 		const mergedRelease = { | ||||
| 			...baseRelease, | ||||
|  |  | |||
|  | @ -37,8 +37,8 @@ async function fetchLatestWrap(site, page = 1) { | |||
| 	return latest.map(scene => extractActors(scene)); | ||||
| } | ||||
| 
 | ||||
| async function fetchSceneWrap(url, site) { | ||||
| 	const scene = await fetchScene(url, site); | ||||
| async function fetchSceneWrap(url, channel, baseRelease, include) { | ||||
| 	const scene = await fetchScene(url, channel, baseRelease, include); | ||||
| 
 | ||||
| 	return extractActors(scene); | ||||
| } | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ function scrapeLatest(scenes, site, models) { | |||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function scrapeScene({ html, qu }, url, site, models) { | ||||
| function scrapeScene({ html, qu }, url, site, include, models) { | ||||
| 	const release = { url }; | ||||
| 
 | ||||
| 	[release.entryId] = url.split('/').slice(-1); | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| const qu = require('../utils/q'); | ||||
| const slugify = require('../utils/slugify'); | ||||
| 
 | ||||
| function scrapeAll(scenes, channel) { | ||||
| 	return scenes.map(({ query }) => { | ||||
|  | @ -75,20 +74,25 @@ async function scrapeScene({ query, html }, url) { | |||
| 	return release; | ||||
| } | ||||
| 
 | ||||
| function scrapeProfile({ query, el }, actorName, entity, include) { | ||||
| 	const profile = {}; | ||||
| function scrapeMovies(movies, channel) { | ||||
| 	return movies.map(({ query }) => { | ||||
| 		const release = {}; | ||||
| 
 | ||||
| 	profile.description = query.cnt('.bio-text'); | ||||
| 	profile.birthPlace = query.cnt('.birth-place span'); | ||||
| 		release.url = query.url('.boxcover', 'href', { origin: channel.url }); | ||||
| 		release.entryId = new URL(release.url).pathname.match(/\/(\d+)/)[1]; | ||||
| 
 | ||||
| 	profile.avatar = query.img('.actor-photo img'); | ||||
| 		release.title = query.cnt('span'); | ||||
| 
 | ||||
| 	if (include.releases) { | ||||
| 		return scrapeAll(qu.initAll(el, '.scene')); | ||||
| 	} | ||||
| 		const cover = query.img('picture img'); | ||||
| 
 | ||||
| 	console.log(profile); | ||||
| 	return profile; | ||||
| 		release.covers = [ | ||||
| 			// filename is ignored, back-cover has suffix after media ID
 | ||||
| 			cover.replace('_sq.jpg', '/front.jpg').replace(/\/product\/\d+/, '/product/500'), | ||||
| 			cover.replace('_sq.jpg', 'b/back.jpg').replace(/\/product\/\d+/, '/product/500'), | ||||
| 		]; | ||||
| 
 | ||||
| 		return release; | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function fetchLatest(channel, page = 1) { | ||||
|  | @ -118,12 +122,14 @@ async function fetchScene(url, channel) { | |||
| 	return res.status; | ||||
| } | ||||
| 
 | ||||
| async function fetchProfile(actorName, entity, include) { | ||||
| 	const url = `${entity.url}/actors/${slugify(actorName, '_')}`; | ||||
| 	const res = await qu.get(url); | ||||
| async function fetchMovies(channel, page = 1) { | ||||
| 	const res = await qu.getAll(`https://www.elegantangel.com/streaming-elegant-angel-dvds-on-video.html?page=${page}`, '.grid-item', null, { | ||||
| 		// invalid certificate
 | ||||
| 		rejectUnauthorized: false, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (res.ok) { | ||||
| 		return scrapeProfile(res.item, actorName, entity, include); | ||||
| 		return scrapeMovies(res.items, channel); | ||||
| 	} | ||||
| 
 | ||||
| 	return res.status; | ||||
|  | @ -132,5 +138,5 @@ async function fetchProfile(actorName, entity, include) { | |||
| module.exports = { | ||||
| 	fetchLatest, | ||||
| 	fetchScene, | ||||
| 	fetchProfile, | ||||
| 	fetchMovies, | ||||
| }; | ||||
|  |  | |||
|  | @ -356,7 +356,7 @@ function scrapeProfileTour({ el, qu }, site) { | |||
| 	return profile; | ||||
| } | ||||
| 
 | ||||
| async function fetchLatest(site, page = 1, _beforeFetchLatest, accSiteReleases) { | ||||
| async function fetchLatest(site, page = 1, include, preflight, accSiteReleases) { | ||||
| 	const url = (site.parameters?.latest && util.format(site.parameters.latest, page)) | ||||
|         || (site.parameters?.t1 && `${site.url}/t1/categories/movies_${page}_d.html`) | ||||
|         || `${site.url}/categories/movies_${page}_d.html`; | ||||
|  |  | |||
|  | @ -139,8 +139,8 @@ function scrapeAll(scenes, site) { | |||
| 
 | ||||
| 		release.entryId = el.dataset.setid || qu.q('.rating_box')?.dataset.id; | ||||
| 
 | ||||
| 		release.url = qu.url('.update_title, .dvd_info > a, a ~ a'); | ||||
| 		release.title = qu.q('.update_title, .dvd_info > a, a ~ a', true); | ||||
| 		release.url = qu.url('.update_title a, .dvd_info > a, a ~ a'); | ||||
| 		release.title = qu.q('.update_title a, .dvd_info > a, a ~ a', true); | ||||
| 		release.date = qu.date('.update_date', 'MM/DD/YYYY'); | ||||
| 
 | ||||
| 		release.actors = qu.all('.update_models a', true); | ||||
|  | @ -249,7 +249,8 @@ async function scrapeScene({ html, qu }, url, site, include) { | |||
| 
 | ||||
| 		if (trailerLines.length) { | ||||
| 			release.trailer = trailerLines.map((trailerLine) => { | ||||
| 				const src = trailerLine.match(/path:"([\w:/.&=?%]+)"/)?.[1]; | ||||
| 				// const src = trailerLine.match(/path:"([\w-:/.&=?%]+)"/)?.[1];
 | ||||
| 				const src = trailerLine.match(/path:"(.+)"/)?.[1]; | ||||
| 				const quality = trailerLine.match(/movie_height:'(\d+)/)?.[1]; | ||||
| 
 | ||||
| 				return src && { | ||||
|  | @ -368,7 +369,7 @@ async function fetchUpcoming(site) { | |||
| 	return res.statusCode; | ||||
| } | ||||
| 
 | ||||
| async function fetchScene(url, site, baseRelease, preflight, include) { | ||||
| async function fetchScene(url, site, baseRelease, include) { | ||||
| 	const res = await get(url); | ||||
| 
 | ||||
| 	return res.ok ? scrapeScene(res.item, url, site, include) : res.status; | ||||
|  |  | |||
|  | @ -80,8 +80,8 @@ function needNextPage(uniqueReleases, pageAccReleases) { | |||
| async function scrapeReleases(scraper, entity, preData, upcoming = false) { | ||||
| 	const scrapePage = async (page = 1, accReleases = []) => { | ||||
| 		const latestReleases = upcoming | ||||
| 			? await scraper.fetchUpcoming(entity, page, preData, include) | ||||
| 			: await scraper.fetchLatest(entity, page, preData, include); | ||||
| 			? await scraper.fetchUpcoming(entity, page, include, preData) | ||||
| 			: await scraper.fetchLatest(entity, page, include, preData); | ||||
| 
 | ||||
| 		if (!Array.isArray(latestReleases)) { | ||||
| 			// scraper is unable to fetch the releases and returned a HTTP code or null
 | ||||
|  | @ -171,6 +171,10 @@ async function scrapeChannelReleases(scraper, channelEntity, preData) { | |||
| 			: [], | ||||
| 	]); | ||||
| 
 | ||||
| 	if (scraper.fetchMovies) { | ||||
| 		await scraper.fetchMovies(channelEntity); | ||||
| 	} | ||||
| 
 | ||||
| 	logger.info(`Fetching ${latestReleases.length} latest and ${upcomingReleases.length} upcoming updates for '${channelEntity.name}' (${channelEntity.parent?.name})`); | ||||
| 
 | ||||
| 	return [...latestReleases, ...upcomingReleases]; | ||||
|  |  | |||