Sleazy Fork is available in English.

LegalPorno Enhancer

Adds extra functionality to LegalPorno and Pornbox. Easily filter out unwanted categories. Also adds tags on top of every scene.

As of 04.02.2020. See ბოლო ვერსია.

// ==UserScript==
// @name        LegalPorno Enhancer
// @namespace   https://www.legalporno.com/forum/viewtopic.php?f=96&t=24238
// @description Adds extra functionality to LegalPorno and Pornbox. Easily filter out unwanted categories. Also adds tags on top of every scene.
// @match       http*://*.legalporno.com/*
// @match       http*://legalporno.com/*
// @exclude     http*://*legalporno.com/forum/*
// @exclude     http*://account.legalporno.com/*
// @match       https://*pornbox.com/*
// @version     1.2.4
// @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,
    Focus: true,
    Assablanca: true,
    'Mr Anal': true,
    'Porn World': true,
    'Girls Gone Wild': true
  },
  categories: {
    Piss: true,
    'Piss Drink': true,
    Fisting: true,
    Prolapse: true,
    Shemale: true
  }
};

const studio_colors = {
  'Gonzo.com': { bg: '#cd0000', text: '#fff' },
  'Giorgio Grandi': { bg: '#693180', text: '#fff' },
  'Interracial Vision': { bg: '#242424', text: '#e6e6e6' },
  'American Anal': { bg: '#b45219', text: '#fffa78' },
  "Giorgio's Lab": { bg: '#206d04', text: '#fff' }
};

// 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 {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: flex;
  margin: 4px 6px;
}

.enhanced--studio {
  background-color: #a76523c9;
  border-radius: 3px;
  padding: 2px 7px;
  color: white;
  text-shadow: 1px 1px #00000080;
  font-size: 12px;
  margin-right: 4px;
}

.enhanced_views_label {
  position: absolute;
  top: 28px;
  right: 5px;
  font-size: 11px;
  display: flex;
}

.enhanced--views {
  background-color: rgba(102,102,102,0.6);
  padding: 2px 6px;
  border-radius: 3px;
  color: #fff;
  display: flex;
  height: 100%;
  align-items: center;
  justify-content: center;
}

.enhanced--views > i {
  margin-right: 5px;
}

.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_lp {
  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;
}

.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 */

.enhanced--sort {
  display: grid;
  grid-template-columns: 1fr 1fr;
  justify-items: center;
  grid-gap: 2px;
  position: absolute;

  top: -4px;
  right: 210px;
}

.enhanced--sort--navbar {
  right: 25px;
  top: 6px;
}

.enhanced--sort > span {
  grid-column-end: span 2;
  color: #fff;
  
}

.enhanced--sort > button {
  width: 70px;
  height: 23px;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 0;
  margin: 0;
}

.page-section {
  position: relative !important;
}
`);

const getSceneInfo = scene => {
  const info = {
    studio: null,
    categories: null,
    date: null,
    views: null
  };

  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));

  const views = scene.querySelector('.rating-views');
  const date = scene.querySelector('.glyphicon-calendar');

  if (views) {
    info.views = parseInt(views.innerText.replace('VIEWS:', '').trim(), 10);
  }
  if (date) {
    try {
      info.date = new Date(
        date.parentElement.innerText.replace('RELEASE:', '').trim()
      );
    } catch (e) {}
  }

  return info;
};

const formatViews = n => {
  if (n >= 1e3 && n < 1e6) return +(n / 1e3).toFixed(0) + 'k';
  if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(0) + 'm';
  if (n >= 1e9 && n < 1e12) return +(n / 1e9).toFixed(0) + 'b';
  // if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
  return n;
};

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, views, date } = getSceneInfo(scene);

  if (studio) {
    const studioLabel = document.createElement('div');
    studioLabel.classList.add('enhanced--studio');
    studioLabel.innerHTML = studio;

    if (studio_colors[studio]) {
      studioLabel.style = `
      background-color: ${studio_colors[studio]['bg']}; color: ${studio_colors[studio]['text']};
      `;
    }

    if (views) {
      const viewsLabel = document.createElement('div');
      viewsLabel.innerHTML = `
      <div class='enhanced--views'><i class='fa fa-eye'></i>${formatViews(
        views
      )}</div>`;
      viewsLabel.classList.add('enhanced_views_label');
      if (scene.querySelector('.thumbnail-image')) {
        scene.querySelector('.thumbnail-image').appendChild(viewsLabel);
      }
    }

    // studioLabel.classList.add('enhanced_title_tags');
    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, views, date } = 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 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');
  if (is_lp) form.classList.add('filters_container_lp');
  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>`;

  form.addEventListener('change', e => {
    glo_filters[e.target.name][e.target.id] = e.target.checked;
    updateFilters();
  });

  const lp_header = document.querySelector('.header');
  const pb_header = document.querySelector('#wrap-container');

  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');
  toggleFilterBtn.classList.add(`enhanced_toggle_${is_lp ? 'lp' : '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)'
  );
  is_lp ? (search_container.style = 'display: flex;') : null;
  search_container.appendChild(toggleFilterBtn);
};

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 config = { childList: true, subtree: true, attributes: true };
const observer = new MutationObserver(callback);
observer.observe(window.document, config);

const sortScenes = ({ date = false, views = false, asc = true } = {}) => {
  const node = document.querySelector('.thumbnails');
  [...node.children]
    .sort((a, b) => {
      const infoA = getSceneInfo(a);
      const infoB = getSceneInfo(b);

      let order = -1;

      if (date) {
        order = infoA.date > infoB.date ? -1 : 1;
      } else if (views) {
        order = infoA.views > infoB.views ? -1 : 1;
      } else {
        order = 0;
      }

      if (a.classList.contains('button--load-more')) return 0;
      if (b.classList.contains('button--load-more')) return 0;

      return !asc ? order : order * -1;
    })
    .map(scene => {
      node.appendChild(scene);
      node.appendChild(document.createTextNode(' '));
    });
};

const sortBtns = () => {
  const sortDiv = document.createElement('div');
  sortDiv.classList.add('enhanced--sort');
  sortDiv.innerHTML = `
  <span>Sort By:</span>
  <button asc="false">Date</button>
  <button asc="false">Views</button>
  `;

  sortDiv.addEventListener('click', e => {
    if (e.target.tagName === 'BUTTON') {
      const asc = e.target.getAttribute('asc') === 'true';
      e.target.setAttribute('asc', !asc);
      const sortObj = { asc };
      sortObj[e.target.innerText.toLowerCase()] = true;
      sortScenes(sortObj);
    }
  });

  const navbar = document.querySelectorAll('.navbar > .container-fluid');
  if (navbar.length) {
    navbar[navbar.length - 1].appendChild(sortDiv);
    sortDiv.classList.add('enhanced--sort--navbar');
    return;
  }

  const cont = document.querySelector('.page-section');
  if (cont) {
    sortDiv.children[0].style = 'color: #000;';
    cont.appendChild(sortDiv);
  }
};

document.addEventListener('DOMContentLoaded', () => {
  if (!is_lp && !document.querySelector('.nav-search-container')) return;
  createFilterElement();
  sortBtns();

  const nodes = document.querySelectorAll('.block-item, .thumbnail');
  [...nodes].map(node => {
    enhanceScene(node);
    filterScene(node);
  });
});