ExResurrect

Resurrect E/Ex gallery listings

À partir de 2020-09-13. Voir la dernière version.

  1. // ==UserScript==
  2. // @name ExResurrect
  3. // @namespace https://sleazyfork.org/en/users/285675-hauffen
  4. // @version 1.12
  5. // @description Resurrect E/Ex gallery listings
  6. // @author Hauffen
  7. // @runat document-start
  8. // @require https://code.jquery.com/jquery-3.3.1.min.js
  9. // @include /https?:\/\/(e-|ex)hentai\.org\/.*/
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. let $ = window.jQuery;
  14. var spl = document.URL.split('/');
  15. const category = {doujinshi: 'ct2', manga: 'ct3', artistcg: 'ct4', gamecg: 'ct5', western: 'cta', nonh: 'ct9', imageset: 'ct6', cosplay: 'ct7', asianporn: 'ct8', misc: 'ct1'};
  16. const fileSizeLabels = [ "B", "KB", "MB", "GB" ];
  17. const defaultNamespace = "misc";
  18.  
  19. function gotonext () {
  20. // Override the default function to prevent redirect
  21. }
  22.  
  23. addJS_Node (gotonext); // Inject the override function before doing anything to avoid the timeout redirect
  24.  
  25. function addJS_Node(text, s_URL, funcToRun, runOnLoad) {
  26. var scriptNode = document.createElement('script');
  27. if (runOnLoad) {
  28. scriptNode.addEventListener("load", runOnLoad, false);
  29. }
  30. scriptNode.type = "text/javascript";
  31. if (text) scriptNode.textContent = text;
  32. if (s_URL) scriptNode.src = s_URL;
  33. if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
  34.  
  35. var targ = document.getElementsByTagName('head')[0] || document.body || document.documentElement;
  36. targ.appendChild(scriptNode);
  37. }
  38. // There's a better critera for this probably
  39. if (!$('.d').length) return; // Quit out of the function if the gallery is available
  40. $('.d').remove(); // Leave us with an entirely blank page to build up
  41. generateRequest();
  42.  
  43. /**
  44. * Convert the star count of a specified element to a double
  45. * @param {Object} el - A specific element within the DOM, or a double
  46. * @param {Boolean} transpose - Whether we're converting background position to double, or double to background position
  47. */
  48. function getStarNumber(el, transpose) {
  49. var starCount = {5: '0px -1px', 4.5: '0px -21px', 4: '-16px -1px', 3.5: '-16px -21px', 3: '-32px -1px', 2.5: '-32px -21px', 2: '-48px -1px', 1.5: '-48px -21px', 1: '-64px -1px', 0.5: '-64px -21px'};
  50. if (!transpose) {
  51. var stars = $(el).find('.ir').css('background-position');
  52. return Object.keys(starCount).find(key => starCount[key] === stars);
  53. } else return starCount[(Math.round(el * 2) / 2).toFixed(1)]; // Ratings are given in x.xx numbers, but we need either whole integers, or half integers
  54. }
  55.  
  56. /** ------ Helper functions cannibalized from the dnsev's script ------ */
  57. function getTagAndNamespace(tag) {
  58. const pattern = /^(?:([^:]*):)?([\w\W]*)$/;
  59. const match = pattern.exec(tag);
  60. return (match !== null) ?
  61. ({ tag: match[2], namespace: match[1] || defaultNamespace }) :
  62. ({ tag: tag, namespace: defaultNamespace });
  63. }
  64.  
  65. function getJsonString(value) {
  66. if (typeof(value) === "string") { return value; }
  67. if (typeof(value) === "undefined" || value === null) { return value; }
  68. return `${value}`;
  69. }
  70.  
  71. function toProperCase(text) {
  72. return text.replace(/(^|\W)(\w)/g, (m0, m1, m2) => `${m1}${m2.toUpperCase()}`);
  73. }
  74.  
  75. function getPrettyFileSize(bytes) {
  76. const ii = fileSizeLabels.length - 1;
  77. let i = 0;
  78. while (i < ii && bytes >= 1024) {
  79. bytes /= 1024;
  80. ++i;
  81. }
  82. return `${bytes.toFixed(i === 0 ? 0 : 2)} ${fileSizeLabels[i]}`;
  83. }
  84. /** ------------ */
  85.  
  86. /**
  87. * Generate the JSON request for the E-H API
  88. */
  89. function generateRequest() {
  90. var reqList = []; // We use an array for our gidlist, since the API can handle up to 25 galleries per request
  91. reqList[0] = [spl[4], spl[5]];
  92. var request = {"method": "gdata", "gidlist": reqList, "namespace": 1};
  93.  
  94. var req = new XMLHttpRequest();
  95. req.onreadystatechange = e => {
  96. if (req.readyState == 4) {
  97. if (req.status == 200) {
  98. var apirsp = JSON.parse(req.responseText);
  99. for (var i = 0; i < apirsp.gmetadata.length; i++) generateListing(apirsp.gmetadata[i]);
  100. } else {
  101. console.error();
  102. }
  103. }
  104. }
  105. req.open("POST", document.location.origin + "/api.php", true); // Due to CORS, we need to use the API on the same domain as the script
  106. req.send(JSON.stringify(request));
  107. }
  108.  
  109. function generateListing(glisting) {
  110. var d = new Date(glisting.posted * 1000);
  111. var language, translated;
  112. document.title = glisting.title;
  113. /** ------ Code blocks cannibalized from dnsev's code ------ */
  114. const tags = {};
  115. if (Array.isArray(glisting.tags)) {
  116. for (const jsonTag of glisting.tags) {
  117. const stringTag = getJsonString(jsonTag);
  118. if (stringTag === null) { continue; }
  119.  
  120. const {tag, namespace} = getTagAndNamespace(stringTag);
  121.  
  122. let namespaceTags;
  123. if (tags.hasOwnProperty(namespace)) {
  124. namespaceTags = tags[namespace];
  125. } else {
  126. namespaceTags = [];
  127. tags[namespace] = namespaceTags;
  128. }
  129.  
  130. namespaceTags.push(tag);
  131. }
  132. }
  133.  
  134. // Tag-based info
  135. if (tags.hasOwnProperty("language")) {
  136. const languageTags = tags.language;
  137. const translatedIndex = languageTags.indexOf("translated");
  138. translated = (translatedIndex >= 0);
  139. if (translatedIndex !== 0) {
  140. language = toProperCase(languageTags[0]);
  141. }
  142. } else {
  143. language = "Japanese";
  144. translated = false;
  145. }
  146. /** ------------ */
  147. // There's a better way to do this, but I suck
  148. var listing = $(`
  149. <div id="nb" class="nose1">
  150. <div><a href="` + document.location.origin + `">Front<span class="nbw1"> Page</span></a></div>
  151. <div><a href="` + document.location.origin + `/watched">Watched</a></div>
  152. <div><a href="` + document.location.origin + `/popular">Popular</a></div>
  153. <div><a href="` + document.location.origin + `/torrents.php">Torrents</a></div>
  154. <div><a href="` + document.location.origin + `/favorites.php">Fav<span class="nbw1">orite</span>s</a></div>
  155. <div><a href="` + document.location.origin + `/uconfig.php">Settings</a></div>
  156. <div><a href="` + document.location.origin + `/upload/manage.php"><span class="nbw2">My </span>Uploads</a></div>
  157. <div><a href="` + document.location.origin + `/mytags">My Tags</a></div>
  158. </div>
  159. <div class="gm">
  160. <div id="gleft">
  161. <div id="gd1">
  162. <div style="width:250px; height:354px; background:transparent url(` + glisting.thumb.substring(0, glisting.thumb.length-5) + `250.jpg) 0 0 no-repeat"></div>
  163. </div>
  164. </div>
  165. <div id="gd2">
  166. <h1 id="gn">` + glisting.title + `</h1>
  167. <h1 id="gj">` + glisting.title_jpn + `</h1>
  168. </div>
  169. <div id="gmid">
  170. <div id="gd3">
  171. <div id="gdc">
  172. <div class="cs ` + category[glisting.category.toLowerCase().replace(/ /g, '').replace(/-/g, '')] + `" onclick="document.location='` + document.location.origin + '/' + glisting.category.toLowerCase().replace(/ /g, '') + `'">` + glisting.category + `</div>
  173. </div>
  174. <div id="gdn">
  175. <a href="` + document.location.origin + '/uploader/' + glisting.uploader + '">' + glisting.uploader + `</a>
  176. </div>
  177. <div id="gdd">
  178. <table>
  179. <tbody>
  180. <tr><td class="gdt1">Posted:</td><td class="gdt2">` + d.getFullYear().toString() + '-' + (d.getMonth() + 1).toString().padStart(2, '0') + '-' + d.getDate().toString().padStart(2, '0') + ' ' + d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0') + `</td></tr>
  181. <tr><td class="gdt1">Visible:</td><td class="gdt2">` + (glisting.expunged ? 'No' : 'Yes') + `</td></tr>
  182. <tr><td class="gdt1">Language:</td><td class="gdt2">` + (translated ? language + `&nbsp <span class="halp" title="This gallery has been translated from the original language text.">TR</span>` : language) + `</td></tr>
  183. <tr><td class="gdt1">File Size:</td><td class="gdt2">` + getPrettyFileSize(glisting.filesize) + `</td></tr>
  184. <tr><td class="gdt1">Length:</td><td class="gdt2">` + glisting.filecount + ` pages</td></tr>
  185. </tbody>
  186. </table>
  187. </div>
  188. <div id="gdr">
  189. <table>
  190. <tbody>
  191. <tr>
  192. <td class="grt1">Rating:</td>
  193. <td class="grt2">
  194. <div id="rating_image" class="ir" style="background-position:` + getStarNumber(glisting.rating, true) + `"></div>
  195. </td>
  196. </tr>
  197. <tr><td id="rating_label" colspan="2">Average: ` + glisting.rating + `</td></tr>
  198. </tbody>
  199. </table>
  200. </div>
  201. </div>
  202. <div id="gd4">
  203. <div id="taglist">
  204. </div>
  205. </div>
  206. <div id="gd5">
  207. <p class="g3 gsp">
  208. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  209. <a href="">Gallery Unavailable</a>
  210. </p>
  211. <p class="g2 gsp">
  212. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  213. <a id="dl_eze" href="#">EZE JSON</a>
  214. </p>
  215. <p class="g2">
  216. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  217. <a id="dl_ehdl">E-HDL JSON</a>
  218. </p>
  219. <p class="g2">
  220. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  221. <a id="dl_gdl">Gallery-DL JSON</a>
  222. </p>
  223. </div>
  224. <div class="c"></div>
  225. </div>
  226. <div class="c"></div>
  227. </div>
  228. `);
  229. $('body').append(listing);
  230. // Generate taglist table
  231. var taglist = "<table><tbody>";
  232. for (const namespace in tags) {
  233. taglist += `<tr><td class="tc">${namespace}:</td><td>`;
  234. for (var i = 0; i < tags[namespace].length; i++) {
  235. taglist += `<div id="td_${namespace}:${tags[namespace][i]}" class="gt" style="opacity:1.0"><a id="ta_${namespace}:${tags[namespace][i]}" href="${document.location.origin}/tag/${namespace}:${tags[namespace][i]}">${tags[namespace][i]}</a></div>`;
  236. }
  237. taglist += "</td></tr>";
  238. }
  239. taglist += "</tbody></table>";
  240. $('#taglist').append(taglist);
  241. $('#dl_eze').on('click', function() {
  242. var json = JSON.stringify(toCommonJson(glisting, tags, language, translated), null, " "),
  243. blob = new Blob([json], {type: "octet/stream"}),
  244. url = window.URL.createObjectURL(blob);
  245. this.href = url;
  246. this.target = '_blank';
  247. this.download = 'info.json';
  248. });
  249. $('#dl_ehdl').on('click', function() {
  250. var blob = new Blob([buildEhdl(glisting, tags, language)], {type: "text/plain"}),
  251. url = window.URL.createObjectURL(blob);
  252. this.href = url;
  253. this.target = '_blank';
  254. this.download = 'info.json';
  255. });
  256. $('#dl_gdl').on('click', function() {
  257. var json = JSON.stringify(toCommonGalleryInfoJsonG(glisting, language), null, " "),
  258. blob = new Blob([json], {type: "octet/stream"}),
  259. url = window.URL.createObjectURL(blob);
  260. this.href = url;
  261. this.target = '_blank';
  262. this.download = 'info.json';
  263. });
  264. }
  265.  
  266. function buildEhdl(info, tags, language) {
  267. var d = new Date(info.posted * 1000);
  268. var d2 = new Date();
  269. var output = info.title + "\r\n" + info.title_jpn + "\r\n" + document.URL + "\r\n\r\nCategory: " + info.category + "\r\nUploader: " + info.uploader + "\r\nPosted: " + d.getFullYear().toString() + '-' + (d.getMonth() + 1).toString().padStart(2, '0') + '-' + d.getDate().toString().padStart(2, '0') + ' ' + d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0') +
  270. "\r\nParent: Null\r\nVisible: " + (info.expunged ? 'No' : 'Yes') + "\r\nLanguage: " + language + "\r\nFile Size: " + getPrettyFileSize(info.filesize) + "\r\nLength: " + info.filecount + " pages\r\nFavorited: Null\r\nRating: " + info.rating + "\r\n\r\nTags:\r\n";
  271. for (const namespace in tags) {
  272. output += `> ${namespace}: `;
  273. for (var i = 0; i < tags[namespace].length; i++) {
  274. output += `${tags[namespace][i]}`;
  275. if (i < tags[namespace].length - 1) output += ", ";
  276. }
  277. output += "\r\n";
  278. }
  279. output += `\r\n\r\n\r\nDownloaded on ${d2.toUTCString()}`;
  280. return output;
  281. }
  282.  
  283. /** ------ Functions cannabilized from dnsev's code ------ */
  284. function toCommonGalleryInfoJson(info, tags, language, translated) {
  285. const date = new Date(toNumberOrDefault(info.posted * 1000, 0));
  286. return {
  287. title: toStringOrDefault(info.title, ""),
  288. title_original: toStringOrDefault(info.title_jpn, ""),
  289. uploader: toStringOrDefault(info.uploader, ""),
  290.  
  291. category: toStringOrDefault(info.category, ""),
  292. tags: tagsToCommonJson(tags),
  293.  
  294. language: toStringOrDefault(language, ""),
  295. translated: !!translated,
  296.  
  297. upload_date: [
  298. date.getUTCFullYear(),
  299. date.getUTCMonth() + 1,
  300. date.getUTCDate(),
  301. date.getUTCHours(),
  302. date.getUTCMinutes(),
  303. date.getUTCSeconds()
  304. ],
  305.  
  306. source: {
  307. site: toStringOrDefault(document.location.host.substr(0, document.location.host.length - 4), ""),
  308. gid: (info.identifier !== null ? toNumberOrDefault(info.gid, 0) : 0),
  309. token: (info.identifier !== null ? toStringOrDefault(info.token, 0) : 0),
  310. parent_gallery: null,
  311. newer_version: []
  312. }
  313. };
  314. }
  315.  
  316. function toCommonGalleryInfoJsonG(info, language) {
  317. const date = new Date(toNumberOrDefault(info.posted * 1000, 0));
  318. return {
  319. category: toStringOrDefault(document.location.host.substr(0, document.location.host.length - 4), ""),
  320. cost: null,
  321. count: info.filecount,
  322. date: toStringOrDefault(getTimestampDateString(date), ""),
  323. extension: null,
  324. filename: null,
  325. gallery_id: toNumberOrDefault(info.gid, 0),
  326. gallery_size: toNumberOrDefault(info.filesize, 0),
  327. gallery_token: toStringOrDefault(info.token, 0),
  328. height: null,
  329. image_token: null,
  330. lang: null,
  331. language: toStringOrDefault(language, ""),
  332. num: 1,
  333. parent: null,
  334. size: toNumberOrDefault(info.filesize, 0),
  335. subcategory: 'gallery',
  336. tags: info.tags,
  337. title: toStringOrDefault(info.title, ""),
  338. title_jp: toStringOrDefault(info.title_jpn, ""),
  339. uploader: toStringOrDefault(info.uploader, ""),
  340. visible: info.expunged ? 'No' : 'Yes',
  341. width: null
  342. };
  343. }
  344.  
  345. function getTimestampDateString(timestamp) {
  346. const date = new Date(timestamp);
  347. const year = date.getFullYear().toString();
  348. const month = (date.getMonth() + 1).toString().padStart(2, "0");
  349. const day = date.getDate().toString().padStart(2, "0");
  350. const hour = date.getHours().toString().padStart(2, "0");
  351. const minute = date.getMinutes().toString().padStart(2, "0");
  352. const seconds = date.getSeconds().toString().padstart(2, "0");
  353. return `${year}-${month}-${day} ${hour}:${minute}:${seconds}`;
  354. }
  355.  
  356. function toStringOrDefault(value, defaultValue) {
  357. return typeof(value) === "string" ? value : defaultValue;
  358. }
  359.  
  360. function toNumberOrDefault(value, defaultValue) {
  361. return Number.isNaN(value) ? defaultValue : value;
  362. }
  363.  
  364. function tagsToCommonJson(tags) {
  365. const result = {};
  366. for (const namespace in tags) {
  367. if (!Object.prototype.hasOwnProperty.call(tags, namespace)) { continue; }
  368. const tagList = tags[namespace];
  369. result[namespace] = [...tagList];
  370. }
  371. return result;
  372. }
  373.  
  374. function toCommonJson(info, tags, language, translated) {
  375. return {
  376. gallery_info: toCommonGalleryInfoJson(info, tags, language, translated)
  377. };
  378. }
  379. })();