f95zone tweaks

f95zone exclude tags and min like filter.

As of 2021-08-25. See the latest version.

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/latest/
// @version     1.3.7
// @author      3xd_Tango
// @require     https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@1,npm/@violentmonkey/[email protected]
// @require     https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// ==/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{width:127px;opacity:1;position:relative;left:0}.border-gradient{border:3px solid;border-image-slice:1}";

const likes = GM_getValue('likes', [['incest', '#ff0000'], ['harem', '#0011ff'], ['futa/trans', '#EE82EE'], ['loli', '#00f5ffff'], ['futa/trans protagonist', '#EE82EE']]);
const dislikes = GM_getValue('dislikes', ['female protagonist', 'text based', 'mind control']);
const totalTags = GM_getValue('tags', []);

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].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][0])) {
        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;
    const elementName = 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').toLowerCase() === 'false') {
        element[i].parentNode.parentNode.parentNode.setAttribute('isLikeLimit', 'true');
      } else {
        element[i].parentNode.parentNode.parentNode.setAttribute('isLikeLimit', 'false');
      }
    } else {
      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 protagonist', 'male protagonist', 'futa/trans protagonist'];
  const tagProtNames = dislikes.filter(e => e.includes('protagonist'));
  const b = [];
  let c = 0;
  dataTags.forEach(e => {
    if (tagProtNames.length >= 1) {
      if (tagProtNames.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++) {
      console.debug(`datatags includes? = ${dataTags.includes(tagProt[i])} for ${tagProt[i]} `);

      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 !== 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 === 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 = event.target.value;
  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];
    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.appendChild(document.createTextNode(tagName));
    event.target.parentNode.parentNode.childNodes[2].append(node);
    likeFilter();
    likeLimit();
    filterOut();
  }
}

function dislikeHandler(event) {
  const tagName = event.target.value;
  dislikes.push(tagName);
  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 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.innerHTML));
        GM_setValue('tags', totalTags);
      }
    });
  }
}

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

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

      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.createElement("div", {
    className: "filter-block accordion-block filter-block_prefix-group"
  }, VM.createElement("h4", {
    className: "filter-block_title accordion-toggle"
  }, "Like Tags"), VM.createElement("div", {
    className: "filter-block_content filter-block_v accordion-content"
  }, VM.createElement("div", {
    className: "selectize-input"
  }, VM.createElement("input", {
    autoComplete: "on",
    placeholder: "Enter a tag to filter...",
    className: "input-tag",
    onChange: likeHandler,
    list: "totalTagName"
  })), VM.createElement("datalist", {
    id: "totalTagName"
  }, totalTags.map(item => {
    return VM.createElement("option", {
      value: item
    });
  })), VM.createElement("div", {
    className: "selected-tags-wrap"
  }, likes.map(dl => VM.createElement("span", {
    onClick: e => tagRemoveHandler(e, true),
    style: {
      borderLeft: `${dl[1]} solid`
    }
  }, dl[0])))));
}

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

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

function mainFilter() {
  init();
  return VM.createElement(VM.Fragment, null, LikeLimitDisplay(), VM.createElement("div", {
    className: "filter-block"
  }, VM.createElement("h4", {
    className: "filter-block_title"
  }, "Filter Fav likes: ", VM.createElement("input", {
    type: "checkbox",
    id: "filter-fav-likes"
  }))), VM.createElement("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);

}());