f95zone tweaks

f95zone exclude tags and min like filter.

Od 16.12.2021.. Pogledajte najnovija verzija.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        f95zone tweaks
// @namespace   f95zone tweaks
// @description f95zone exclude tags and min like filter.
// @match       https://f95zone.to/sam/latest_alpha/
// @version     1.4.0
// @author      3xd_Tango
// @require     https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected]
// @require     https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// @license     GNU GPLv3
// ==/UserScript==

(function () {
'use strict';

var css_248z = ".customTags{border-radius:2px;box-shadow:0 0 0 0 transparent,0 1px 1px 0 rgba(0,0,0,.15),0 1px 2px 0 rgba(0,0,0,.15);display:inline-block;font-size:.8em;line-height:1.8em;margin:4px 4px 0 0;padding:0 6px;text-shadow:1px 1px 2px rgba(0,0,0,.75)}.selected-tags-wrap span:hover{background-color:#ba4545}.selected-tags-wrap span{background-color:#181a1d;border-radius:3px;cursor:pointer;display:inline-block;font-size:.9em;line-height:24px;margin:4px 4px 0 0;padding:0 7px;transition:.2s}.input-tag{left:0;opacity:1;position:relative;width:127px}.border-gradient{border:3px solid;border-image-slice:1}.flxgrow{flex-grow:1}";

const likes = GM_getValue('likes', [['incest', '#ff0000', '30'], ['harem', '#0011ff', '254'], ['futa/trans', '#EE82EE', '191'], ['loli', '#00f5ffff', '639'], ['futa/trans protagonist', '#EE82EE', '2255']]);
const dislikes = GM_getValue('dislikes', [['female protagonist', '392'], ['text based', '522'], ['mind control', '111']]);
const totalTags = GM_getValue('tags', [['2d game', '2214'], ['2dcg', '1507'], ['3d game', '1434'], ['3dcg', '107'], ['adventure', '162'], ['ahegao', '916'], ['anal sex', '2241'], ['animated', '783'], ['bdsm', '264'], ['bestiality', '105'], ['big ass', '817'], ['big tits', '130'], ['blackmail', '339'], ['bukkake', '216'], ['censored', '2247'], ['character creation', '2246'], ['cheating', '924'], ['combat', '550'], ['corruption', '103'], ['cosplay', '606'], ['creampie', '278'], ['dating sim', '348'], ['dilf', '1407'], ['drugs', '2217'], ['dystopian setting', '2249'], ['exhibitionism', '384'], ['fantasy', '179'], ['female domination', '2252'], ['female protagonist', '392'], ['footjob', '553'], ['furry', '382'], ['futa/trans', '191'], ['futa/trans protagonist', '2255'], ['gay', '360'], ['graphic violence', '728'], ['groping', '535'], ['group sex', '498'], ['handjob', '259'], ['harem', '254'], ['horror', '708'], ['humiliation', '871'], ['humor', '361'], ['incest', '30'], ['internal view', '1483'], ['interracial', '894'], ['japanese game', '736'], ['kinetic novel', '1111'], ['lactation', '290'], ['lesbian', '181'], ['loli', '639'], ['male domination', '174'], ['male protagonist', '173'], ['management', '449'], ['masturbation', '176'], ['milf', '75'], ['mind control', '111'], ['mobile game', '2229'], ['monster', '182'], ['monster girl', '394'], ['multiple endings', '322'], ['multiple penetration', '1556'], ['multiple protagonist', '2242'], ['necrophilia', '1828'], ['no sexual content', '324'], ['ntr', '258'], ['oral sex', '237'], ['paranormal', '408'], ['parody', '505'], ['platformer', '1508'], ['point & click', '1525'], ['possession', '1476'], ['pov', '1766'], ['pregnancy', '225'], ['prostitution', '374'], ['puzzle', '1471'], ['rape', '417'], ['real porn', '1707'], ['religion', '2218'], ['romance', '330'], ['rpg', '45'], ['sandbox', '2257'], ['scat', '689'], ['school setting', '547'], ['sci-fi', '141'], ['sex toys', '2216'], ['sexual harassment', '670'], ['shooter', '1079'], ['shota', '749'], ['side-scroller', '776'], ['simulator', '448'], ['sissification', '2215'], ['slave', '44'], ['sleep sex', '1305'], ['spanking', '769'], ['strategy', '628'], ['stripping', '480'], ['superpowers', '354'], ['swinging', '2234'], ['teasing', '351'], ['tentacles', '215'], ['text based', '522'], ['titfuck', '411'], ['trainer', '199'], ['transformation', '875'], ['trap', '362'], ['turn based combat', '452'], ['twins', '327'], ['urination', '1254'], ['vaginal sex', '2209'], ['virgin', '833'], ['virtual reality', '895'], ['voiced', '1506'], ['vore', '757'], ['voyeurism', '485']]);

function likeFilter() {
  const element = document.querySelectorAll('.resource-tile');
  document.querySelectorAll('.customTags').forEach(e => e.parentNode.remove());

  for (let i = 0; i < element.length; i += 1) {
    element[i].style = '';
    element[i].classList.remove('border-gradient');
    element[i].setAttribute('isDisplay', 'false');
    const colors = [];
    const node = document.createElement('DIV');
    const dataTags = element[i].dataset.tags.split(',');
    node.style.padding = '.5rem 0';

    for (let j = likes.length - 1; j >= 0; j -= 1) {
      if (dataTags.includes(likes[j][2])) {
        console.debug(likes[j][0], likes[j][1]);

        if (!element[i].classList.contains('border-gradient')) {
          element[i].classList.add('border-gradient');
        }

        const node1 = document.createElement('SPAN');
        colors.push(likes[j][1]);
        node1.classList.add('customTags');
        node1.style.borderLeft = `${likes[j][1]} solid`;
        node1.appendChild(document.createTextNode(likes[j][0]));
        node.appendChild(node1);
        element[i].setAttribute('isDisplay', 'true');
      } else if (element[i].getAttribute('isDisplay') === null) {
        element[i].setAttribute('isDisplay', 'false');
      }
    }

    if (colors.length > 1) {
      element[i].style.borderImageSource = `linear-gradient(to bottom right, ${colors.toString()})`;
    } else {
      element[i].style.color = colors[0];
    }

    if (element[i].getAttribute('isDisplay') === 'true') {
      try {
        element[i].childNodes[0].childNodes[1].childNodes[1].appendChild(node);
      } catch (e) {
        console.error(e);
        setTimeout(likeFilter, 200);
      }
    }
  }
}

function likeLimit() {
  const element = document.querySelectorAll('.resource-tile_info'); // if (likesLimit === '') {
  //   likesLimit = -1;
  // }

  for (let i = 0; i < element.length; i++) {
    const elementLikes = element[i].childNodes[1].childNodes[1].innerText;
    element[i].childNodes[0].childNodes[0].childNodes[0].innerText;
    const checkFilter = Number(elementLikes) <= Number(GM_getValue('like_limit', 200)); // console.debug(`we get Like limit of ${elementName} is ${elementLikes} with check ${checkFilter} with display attribute of ${(element[i].parentNode.parentNode.parentNode.getAttribute('isDisplay').toLowerCase() !== 'false')}`);

    if (checkFilter) {
      if (element[i].parentNode.parentNode.parentNode.getAttribute('isDisplay') === 'false') {
        element[i].parentNode.parentNode.parentNode.setAttribute('isLikeLimit', 'true');
        continue;
      }

      if (GM_getValue('favLikeFilter', false)) {
        element[i].parentNode.parentNode.parentNode.setAttribute('isLikeLimit', 'true');
        continue;
      }
    }

    element[i].parentNode.parentNode.parentNode.setAttribute('isLikeLimit', 'false');
  }
}

function filterOut() {
  const element = document.querySelectorAll('.resource-tile');

  for (let i = 0; i < element.length; i++) {
    if (element[i].getAttribute('isDislike') === 'true' || element[i].getAttribute('isLikeLimit') === 'true') {
      element[i].style.setProperty('display', 'none', 'important');
      element[i].style.setProperty('height', '0px', 'important');
      element[i].style.setProperty('margin', '0px', 'important');
    } else {
      element[i].style.setProperty('display', 'block', 'important');
      element[i].style.setProperty('height', 'unset', 'important');
      element[i].style.setProperty('margin', 'unset', 'important');
    }
  }
}

function checkTags(element, dataTags) {
  let tagProt = [['female domination', '2252'], ['male protagonist', '173'], ['futa/trans protagonist', '2255']];
  const tagProtNames = dislikes.filter(e => e[0].includes('protagonist'));
  console.log(tagProtNames, 'tagProtNames');
  const b = [];
  let c = 0;
  dataTags.forEach(e => {
    if (tagProtNames.length >= 1) {
      for (let i = 0; i < tagProtNames.length; i += 1) {
        if (tagProtNames[i][1].includes(e)) {
          return b.push(e);
        }
      }
    }

    if (dislikes.includes(e)) {
      c += 1;
    }
  }); // console.debug(`b = ${b}`, c, element);

  if (c > 0) {
    return true;
  }

  if (b.length > 0) {
    b.forEach(e => {
      tagProt = tagProt.filter(r => r !== e);
    });

    for (let i = 0; i <= tagProt.length - 1; i += 1) {
      console.debug(`datatags includes? = ${dataTags.includes(tagProt[i][1])} for ${tagProt[i][0]} `);

      if (dataTags.includes(tagProt[i])) {
        return false;
      }

      if (i === tagProt.length - 1) {
        return true;
      }
    }
  }

  console.debug(`tagProt = ${tagProt}`);
  return false;
}

function dislikeFilter() {
  const element = document.querySelectorAll('.resource-tile');

  for (let i = 0; i < element.length; i++) {
    const dataTags = element[i].dataset.tags.split(',');
    const isFP = checkTags(element[i], dataTags);

    if (isFP) {
      element[i].setAttribute('isDislike', 'true');
    } else {
      element[i].setAttribute('isDislike', 'false');
    }
  }
}

function tagRemoveHandler(event, isLikes) {
  let filtered = [];

  if (isLikes) {
    filtered = likes.filter(r => r[0] !== event.target.textContent);
    console.debug(`You have clicked the Like Tag to Remove it and the filtered is: ${filtered} within ${likes}`);

    const removed = _.remove(likes, n => {
      return n[0] === event.target.textContent;
    });

    console.debug(`after filtred you got likes tags: ${likes} and you removed ${removed}`);
    likeFilter();
    likeLimit();
  } else {
    filtered = dislikes.filter(r => r[0] !== event.target.textContent);
    console.debug(`You have clicked the dislike Tag to Remove it and the filtered is: ${filtered} within ${dislikes}`);

    const removed = _.remove(dislikes, n => {
      return n[0] === event.target.textContent;
    });

    console.debug(`after filtred you got dislike tags: ${dislikes} and you removed ${removed}`);
    dislikeFilter();
  }

  event.target.remove();
  GM_setValue(isLikes ? 'likes' : 'dislikes', filtered);
  filterOut();
} // function tagRemoveHandler1(event) {
//   likes = likes.filter(r => r[0] !== event.target.textContent);
//   event.target.remove();
//   GM_setValue('likes', likes);
//   likeFilter();
//   likeLimit();
//   filterOut();
// }

function likeHandler(event) {
  const [tagName, tagCode] = event.target.value.split(':');
  event.target.value = '';
  let x;

  do {
    x = prompt("Please Enter the Color in Hex like '#FFFFFF'");
  } while (!x.includes('#') && x === '' && x.length <= 1);

  console.debug(`you have entered color in hex: ${x} with tagName: ${tagName} and the condition ${x !== null}`);

  if (x !== null) {
    const tag = [tagName, x, tagCode];
    likes.push(tag);
    console.debug(`the likes after pushing the ${tag} is ${likes}`);
    GM_setValue('likes', _.sortBy(likes, item => item[0]));
    const node = document.createElement('SPAN');

    node.onclick = e => tagRemoveHandler(e, true);

    node.style.borderLeft = `${x} solid`;
    node.appendChild(document.createTextNode(tagName));
    event.target.parentNode.parentNode.childNodes[2].append(node);
    likeFilter();
    likeLimit();
    filterOut();
  }
}

function dislikeHandler(event) {
  const [tagName, tagCode] = event.target.value.split(':');
  dislikes.push([tagName, tagCode]);
  GM_setValue('dislikes', _.sortBy(dislikes));
  event.target.value = '';
  const node = document.createElement('SPAN');

  node.onclick = e => tagRemoveHandler(e, false);

  node.appendChild(document.createTextNode(tagName));
  event.target.parentNode.parentNode.childNodes[2].append(node);
  dislikeFilter();
  filterOut();
}

function limitHandler(event) {
  GM_setValue('like_limit', event.target.value);
  likeLimit();
  filterOut();
}

function clickTotalTags() {
  const y = document.querySelector('.selectize-control');

  if (y !== null) {
    y.addEventListener('click', e => {
      const x = document.querySelectorAll('div.option');
      console.log('clicked', x);

      if (x !== []) {
        const totalTags = [];
        x.forEach(e => totalTags.push([e.childNodes[0].innerHTML, e.dataset.value]));
        GM_setValue('tags', totalTags);
      }
    });
  }
}

function init() {
  const element = document.querySelectorAll('.resource-tile');
  const elementLength = element.length;

  if (element[0]) {
    likeFilter();
    clickTotalTags();

    for (let i = 0; i < 3; i++) {
      dislikeFilter();
      likeLimit();
      filterOut(); // if(document.querySelectorAll('.resource-tile'))

      const filtered = _.filter(document.querySelectorAll('.resource-tile'), e => {
        return e.getAttribute('isDisplay') === 'true';
      });

      console.debug('Filtered', filtered);

      if (filtered.length < elementLength) {
        break;
      }
    }
  } else {
    setTimeout(init, 100);
  }
}

function waitForElementToDisplayAndAppend(selector, callback, checkFrequencyInMs, timeoutInMs) {
  const startTimeInMs = Date.now();

  (function loopSearch() {
    if (document.querySelector(selector).childNodes.length > 0) {
      document.querySelector(selector).appendChild(callback());
    } else {
      setTimeout(() => {
        if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs) return;
        loopSearch();
      }, checkFrequencyInMs);
    }
  })();
}

function LikesTagFilter() {
  // filter-block accordion-block filter-block_prefix-group
  return VM.hm("div", {
    className: "filter-block accordion-block filter-block_prefix-group"
  }, VM.hm("h4", {
    className: "filter-block_title accordion-toggle"
  }, "Like Tags"), VM.hm("div", {
    className: "filter-block_content filter-block_v accordion-content"
  }, VM.hm("div", {
    className: "selectize-input"
  }, VM.hm("input", {
    autoComplete: "on",
    placeholder: "Enter a tag to filter...",
    className: "input-tag",
    onChange: likeHandler,
    list: "totalTagName"
  })), VM.hm("datalist", {
    id: "totalTagName"
  }, totalTags.map(item => {
    return VM.hm("option", {
      value: `${item[0]}:${item[1]}`
    });
  })), VM.hm("div", {
    className: "selected-tags-wrap"
  }, likes.map(dl => {
    const node = document.createElement('SPAN');

    node.onclick = e => tagRemoveHandler(e, true);

    node.style.borderLeft = `${dl[1]} solid`;
    node.appendChild(document.createTextNode(dl[0]));
    return node; // <span
    //   style={{ borderLeft: `${dl[1]} solid` }}
    //   onClick={(e) => tagRemoveHandler(e, true)}
    // >
    //   {dl[0]}
    // </span>;
  }))));
}

function DislikeTagFilter() {
  return VM.hm("div", {
    className: "filter-block accordion-block filter-block_prefix-group"
  }, VM.hm("h4", {
    className: "filter-block_title accordion-toggle"
  }, "Dislike Tags"), VM.hm("div", {
    className: "filter-block_content filter-block_v accordion-content"
  }, VM.hm("div", {
    className: "selectize-input"
  }, VM.hm("input", {
    autoComplete: "off",
    placeholder: "Enter a tag to filter...",
    className: "input-tag",
    onChange: dislikeHandler,
    list: "totalTagName1"
  })), VM.hm("datalist", {
    id: "totalTagName1"
  }, totalTags.map(item => {
    return VM.hm("option", {
      value: `${item[0]}:${item[1]}`
    });
  })), VM.hm("div", {
    className: "selected-tags-wrap"
  }, dislikes.map(dl => VM.hm("span", {
    onClick: e => tagRemoveHandler(e, false)
  }, dl[0])))));
}

function LikeLimitDisplay() {
  return VM.hm("div", {
    className: "filter-block"
  }, VM.hm("h4", {
    className: "filter-block_title"
  }, "Tags Like Limits"), VM.hm("div", {
    className: "selectize-input"
  }, VM.hm("input", {
    value: GM_getValue('like_limit', 200),
    autoComplete: "off",
    style: "width: 127px;",
    onChange: limitHandler
  })));
}

function FilterFavLikeHandler(event) {
  GM_setValue('favLikeFilter', event.target.checked);
  likeLimit();
  filterOut();
}

function mainFilter() {
  init();
  return VM.hm(VM.Fragment, null, LikeLimitDisplay(), VM.hm("div", {
    className: "filter-block"
  }, VM.hm("div", {
    className: "filter-block_button-wrap",
    style: {
      display: 'flex'
    }
  }, VM.hm("span", {
    className: "filter-block_title flxgrow"
  }, "Filter Fav likes:"), VM.hm("input", {
    type: "checkbox",
    id: "filter-fav-likes",
    onChange: FilterFavLikeHandler,
    checked: GM_getValue('favLikeFilter', false)
  }))), VM.hm("style", null, css_248z));
}

document.querySelector('.content-block_filter').appendChild(mainFilter());
waitForElementToDisplayAndAppend('#filter-block_prefixes', LikesTagFilter, 1000, 10000);
waitForElementToDisplayAndAppend('#filter-block_prefixes', DislikeTagFilter, 1000, 10000);

history.onpushstate = function (state) {
  setTimeout(init, 200);
};

(function (history) {
  const pushState = history.pushState;

  history.pushState = function (state) {
    if (typeof history.onpushstate === 'function') {
      history.onpushstate({
        state
      });
    }

    return pushState.apply(history);
  };
})(window.history);

})();