Sleazy Fork is available in English.

ExHentai/E-Hentai Free Tagsets

Unlimited locally stored tagsets for free

// ==UserScript==
// @name        ExHentai/E-Hentai Free Tagsets
// @description Unlimited locally stored tagsets for free
// @namespace   Violentmonkey Scripts
// @match       https://exhentai.org/mytags
// @match       https://e-hentai.org/mytags
// @version     1.0
// @author      shlsdv
// @icon        https://e-hentai.org/favicon.ico
// @grant       GM_getValue
// @grant       GM_setValue
// @license     MIT
// ==/UserScript==

const tagsets = GM_getValue('tagsets') || {};
let newSelect;
let pendingSaves = new Set();


function usertagSave(a) {
  let id = a;
  pendingSaves.add(id);
  void 0 == usertag_xhr &&
  (
    usertag_xhr = new XMLHttpRequest,
    a = {
      method: 'setusertag',
      apiuid,
      apikey,
      tagid: a,
      tagwatch: document.getElementById('tagwatch_' + a).checked ? 1 : 0,
      taghide: document.getElementById('taghide_' + a).checked ? 1 : 0,
      tagcolor: document.getElementById('tagcolor_' + a).value,
      tagweight: document.getElementById('tagweight_' + a).value
    },
    api_call(usertag_xhr, a, () => usertagCallback(id))
  )
}
function usertagCallback(id) {
  var a = api_response(usertag_xhr);
  0 != a &&
  (
    void 0 != a.error ? alert('Could not save tag: ' + a.error) : document.getElementById('selector_' + a.tagid).innerHTML = '<label class="lc"><input type="checkbox" name="modify_usertags[]" value="' + a.tagid + '" /><span></span></label>',
    usertag_xhr = void 0
  )
  pendingSaves.delete(id);
}

function getTagVals(div) {
  const tagpreviewDiv = div.querySelector('div[id*="tagpreview"]');
  const tagwatchInput = div.querySelector('input[id*="tagwatch"]');
  const taghideInput = div.querySelector('input[id*="taghide"]');
  const tagweightInput = div.querySelector('input[id*="tagweight"]');
  const tagcolorInput = div.querySelector('input[id*="tagcolor"]');

  const dict = {}
  const title = tagpreviewDiv ? tagpreviewDiv.getAttribute('title') : '';
  const id = div.id.split('_').length > 1 ? parseInt(div.id.split('_')[1]) : 0;
  dict['watched'] = tagwatchInput ? tagwatchInput.checked : false;
  dict['hidden'] = taghideInput ? taghideInput.checked : false;
  dict['weight'] = tagweightInput ? parseInt(tagweightInput.value) || 0 : 0;
  dict['color'] = tagcolorInput ? tagcolorInput.value : '';

  const inputs = [tagwatchInput, taghideInput, tagcolorInput, tagweightInput];
  return [title, id, dict, inputs];
}

function getCurrentSet() {
    const dataDict = {};
    document.querySelectorAll('div[id*="usertag"]').forEach(usertagDiv => {
        const [title, id, vals, _] = getTagVals(usertagDiv);
        if (title != '') {
          dataDict[title] = vals;
        }
    });

    return dataDict;
}

function saveCurrentSet(name) {
  if (!name) {
    return;
  }
  const dict = getCurrentSet();
  tagsets[name] = dict;
  GM_setValue('tagsets', tagsets);
  populateSelect();
  newSelect.value = name;
  alert(`Saved tagset '${name}' to script storage!`);
}

function loadSavedSet(key, save=true) {
  if (!(key in tagsets)) {
    return;
  }
  const dict = tagsets[key];

  const usertagDivs = document.querySelectorAll('div[id*="usertag"]');
  (async () => {
    for (const usertagDiv of usertagDivs) {
      const [title, id, _, inputs] = getTagVals(usertagDiv);
      const [tagwatchInput, taghideInput, tagcolorInput, tagweightInput] = inputs;

      if (!id || !(title in dict)) {
        continue;
      }

      const [watch, hide, color, weight] = [dict[title]['watched'], dict[title]['hidden'], dict[title]['color'], dict[title]['weight']];
      const vals1 = [tagwatchInput.checked, taghideInput.checked, tagcolorInput.value, parseInt(tagweightInput.value)];
      const changed = ![watch, hide, color, weight].every((x, i) => x === vals1[i]);
      const saveInput = usertagDiv.querySelector('input[id*="tagsave"]');

      if (!changed && !saveInput) {
        continue;
      }

      while (pendingSaves.size > 0) {
        await new Promise(resolve => setTimeout(resolve, 100));
      }

      tagwatchInput.click();
      tagwatchInput.click();
      tagwatchInput.checked = watch;
      taghideInput.checked = hide;
      tagcolorInput.value = color;
      update_tagcolor(id, tagcolorInput.value, color.replace(/^#*/, ""));
      tagweightInput.value = weight;
      if (save) {
        usertagSave(id);
      }
      //console.log(`${title}, ${id}, ${watch}, ${hide}, ${weight}`);
    }
    if (save) {
      setTimeout(() => alert("Loaded!"), 150);
    }
  })();
}

function deleteTagset(key) {
  if (!(key in tagsets)) {
    return;
  }
  delete tagsets[key];
  GM_setValue('tagsets', tagsets);
  populateSelect();
  newSelect.value = 'None';
}

function populateSelect() {
  newSelect.innerHTML = '';
  for (const key in tagsets) {
      const option = document.createElement('option');
      option.value = key;
      option.text = key;
      newSelect.appendChild(option);
  }
}

(function() {
    const newDiv = document.createElement('div');
    newDiv.style.paddingLeft = '196px';
    newDiv.style.paddingTop = '8px';

    const newButton = document.createElement('input');
    newButton.type = 'button';
    newButton.value = 'New';
    newButton.addEventListener('click', () => saveCurrentSet(prompt("Enter tagset name:")));
    newDiv.appendChild(newButton);

    const inputButton = document.createElement('input');
    inputButton.type = 'button';
    inputButton.value = 'Save';
    inputButton.addEventListener('click', () => {
      let userInput = newSelect.value && newSelect.value in tagsets ? newSelect.value : prompt("Enter tagset name:");
      saveCurrentSet(userInput);
    });
    newDiv.appendChild(inputButton);

    const delBtn = document.createElement('input');
    delBtn.type = 'button';
    delBtn.value = 'Delete';
    delBtn.addEventListener('click', () => deleteTagset(newSelect.value));
    newDiv.appendChild(delBtn);

    newSelect = document.createElement('select');
    populateSelect();
    newSelect.value = 'None';
    newSelect.addEventListener('change', () => loadSavedSet(newSelect.value, false));
    newDiv.appendChild(newSelect);

    const loadButton = document.createElement('input');
    loadButton.type = 'button';
    loadButton.value = 'Apply';
    loadButton.addEventListener('click', () => loadSavedSet(newSelect.value));
    newDiv.appendChild(loadButton);

    const tagsetForm = document.getElementById('tagset_form');
    tagsetForm.insertAdjacentElement('afterend', newDiv);

})();