E-Hentai Info on Hover

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

Устаревшая версия за 21.08.2014. Перейдите к последней версии.

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