ehentai tag translator

hover tag, show translation

  1. // ==UserScript==
  2. // @name ehentai tag translator
  3. // @namespace Violentmonkey Scripts
  4. // @match https://exhentai.org/*
  5. // @exclude-match https://exhentai.org/s/*
  6. // @grant GM_addStyle
  7. // @grant GM_xmlhttpRequest
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @version 1.1
  11. // @author -
  12. // @description hover tag, show translation
  13. // ==/UserScript==
  14.  
  15. /**
  16. * find node
  17. * if have node, define translation, css
  18. * add translation div
  19. */
  20.  
  21. const $ = s => document.querySelector(s);
  22. const $$ = s => document.querySelectorAll(s);
  23.  
  24. main();
  25.  
  26. function fetch2(detail) {
  27. return new Promise((resolve, reject) => {
  28. detail.onload = resolve;
  29. detail.onerror = reject;
  30. GM_xmlhttpRequest(detail);
  31. });
  32. }
  33.  
  34. async function updateDB(db) {
  35. console.info('[ehentai_tag_translator] updating translation database.');
  36. const DBversion = 6;
  37. const dbUrl = 'https://github.com/EhTagTranslation/Database/releases/latest/download/db.text.json';
  38. const remoteDB = await fetch2({ url: dbUrl, method: 'GET' }).then(rsp => JSON.parse(rsp.responseText)).catch(err => alert('failed to download tag database from github'));
  39. if (remoteDB.version !== DBversion) {
  40. // database changelog: https://github.com/EhTagTranslation/Database/wiki/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
  41. alert('EhTag Database version changed, please update script');
  42. return;
  43. }
  44. if (remoteDB.head.sha === db.sha) {
  45. return;
  46. }
  47.  
  48. db.sha = remoteDB.head.sha;
  49. db.data = {};
  50. for (let data of remoteDB.data) {
  51. const key = data.namespace;
  52. db.data[key] = {};
  53. for (let subKey in data.data) {
  54. // needs name translation only
  55. const value = data.data[subKey]['name'];
  56. if (subKey !== value) {
  57. // ignore invalid translation
  58. db.data[key][subKey] = value;
  59. }
  60. }
  61. }
  62. console.info('[ehentai_tag_translator] translation database updated.');
  63. return db;
  64. }
  65.  
  66. async function loadDB() {
  67. let db = GM_getValue('db', {});
  68. // update database after 7 days
  69. const expiration = 7 * 24 * 60 * 60 * 1000;
  70. if (db.lastUpdate === undefined) {
  71. db.lastUpdate = 0; // needs update
  72. }
  73. const expired = Date.now() - db.lastUpdate > expiration ? true : false;
  74. if (expired) {
  75. db = await updateDB(db);
  76. db.lastUpdate = Date.now();
  77. GM_setValue('db', db);
  78. }
  79. // console.log(db);
  80. return db;
  81. }
  82.  
  83. function translateTagEl(el, translation) {
  84. const translationEl = document.createElement('div');
  85. translationEl.classList.add('eh-tag-translation');
  86. translationEl.innerText = translation;
  87. el.append(translationEl);
  88. }
  89.  
  90. async function main() {
  91. let tagEls = $$('div[class^="gt"]');
  92.  
  93. if (tagEls.length > 0) {
  94. // load db only if needs translation
  95. const db = await loadDB();
  96. const css = `
  97. .eh-tag-translation{
  98. display:none;
  99. position: absolute;
  100. top: 20px;
  101. left: 0;
  102. z-index: 999;
  103. background: #5f636b;
  104. padding: 5px;
  105. width: max-content;
  106. max-width: 300px;
  107. border-radius: 2px;
  108. box-shadow: 1px 1px 2px 0 #0005;
  109. transition-duration: 1000ms;
  110. }
  111. /*show translation in list page*/
  112. .gl3c > a > div{
  113. overflow: inherit!important;
  114. }
  115. div.gt,
  116. div.gtl{
  117. opacity: 1!important;
  118. }
  119. div.gt:hover .eh-tag-translation,
  120. div.gtl:hover .eh-tag-translation{
  121. display: block;
  122. }
  123. .eh-tag-translation::before {
  124. border: 5px solid transparent;
  125. border-bottom-color: transparent;
  126. border-bottom-color: #5f636b;
  127. content: '';
  128. top: -10px;
  129. width: 0;
  130. height: 0;
  131. position: absolute;
  132. }
  133. `;
  134. GM_addStyle(css);
  135. for (let el of tagEls) {
  136. let arr;
  137. if (el.id === 'ehv-panel-btn') {
  138. continue;
  139. } else if (el.id !== '') {
  140. arr = el.id.replace('td_', '').split(':');
  141. } else if (el.title !== '') {
  142. arr = el.title.split(':');
  143. } else {
  144. console.log('tag not found in element', el);
  145. continue;
  146. }
  147. if (arr.length === 2) {
  148. const key = arr[0];
  149. const subKey = arr[1].replace('_', ' ');
  150. if(db.data[key]){
  151. const translation = db.data[key][subKey];
  152. if (translation) {
  153. translateTagEl(el, translation);
  154. } else {
  155. console.log('[ehentai_tag_translator] translation not found:', { key, subKey });
  156. }
  157. }else{
  158. console.log('[ehentai_tag_translator] unknown tag type: ', key);
  159. }
  160. } else {
  161. console.log('[ehentai_tag_translator] unknown attribute:', arr);
  162. }
  163. }
  164. }
  165. }