ExHentai/E-Hentai Free Tagsets

Unlimited locally stored tagsets for free

  1. // ==UserScript==
  2. // @name ExHentai/E-Hentai Free Tagsets
  3. // @description Unlimited locally stored tagsets for free
  4. // @namespace Violentmonkey Scripts
  5. // @match https://exhentai.org/mytags
  6. // @match https://e-hentai.org/mytags
  7. // @version 1.0
  8. // @author shlsdv
  9. // @icon https://e-hentai.org/favicon.ico
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. const tagsets = GM_getValue('tagsets') || {};
  16. let newSelect;
  17. let pendingSaves = new Set();
  18.  
  19.  
  20. function usertagSave(a) {
  21. let id = a;
  22. pendingSaves.add(id);
  23. void 0 == usertag_xhr &&
  24. (
  25. usertag_xhr = new XMLHttpRequest,
  26. a = {
  27. method: 'setusertag',
  28. apiuid,
  29. apikey,
  30. tagid: a,
  31. tagwatch: document.getElementById('tagwatch_' + a).checked ? 1 : 0,
  32. taghide: document.getElementById('taghide_' + a).checked ? 1 : 0,
  33. tagcolor: document.getElementById('tagcolor_' + a).value,
  34. tagweight: document.getElementById('tagweight_' + a).value
  35. },
  36. api_call(usertag_xhr, a, () => usertagCallback(id))
  37. )
  38. }
  39. function usertagCallback(id) {
  40. var a = api_response(usertag_xhr);
  41. 0 != a &&
  42. (
  43. 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>',
  44. usertag_xhr = void 0
  45. )
  46. pendingSaves.delete(id);
  47. }
  48.  
  49. function getTagVals(div) {
  50. const tagpreviewDiv = div.querySelector('div[id*="tagpreview"]');
  51. const tagwatchInput = div.querySelector('input[id*="tagwatch"]');
  52. const taghideInput = div.querySelector('input[id*="taghide"]');
  53. const tagweightInput = div.querySelector('input[id*="tagweight"]');
  54. const tagcolorInput = div.querySelector('input[id*="tagcolor"]');
  55.  
  56. const dict = {}
  57. const title = tagpreviewDiv ? tagpreviewDiv.getAttribute('title') : '';
  58. const id = div.id.split('_').length > 1 ? parseInt(div.id.split('_')[1]) : 0;
  59. dict['watched'] = tagwatchInput ? tagwatchInput.checked : false;
  60. dict['hidden'] = taghideInput ? taghideInput.checked : false;
  61. dict['weight'] = tagweightInput ? parseInt(tagweightInput.value) || 0 : 0;
  62. dict['color'] = tagcolorInput ? tagcolorInput.value : '';
  63.  
  64. const inputs = [tagwatchInput, taghideInput, tagcolorInput, tagweightInput];
  65. return [title, id, dict, inputs];
  66. }
  67.  
  68. function getCurrentSet() {
  69. const dataDict = {};
  70. document.querySelectorAll('div[id*="usertag"]').forEach(usertagDiv => {
  71. const [title, id, vals, _] = getTagVals(usertagDiv);
  72. if (title != '') {
  73. dataDict[title] = vals;
  74. }
  75. });
  76.  
  77. return dataDict;
  78. }
  79.  
  80. function saveCurrentSet(name) {
  81. if (!name) {
  82. return;
  83. }
  84. const dict = getCurrentSet();
  85. tagsets[name] = dict;
  86. GM_setValue('tagsets', tagsets);
  87. populateSelect();
  88. newSelect.value = name;
  89. alert(`Saved tagset '${name}' to script storage!`);
  90. }
  91.  
  92. function loadSavedSet(key, save=true) {
  93. if (!(key in tagsets)) {
  94. return;
  95. }
  96. const dict = tagsets[key];
  97.  
  98. const usertagDivs = document.querySelectorAll('div[id*="usertag"]');
  99. (async () => {
  100. for (const usertagDiv of usertagDivs) {
  101. const [title, id, _, inputs] = getTagVals(usertagDiv);
  102. const [tagwatchInput, taghideInput, tagcolorInput, tagweightInput] = inputs;
  103.  
  104. if (!id || !(title in dict)) {
  105. continue;
  106. }
  107.  
  108. const [watch, hide, color, weight] = [dict[title]['watched'], dict[title]['hidden'], dict[title]['color'], dict[title]['weight']];
  109. const vals1 = [tagwatchInput.checked, taghideInput.checked, tagcolorInput.value, parseInt(tagweightInput.value)];
  110. const changed = ![watch, hide, color, weight].every((x, i) => x === vals1[i]);
  111. const saveInput = usertagDiv.querySelector('input[id*="tagsave"]');
  112.  
  113. if (!changed && !saveInput) {
  114. continue;
  115. }
  116.  
  117. while (pendingSaves.size > 0) {
  118. await new Promise(resolve => setTimeout(resolve, 100));
  119. }
  120.  
  121. tagwatchInput.click();
  122. tagwatchInput.click();
  123. tagwatchInput.checked = watch;
  124. taghideInput.checked = hide;
  125. tagcolorInput.value = color;
  126. update_tagcolor(id, tagcolorInput.value, color.replace(/^#*/, ""));
  127. tagweightInput.value = weight;
  128. if (save) {
  129. usertagSave(id);
  130. }
  131. //console.log(`${title}, ${id}, ${watch}, ${hide}, ${weight}`);
  132. }
  133. if (save) {
  134. setTimeout(() => alert("Loaded!"), 150);
  135. }
  136. })();
  137. }
  138.  
  139. function deleteTagset(key) {
  140. if (!(key in tagsets)) {
  141. return;
  142. }
  143. delete tagsets[key];
  144. GM_setValue('tagsets', tagsets);
  145. populateSelect();
  146. newSelect.value = 'None';
  147. }
  148.  
  149. function populateSelect() {
  150. newSelect.innerHTML = '';
  151. for (const key in tagsets) {
  152. const option = document.createElement('option');
  153. option.value = key;
  154. option.text = key;
  155. newSelect.appendChild(option);
  156. }
  157. }
  158.  
  159. (function() {
  160. const newDiv = document.createElement('div');
  161. newDiv.style.paddingLeft = '196px';
  162. newDiv.style.paddingTop = '8px';
  163.  
  164. const newButton = document.createElement('input');
  165. newButton.type = 'button';
  166. newButton.value = 'New';
  167. newButton.addEventListener('click', () => saveCurrentSet(prompt("Enter tagset name:")));
  168. newDiv.appendChild(newButton);
  169.  
  170. const inputButton = document.createElement('input');
  171. inputButton.type = 'button';
  172. inputButton.value = 'Save';
  173. inputButton.addEventListener('click', () => {
  174. let userInput = newSelect.value && newSelect.value in tagsets ? newSelect.value : prompt("Enter tagset name:");
  175. saveCurrentSet(userInput);
  176. });
  177. newDiv.appendChild(inputButton);
  178.  
  179. const delBtn = document.createElement('input');
  180. delBtn.type = 'button';
  181. delBtn.value = 'Delete';
  182. delBtn.addEventListener('click', () => deleteTagset(newSelect.value));
  183. newDiv.appendChild(delBtn);
  184.  
  185. newSelect = document.createElement('select');
  186. populateSelect();
  187. newSelect.value = 'None';
  188. newSelect.addEventListener('change', () => loadSavedSet(newSelect.value, false));
  189. newDiv.appendChild(newSelect);
  190.  
  191. const loadButton = document.createElement('input');
  192. loadButton.type = 'button';
  193. loadButton.value = 'Apply';
  194. loadButton.addEventListener('click', () => loadSavedSet(newSelect.value));
  195. newDiv.appendChild(loadButton);
  196.  
  197. const tagsetForm = document.getElementById('tagset_form');
  198. tagsetForm.insertAdjacentElement('afterend', newDiv);
  199.  
  200. })();