// ==UserScript==
// @name LegalPorno Enhancer
// @namespace lpEnhancer
// @description Adds extra functionality to LegalPorno and Pornbox. Easily filter out unwanted categories. Also adds tags on every scene.
// @match http*://*.legalporno.com/*
// @match http*://legalporno.com/*
// @exclude http*://*legalporno.com/forum/*
// @match https://*pornbox.com/*
// @homepage https://github.com/HwtOxc8K/lp-filter
// @version 1.0.0
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==
const is_lp = !!window.location.href.match(/legalporno/);
let glo_filters = {
studios: {
'Gonzo.com': true,
'Giorgio Grandi': true,
'Interracial Vision': true,
'American Anal': true,
"Giorgio's Lab": true,
'Porn World': true
},
categories: {
Piss: true,
'Piss Drink': true,
Fisting: true,
Prolapse: true,
Shemale: true
}
};
// Uncomment the following line to reset global filters
// GM_setValue('user_filters', JSON.stringify(glo_filters));
validateUserFilters = filters => {
if (!filters.studios || !filters.categories) return false;
if (
Object.keys(filters.studios).length !==
Object.keys(glo_filters.studios).length
)
return false;
if (
Object.keys(filters.categories).length !==
Object.keys(glo_filters.categories).length
)
return false;
if (
Object.keys(filters.categories)
.sort()
.some(
(val, idx) => val !== Object.keys(glo_filters.categories).sort()[idx]
)
)
return false;
if (
Object.keys(filters.studios)
.sort()
.some((val, idx) => val !== Object.keys(glo_filters.studios).sort()[idx])
)
return false;
return true;
};
try {
const user_config = GM_getValue('user_filters');
if (!user_config)
throw new Error('No user filters found. Using default ones.');
user_filters = JSON.parse(user_config);
if (!validateUserFilters(user_filters))
throw new Error('Invalid user settings.');
glo_filters = user_filters;
} catch (e) {}
const studios = {
Interracial: 'Interracial Vision',
'Hard Porn World': 'Porn World'
};
const icon_categories = {
interracial: 'IR',
'double anal (DAP)': 'DAP',
fisting: 'Fisting',
prolapse: 'Prolapse',
shemale: 'Shemale',
'triple anal (TAP)': 'TAP',
piss: 'Piss',
squirting: 'Squirt',
'3+ on 1': '3+on1',
'double vaginal (DPP)': 'DPP',
'first time': '1st',
'triple penetration': 'TP',
'0% pussy': '0% Pussy',
'cum swallowing': 'Cum Swallow',
'piss drinking': 'Piss Drink',
'anal creampies': 'Anal Creampie',
'1 on 1': '1on1',
milf: 'Milf',
'facial cumshot': 'Facial'
};
GM_addStyle(`
.hiddenScene {
display: none;
}
.hide_element {
display: none !important;
}
.thumbnail-duration {
top: 0 !important;
right: 0 !important;
left: unset !important;
bottom: unset !important;
margin: 5px !important;
}
.enhanced_categories_container {
width: 100%;
display: flex;
flex-wrap: wrap-reverse;
position: absolute;
bottom:0; left: 0;
z-index: 3;
padding: 3px 5px;
}
.enchanced_icon {
border-radius: 3px;
background-color: #616161;
color: #eee;
opacity: 0.9;
padding: 2px 7px;
margin-right: 2px;
margin-top: 2px;
font-size: 13px;
}
.enhanced_studio_label {
position: absolute;
top: 0;
left: 0;
z-index: 3;
background-color: #a76523c9;
border-radius: 3px;
padding: 2px 7px;
margin: 4px 6px;
color: white;
text-shadow: 1px 1px #00000080;
font-size: 12px;
}
.item__img:hover > div {
display: none;
}
.item__img-labels-bottom {
top: 3px;
right: 3px;
left: unset;
bottom: unset;
display: flex
}
.img-label {
margin-top: unset;
margin-left: 2px;
}
.item__img-labels {
display: flex;
flex-direction: row-reverse;
left: unset;
right: 3px;
top: 23px;
}
/* Filter Css Begin */
.filters_container {
margin-left: -15px;
margin-right: -15px;
}
.filters_container--main {
width: 100%;
display: flex;
justify-content: center;
background: linear-gradient(
0deg,
#babcbc 0%,
#dfe1e1 2%,
#dfe1e1 98%,
#babcbc 100%
);
}
.filters_card {
margin: 0 25px;
padding-bottom: 15px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.filters_selections {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.filters--divider {
width: 1px;
background-color: rgb(0, 0, 0, 0.25);
height: 50px;
margin-top: 15px;
align-self: center;
}
.filters--selection {
padding: 1px 10px 0 0;
min-width: 140px;
display: flex;
align-items: center;
}
.filters--selection > label,
.filters--selection > input[type="checkbox"] {
margin: 0 2px 0 0;
font-weight: bold;
}
.fitlers--header {
padding: 5px 0 5px 0;
align-self: center;
font-weight: bold;
}
.enhancedFilterBtn {
background: -webkit-linear-gradient(top,#a9a9a9 0%,#d97575 5%,#563434 100%) !important;
border: 1px solid #722424 !important;
}
.enhanced_toggle {
background: -webkit-linear-gradient(
top,
#a9a9a9 0%,
#d97575 5%,
#563434 100%
) !important;
// height: 100%;
border: 1px solid #722424 !important;
color: white;
padding: 7px 11px;
font-weight: 700;
border: none !important;
}
.enhanced_toggle_pb {
height: 100%;
padding: 7px 11px;
border-radius: 300px;
position: absolute;
margin-left: 4px;
}
.enhanced_toggle_lp {
border-radius: 200px;
margin-left: 4px;
}
.enhanced_toggle:hover {
cursor: pointer;
}
.enhanced_toggle > i {
font-size: 12px;
}
/* Filter Css End */
`);
const callback = mutationsList => {
for (let mutation of mutationsList) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) {
if (node.nodeName === 'DIV') {
if (
node.classList.contains('block-item') ||
node.classList.contains('thumbnail')
) {
enhanceScene(node);
filterScene(node);
}
}
}
}
}
};
const getSceneInfo = scene => {
const info = {};
info.studio = scene.querySelector('.rating-studio-name, a[href^="#studio/"]');
info.studio = info.studio ? info.studio.innerText : null;
if (studios[info.studio]) {
info.studio = studios[info.studio];
}
info.categories = [...scene.querySelectorAll('a[href*="niche/"]')]
.filter(cat => cat.innerText && icon_categories[cat.innerText])
.map(cat => icon_categories[cat.innerText])
.sort();
// info.categories = info.categories.filter(
// cat => !(cat === 'Piss' && info.categories.includes('Piss Drink'))
// );
info.categories = Array.from(new Set(info.categories));
return info;
};
const enhanceScene = scene => {
// Remove icons from thumnail. ("4k" and "new")
const icons = scene.querySelectorAll('.icon');
for (const icon of icons) {
icon.classList.add('hide_element');
}
const { studio, categories } = getSceneInfo(scene);
if (studio) {
const studioLabel = document.createElement('div');
studioLabel.innerHTML = studio;
studioLabel.classList.add('enhanced_studio_label');
scene
.querySelector('.thumbnail-image, .item__img')
.appendChild(studioLabel);
}
if (categories.length) {
const cat_div = document.createElement('div');
cat_div.classList.add('enhanced_categories_container');
for (const cat of categories) {
const icon = document.createElement('div');
icon.classList.add('enchanced_icon');
icon.innerHTML = cat;
cat_div.appendChild(icon);
}
if (scene.querySelector('.thumbnail-image, .item__img')) {
scene.querySelector('.thumbnail-image, .item__img').appendChild(cat_div);
}
}
};
const is_scene_filtered = scene => {
const { studio, categories } = getSceneInfo(scene);
for (const key of Object.keys(glo_filters.studios)) {
if (studio && studio.includes(key) && !glo_filters.studios[key])
return true;
}
for (const key of Object.keys(glo_filters.categories)) {
if (categories && categories.includes(key) && !glo_filters.categories[key])
return true;
}
return false;
};
const filterScene = scene => {
if (is_scene_filtered(scene)) {
scene.classList.add('hiddenScene');
}
};
const config = { childList: true, subtree: true };
const observer = new MutationObserver(callback);
observer.observe(window.document, config);
const updateFilters = () => {
GM_setValue('user_filters', JSON.stringify(glo_filters));
const scenes = document.querySelectorAll('.thumbnail, .block-item');
for (const scene of scenes) {
if (scene.classList.contains('hiddenScene'))
scene.classList.remove('hiddenScene');
filterScene(scene);
}
};
const createFilterElement = () => {
const { studios, categories } = glo_filters;
const studioHtml = Object.keys(studios).reduce(
(acc, studio) =>
(acc += `<div class="filters--selection">
<input type="checkbox" name="studios" id="${studio}"
${studios[studio] ? 'checked="checked"' : ''}
/>
<label for="${studio}">${studio}</label>
</div>`),
''
);
const categoriesHtml = Object.keys(categories).reduce(
(acc, cat) =>
(acc += `<div class="filters--selection">
<input type="checkbox" name="categories" id="${cat}"
${categories[cat] ? 'checked="checked"' : ''}
/>
<label for="${cat}">${cat}</label>
</div>`),
''
);
const form = document.createElement('form');
form.classList.add('filters_container', 'hide_element');
form.innerHTML = `
<div class="filters_container--main">
<div class="filters_card">
<div class="fitlers--header">Studios:</div>
<div class="filters_selections">
${studioHtml}
</div>
</div>
<!-- -->
<div class="filters--divider"></div>
<!-- -->
<div class="filters_card">
<div class="fitlers--header">Categories:</div>
<div class="filters_selections">
${categoriesHtml}
</div>
</div>
</div>
`;
const lp_header = document.querySelector('.header');
const pb_header = document.querySelector('#wrap-container');
form.addEventListener('change', e => {
glo_filters[e.target.name][e.target.id] = e.target.checked;
updateFilters();
});
if (lp_header) lp_header.insertBefore(form, lp_header.childNodes[2]);
if (pb_header) pb_header.insertBefore(form, pb_header.childNodes[0]);
createFilterToggle();
};
const createFilterToggle = () => {
const toggleFilterBtn = document.createElement('button');
toggleFilterBtn.classList.add('enhanced_toggle');
if (is_lp) {
toggleFilterBtn.classList.add('enhanced_toggle_lp');
} else {
toggleFilterBtn.classList.add('enhanced_toggle_pb');
}
toggleFilterBtn.innerHTML = `<i class="fa fa-filter"></i>`;
toggleFilterBtn.addEventListener('click', () => {
const filters_container = document.querySelector('.filters_container');
if (filters_container) {
filters_container.classList.toggle('hide_element');
}
});
const search_container = document.querySelector(
'.nav-search-container, .header-block:nth-of-type(3)'
);
search_container.appendChild(toggleFilterBtn);
};
document.addEventListener('DOMContentLoaded', () => {
if (!is_lp && !document.querySelector('.nav-search-container')) return;
createFilterElement();
});