Sleazy Fork is available in English.

E-Hentai Info on Hover

Displays additional gallery information when hovering over thumbnails. Only works in thumbnail mode.

  1. // ==UserScript==
  2. // @name E-Hentai Info on Hover
  3. // @description Displays additional gallery information when hovering over thumbnails. Only works in thumbnail mode.
  4. // @namespace http://userscripts.org/users/106844
  5. // @include http://e-hentai.org/*
  6. // @include https://e-hentai.org/*
  7. // @include http://g.e-hentai.org/*
  8. // @include https://g.e-hentai.org/*
  9. // @include http://exhentai.org/*
  10. // @include https://exhentai.org/*
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_xmlhttpRequest
  14. // @version 0.2.6
  15. // ==/UserScript==
  16.  
  17. var save = function(key,value) {
  18. if (typeof(GM_setValue) !== 'undefined') GM_setValue(key,value);
  19. else localStorage.setItem(key,value);
  20. };
  21.  
  22. var load = function(key,def) {
  23. if (typeof(GM_getValue) !== 'undefined') return GM_getValue(key,def);
  24. else return (localStorage.getItem(key) || def);
  25. };
  26.  
  27. var doRequest = function(url, method, data, callback) {
  28. if (typeof(GM_xmlhttpRequest) === 'undefined') {
  29. var xhr = new XMLHttpRequest();
  30. xhr.open(method, url);
  31. xhr.onload = function() { callback(this); };
  32. xhr.send(data);
  33. } else {
  34. GM_xmlhttpRequest({
  35. url: url,
  36. method: method,
  37. data: data,
  38. onload: callback
  39. });
  40. }
  41. };
  42.  
  43. /* * * * * * * * * */
  44.  
  45. var stringify = function(data) {
  46. var volatile = (new Date().valueOf() - data.posted*1000) < 1000*60*60*2;
  47. return data.gid + ':' + (volatile*1) + ':' + data.filesize + ':' + data.tags.join(',') + ':' + data.uploader;
  48. };
  49.  
  50. var parse = function(data) {
  51. if (data && data[0] == '@') data = data.slice(1);
  52. var tokens = data.split(/:/);
  53. if (!tokens || tokens.length < 5) return null;
  54. return { gid: parseInt(tokens[0],10), volatile: parseInt(tokens[1],10) == 1,
  55. size: parseInt(tokens[2],10), tags: tokens[3].split(','), uploader: tokens[4] };
  56. };
  57.  
  58. var getRegex = function(gid,flags) {
  59. return new RegExp('(@|^)' + gid + ':[^@]+',flags);
  60. };
  61.  
  62. /* * * * * * * * * */
  63.  
  64. var checkStorage = function(gid) {
  65. var data = load('g.cache',null);
  66. if (data === null) return null;
  67. var match = data.match(getRegex(gid));
  68. return match === null ? null : parse(match[0]);
  69. };
  70.  
  71. var cleanStorage = function() {
  72. var data = load('g.cache',null), lastClean = load('g.lastClean',null);
  73. if (data === null) return;
  74. var now = new Date().valueOf(), hours = 1000*60*60;
  75. if (lastClean !== null && now - parseInt(lastClean,10) < 6*hours) return;
  76. data = data.split(/@/).filter(function(x) { var parsed = parse(x); return parsed && !parsed.volatile; });
  77. save('g.cache',data.join('@'));
  78. save('g.lastClean',now);
  79. };
  80.  
  81. /* * * * * * * * * */
  82.  
  83. var startApiRequest = function(target) {
  84. apiBusy = true;
  85. var request = [ [ target.gid, target.token ] ], data = load('g.cache','');
  86. var temp = targets.filter(function(x) { return x.gid != target.gid && !getRegex(x.gid).test(data); });
  87. temp = temp.slice(0,24).map(function(x) { return [ x.gid, x.token ]; });
  88. request = request.concat(temp);
  89. request = JSON.stringify({ method: 'gdata', gidlist: request });
  90. doRequest('https://e-hentai.org/api.php', 'POST', request, function(data) { onApiLoad(target,data.responseText); });
  91. };
  92.  
  93. var onApiLoad = function(target,response) {
  94. var data = load('g.cache',null);
  95. data = data === null ? [ ] : data.split(/@/);
  96. response = JSON.parse(response);
  97. response.gmetadata.forEach(function(x) { data.push(stringify(x)); });
  98. save('g.cache',data.slice(-1000).join('@'));
  99. apiBusy = false;
  100. if (timeout !== null) showData(target,checkStorage(target.gid));
  101. };
  102.  
  103. /* * * * * * * * * */
  104.  
  105. var mouseOver = function(target) {
  106. if (apiBusy) return;
  107. if (timeout !== null) clearTimeout(timeout);
  108. timeout = setTimeout(function() { hoverTimeout(target); },500);
  109. };
  110.  
  111. var mouseLeave = function(target) {
  112. if (timeout !== null) clearTimeout(timeout);
  113. timeout = null;
  114. target.target.classList.remove('gShow');
  115. };
  116.  
  117. var hoverTimeout = function(target) {
  118. if (target.target.querySelector('.gData') !== null) {
  119. target.target.classList.add('gShow');
  120. return;
  121. }
  122. var data = checkStorage(target.gid);
  123. if (data !== null) showData(target,data);
  124. else startApiRequest(target);
  125. };
  126.  
  127. /* * * * * * * * * */
  128.  
  129. var showData = function(target,data) {
  130. if (data === null) return;
  131. var div = document.createElement('div');
  132. var size = (Math.round(data.size/1024/1024*100)/100) + 'MB';
  133. div.appendChild(document.createElement('div')).innerHTML = size;
  134. if (data.uploader) {
  135. var uploader = document.createElement('a');
  136. uploader.href = window.location.origin + '/uploader/' + data.uploader;
  137. uploader.textContent = data.uploader;
  138. var uploaderContainer = document.createElement('span');
  139. uploaderContainer.appendChild(uploader);
  140. div.appendChild(uploaderContainer);
  141. }
  142. var tags = div.appendChild(document.createElement('div'));
  143. data.tags.forEach(function(x,n) {
  144. var a = tags.appendChild(document.createElement('a'));
  145. a.href = '/?f_search=' + x.replace(/\s/g,'+');
  146. a.innerHTML = x;
  147. if (n < data.tags.length-1) tags.appendChild(document.createTextNode(', '));
  148. });
  149. div.className = 'gData id1';
  150. target.target.appendChild(div);
  151. setTimeout(function() { target.target.classList.add('gShow'); },10);
  152. };
  153.  
  154. /* * * * * * * * * */
  155.  
  156. var onPanda = (window.location.href.indexOf('exhentai') != -1);
  157. var style = document.createElement('style');
  158. style.innerHTML =
  159. '.gData { position: absolute; top: 0px; left: 0px; text-align: left; padding: 5px;' +
  160. 'font-weight: bold; height: 100%; width: 95% !important; z-index: 1; font-size: 10px; margin: 0 !important;' +
  161. 'border: none !important; border-radius: 0 !important; transition: left .5s; left: -300px;' +
  162. 'color: ' + (onPanda ? 'white' : 'black') + '}' +
  163. '.gShow > .gData { left: 0px !important; }' +
  164. '.gData > :first-child:before { content: "Size: " }' +
  165. '.gData > :nth-child(2):not(:last-child):before { content: "Uploader: " }' +
  166. '.gData > :last-child:before { content: "Tags: " }' +
  167. '.gData > :last-child > a { color: ' + (onPanda ? 'white' : 'black') + ' !important; }' +
  168. '.gData > :last-child > a:hover { background: red !important; }' +
  169. '.automatedButton { z-index: 2; }';
  170. document.head.appendChild(style);
  171.  
  172. /* * * * * * * * * */
  173.  
  174. var timeout = null, apiBusy = false;
  175. var targets = Array.prototype.slice.call(document.querySelectorAll('.id3 > a'),0);
  176.  
  177. targets = targets.map(function(x) {
  178. var tokens = x.href.match(/g\/(\d+)\/([0-9a-f]{10,10})/);
  179. if (!tokens) return null;
  180. return { target: x, gid: parseInt(tokens[1],10), token: tokens[2] };
  181. });
  182. targets = targets.filter(function(x) { return x !== null; });
  183.  
  184. targets.forEach(function(x) {
  185. x.target.addEventListener('mouseover',function() { mouseOver(x); },false);
  186. x.target.addEventListener('mouseleave',function() { mouseLeave(x); },false);
  187. });
  188.  
  189. cleanStorage();