// ==UserScript==
// @name f95zone tweaks
// @namespace f95zone tweaks
// @description f95zone exclude tags and min like filter.
// @match https://f95zone.to/latest/
// @version 1.3.5
// @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.backgroundColor = likes[j][1];
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: {
backgroundColor: dl[1]
}
}, 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);
}());