ExResurrect

Resurrect E/Ex gallery listings

  1. // ==UserScript==
  2. // @name ExResurrect
  3. // @namespace https://sleazyfork.org/en/users/285675-hauffen
  4. // @version 1.21.25
  5. // @description Resurrect E/Ex gallery listings
  6. // @author Hauffen
  7. // @license MIT
  8. // @runat document-start
  9. // @require https://code.jquery.com/jquery-3.3.1.min.js
  10. // @include /https?:\/\/(e-|ex)hentai\.org\/.*/
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. let $ = window.jQuery;
  15. var spl = document.URL.split('/');
  16. var dead, claim, language, translated;
  17. const category = {doujinshi: 'ct2', manga: 'ct3', artistcg: 'ct4', gamecg: 'ct5', western: 'cta', nonh: 'ct9', imageset: 'ct6', cosplay: 'ct7', asianporn: 'ct8', misc: 'ct1'};
  18. const fileSizeLabels = [ "B", "KB", "MB", "GB" ];
  19. const defaultNamespace = "misc";
  20. const tags = {};
  21.  
  22. if (spl[3] != 'g') return;
  23.  
  24. function gotonext() {};
  25. // Override the default function to prevent redirect
  26.  
  27. addJS_Node (gotonext); // Inject the override function before doing anything to avoid the timeout redirect
  28.  
  29. function addJS_Node(text, s_URL, funcToRun, runOnLoad) {
  30. var scriptNode = document.createElement('script');
  31. if (runOnLoad) {
  32. scriptNode.addEventListener("load", runOnLoad, false);
  33. }
  34. scriptNode.type = "text/javascript";
  35. if (text) scriptNode.textContent = text;
  36. if (s_URL) scriptNode.src = s_URL;
  37. if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
  38.  
  39. var targ = document.getElementsByTagName('head')[0] || document.body || document.documentElement;
  40. targ.appendChild(scriptNode);
  41. }
  42.  
  43. // There's a better critera for this probably
  44. if ($('.d').length && !spl[3].match('upload')) {
  45. dead = true;
  46. if ($('.d').text().indexOf("copyright") > 0) claim = $('.d').text().split('by')[1].split('.')[0].trim();
  47. $('.d').remove(); // Leave us with an entirely blank page to build up
  48. }
  49. generateRequest();
  50.  
  51. /**
  52. * Generate the JSON request for the E-H API
  53. */
  54. function generateRequest() {
  55. var reqList = [[spl[4], spl[5]]]; // We use an array for our gidlist, since the API can handle up to 25 galleries per request
  56. var request = {"method": "gdata", "gidlist": reqList, "namespace": 1};
  57.  
  58. var req = new XMLHttpRequest();
  59. req.onreadystatechange = e => {
  60. if (req.readyState == 4) {
  61. if (req.status == 200) {
  62. var apirsp = JSON.parse(req.responseText);
  63. for (var i = 0; i < apirsp.gmetadata.length; i++) {
  64. if (dead) generateListing(apirsp.gmetadata[i]);
  65. else generateLiveListing(apirsp.gmetadata[i]);
  66. }
  67. } else {
  68. console.error();
  69. }
  70. }
  71. }
  72. 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
  73. req.send(JSON.stringify(request));
  74. }
  75.  
  76. function generateListing(glisting) {
  77. var d = new Date(glisting.posted * 1000);
  78. document.title = glisting.title;
  79. generateTags(glisting);
  80. // There's a better way to do this, but I suck
  81. var listing = $(`
  82. <div id="nb" class="nose1">
  83. <div><a href="` + document.location.origin + `">Front<span class="nbw1"> Page</span></a></div>
  84. <div><a href="` + document.location.origin + `/watched">Watched</a></div>
  85. <div><a href="` + document.location.origin + `/popular">Popular</a></div>
  86. <div><a href="` + document.location.origin + `/torrents.php">Torrents</a></div>
  87. <div><a href="` + document.location.origin + `/favorites.php">Fav<span class="nbw1">orite</span>s</a></div>
  88. <div><a href="` + document.location.origin + `/uconfig.php">Settings</a></div>
  89. <div><a href="` + document.location.origin + `/upload/manage.php"><span class="nbw2">My </span>Uploads</a></div>
  90. <div><a href="` + document.location.origin + `/mytags">My Tags</a></div>
  91. </div>
  92. <div class="gm">
  93. <div id="gleft">
  94. <div id="gd1">
  95. <div style="width:250px; height:354px; background:transparent url(` + glisting.thumb.substring(0, glisting.thumb.length-5) + `250.jpg) 0 0 no-repeat"></div>
  96. </div>
  97. </div>
  98. <div id="gd2">
  99. <h1 id="gn">` + glisting.title + `</h1>
  100. <h1 id="gj">` + glisting.title_jpn + `</h1>
  101. </div>
  102. <div id="gmid">
  103. <div id="gd3">
  104. <div id="gdc">
  105. <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>
  106. </div>
  107. <div id="gdn">
  108. <a href="` + document.location.origin + '/uploader/' + glisting.uploader + '">' + glisting.uploader + `</a>
  109. </div>
  110. <div id="gdd">
  111. <table>
  112. <tbody>
  113. <tr><td class="gdt1">Posted:</td><td class="gdt2">` + d.getUTCFullYear().toString() + '-' + (d.getUTCMonth() + 1).toString().padStart(2, '0') + '-' + d.getUTCDate().toString().padStart(2, '0') + ' ' + d.getUTCHours().toString().padStart(2, '0') + ':' + d.getUTCMinutes().toString().padStart(2, '0') + `</td></tr>
  114. <tr><td class="gdt1">Visible:</td><td class="gdt2">` + (glisting.expunged ? 'No' : 'Yes') + `</td></tr>
  115. <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>
  116. <tr><td class="gdt1">File Size:</td><td class="gdt2">` + getPrettyFileSize(glisting.filesize) + `</td></tr>
  117. <tr><td class="gdt1">Length:</td><td class="gdt2">` + glisting.filecount + ` pages</td></tr>
  118. <tr><td class="gdt1">Removal:</td><td class="gdt2">` + claim + `</td></tr>
  119. </tbody>
  120. </table>
  121. </div>
  122. <div id="gdr">
  123. <table>
  124. <tbody>
  125. <tr>
  126. <td class="grt1">Rating:</td>
  127. <td class="grt2">
  128. <div id="rating_image" class="ir" style="background-position:` + getStarNumber(glisting.rating, true) + `"></div>
  129. </td>
  130. </tr>
  131. <tr><td id="rating_label" colspan="2">Average: ` + glisting.rating + `</td></tr>
  132. </tbody>
  133. </table>
  134. </div>
  135. <div id="gdf">
  136. <div style="float:left; cursor:pointer;" id="fav"></div>
  137. &nbsp;
  138. <a id="favoritelink" href="#" onclick="window.open('` + document.location.origin + '/gallerypopups.php?gid=' + glisting.gid + '&t=' + glisting.token + `&act=addfav','Add to Favorites','width=675,height=415')">
  139. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  140. Add to Favorites
  141. </a>
  142. </div>
  143. </div>
  144. <div id="gd4">
  145. <div id="taglist">
  146. </div>
  147. </div>
  148. <div id="gd5">
  149. <p class="g3 gsp">
  150. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  151. <a href="">Gallery Unavailable</a>
  152. </p>
  153. <p class="g2 gsp">
  154. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  155. <a id="dl_eze" href="#">EZE JSON</a>
  156. </p>
  157. <p class="g2">
  158. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  159. <a id="dl_ehdl">E-HDL JSON</a>
  160. </p>
  161. <p class="g2">
  162. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  163. <a id="dl_gdl">Gallery-DL JSON</a>
  164. </p>
  165. </div>
  166. <div class="c"></div>
  167. </div>
  168. <div class="c"></div>
  169. </div>
  170. `);
  171. $('body').append(listing);
  172. // Generate taglist table
  173. var taglist = "<table><tbody>";
  174. for (const namespace in tags) {
  175. taglist += `<tr><td class="tc">${namespace}:</td><td>`;
  176. for (var i = 0; i < tags[namespace].length; i++) {
  177. 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>`;
  178. }
  179. taglist += "</td></tr>";
  180. }
  181. taglist += "</tbody></table>";
  182. $('#taglist').append(taglist);
  183. // I want to make these smaller, but the 'this' call prevents me from doing so
  184. $('#dl_eze').on('click', function() {
  185. var json = JSON.stringify(toEze(glisting), null, " "),
  186. blob = new Blob([json], {type: "octet/stream"}),
  187. url = window.URL.createObjectURL(blob);
  188. this.href = url;
  189. this.target = '_blank';
  190. this.download = 'info.json';
  191. });
  192. $('#dl_ehdl').on('click', function() {
  193. var blob = new Blob([toEhDl(glisting)], {type: "text/plain"}),
  194. url = window.URL.createObjectURL(blob);
  195. this.href = url;
  196. this.target = '_blank';
  197. this.download = 'info.json';
  198. });
  199. $('#dl_gdl').on('click', function() {
  200. var json = JSON.stringify(toGalleryDl(glisting), null, " "),
  201. blob = new Blob([json], {type: "octet/stream"}),
  202. url = window.URL.createObjectURL(blob);
  203. this.href = url;
  204. this.target = '_blank';
  205. this.download = 'info.json';
  206. });
  207. // Try to generate torrent links
  208. if (glisting.torrentcount > 0) {
  209. $(`<div id="torrents" style="border-top:1px solid #000; padding: 10px 10px 5px 10px;"><p><span class="halp" title="If the torrent link doesn't work, try the magnet link">Possible Torrents:</span></p></div>`).appendTo('.gm');
  210. for (var j = 0; j < glisting.torrentcount; j++) {
  211. $(`<div><img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" /><a style="padding-left:3px;margin-right:1.5px;" href="` + generateTorrentLink(glisting, j) + `">` + glisting.torrents[j].name + `</a>&#9;` + getPrettyFileSize(glisting.torrents[j].fsize) + `<span style="float:right">Added ` + getTimestampDateString(glisting.torrents[j].added * 1000) + `</span><p style="margin-left:8px"><a href="magnet:?xt=urn:btih:` + glisting.torrents[j].hash + `">Magnet Link</a></p>`).appendTo('#torrents');
  212. }
  213. }
  214. generateSearchLink(glisting);
  215. }
  216.  
  217. function generateLiveListing(glisting) {
  218. generateTags(glisting);
  219. var listing = $(`
  220. <p class="g2">
  221. <img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" />
  222. <a id="dl_eze" href="#">EZE</a> / <a id="dl_ehdl" href="#">E-HDL</a> / <a id="dl_gdl" href="#">G-DL</a>
  223. </p>`);
  224. $('#gd5').append(listing);
  225. $('#gmid').css('height', 'unset');
  226. $('#dl_eze').on('click', function() {
  227. var json = JSON.stringify(toEze(glisting), null, " "),
  228. blob = new Blob([json], {type: "octet/stream"}),
  229. url = window.URL.createObjectURL(blob);
  230. this.href = url;
  231. this.target = '_blank';
  232. this.download = 'info.json';
  233. });
  234. $('#dl_ehdl').on('click', function() {
  235. var blob = new Blob([toEhDl(glisting)], {type: "text/plain"}),
  236. url = window.URL.createObjectURL(blob);
  237. this.href = url;
  238. this.target = '_blank';
  239. this.download = 'info.json';
  240. });
  241. $('#dl_gdl').on('click', function() {
  242. var json = JSON.stringify(toGalleryDl(glisting), 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. generateSearchLink(glisting);
  250. }
  251.  
  252. function generateSearchLink(glisting) {
  253. var menu = $(`
  254. <div id="menu">
  255. <button class="menuControl">Custom Search</button>
  256. <div class="menuContent">
  257. <a href='/?f_cats=0&f_sname=1&f_search="` + encodeURIComponent(getShortTitle(glisting.title)) + `"' rel="noreferrer" target="_blank">Search by Name</a>
  258. <a href='/?f_cats=0&f_sname=1&f_search="` + encodeURIComponent(glisting.title) + `"' rel="noreferrer" target="_blank">Search by Full Name</a>
  259. <a href='https://nhentai.net/search/?q="` + encodeURIComponent(getShortTitle(glisting.title)) + `"' rel="noreferrer" target="_blank">Search by Name (nhentai.net)</a>
  260. <a href='https://nhentai.net/search/?q="` + encodeURIComponent(glisting.title) + `"' rel="noreferrer" target="_blank">Search by Full Name (nhentai.net)</a>
  261. <a href="https://hitomi.la/search.html?` + encodeURIComponent(getShortTitle(glisting.title)) + `" rel="noreferrer" target="_blank">Search by Name (hitomi.la)</a>
  262. <a href="https://panda.chaika.moe/search/?qsearch=` + encodeURIComponent(getShortTitle(glisting.title)) + `" rel="noreferrer" target="_blank">Search by Name (chaika)</a>
  263. <a href="https://panda.chaika.moe/search/?qsearch=` + encodeURIComponent(glisting.title) + `" rel="noreferrer" target="_blank">Search by Full Name (chaika)</a>
  264. <a href="https://panda.chaika.moe/search/?qsearch=` + encodeURIComponent(getGalleryUrl(glisting)) + `" rel="noreferrer" target="_blank">Search by URL (chaika)</a>
  265. <a href="https://www.dlsite.com/maniax/fsr/=/language/jp/sex_category%5B0%5D/male/keyword/` + encodeURIComponent(getShortTitle(glisting.title_jpn)) + `" rel="noreferrer" target="_blank">Search by JP Title (DLsite)</a>
  266. </div>
  267. </div>`);
  268. $('.gm').first().append(menu);
  269. $(document).click(e => {
  270. if (e.target.className != 'menuControl') {
  271. $('div.menuContent').css("display", "none");
  272. }
  273. });
  274. $('.menuControl').click(e => {
  275. $('div.menuContent').css("display", "flex");
  276. });
  277. }
  278.  
  279. function generateTorrentLink(glisting, index) {
  280. if (window.location.hostname.indexOf("exhentai") > 0) return window.location.origin + "/torrent/" + glisting.gid + "/" + glisting.torrents[index].hash + ".torrent";
  281. else return "https://ehtracker.org/get/" + glisting.gid + "/" + glisting.torrents[index].hash + ".torrent";
  282. }
  283.  
  284. function generateTags(glisting) {
  285. if (Array.isArray(glisting.tags)) {
  286. for (const jsonTag of glisting.tags) {
  287. const stringTag = getJsonString(jsonTag);
  288. if (stringTag === null) { continue; }
  289.  
  290. const {tag, namespace} = getTagAndNamespace(stringTag);
  291.  
  292. let namespaceTags;
  293. if (tags.hasOwnProperty(namespace)) {
  294. namespaceTags = tags[namespace];
  295. } else {
  296. namespaceTags = [];
  297. tags[namespace] = namespaceTags;
  298. }
  299.  
  300. namespaceTags.push(tag);
  301. }
  302. }
  303.  
  304. // Tag-based info
  305. if (tags.hasOwnProperty("language")) {
  306. const languageTags = tags.language;
  307. const translatedIndex = languageTags.indexOf("translated");
  308. translated = (translatedIndex >= 0);
  309. if (translatedIndex !== 0) {
  310. language = toProperCase(languageTags[0]);
  311. }
  312. } else {
  313. language = "Japanese";
  314. translated = false;
  315. }
  316. }
  317.  
  318. function toEhDl(info) { // EH-DL
  319. var d = new Date(info.posted * 1000);
  320. var d2 = new Date();
  321. 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.getUTCFullYear().toString() + '-' + (d.getUTCMonth() + 1).toString().padStart(2, '0') + '-' + d.getUTCDate().toString().padStart(2, '0') + ' ' + d.getUTCHours().toString().padStart(2, '0') + ':' + d.getUTCMinutes().toString().padStart(2, '0') +
  322. "\r\nParent: " + (spl[3] == 'g' ? $('.gdt2 > a').text() : "None" ) + "\r\nVisible: " + (info.expunged ? 'No' : 'Yes') + "\r\nLanguage: " + language + "\r\nFile Size: " + getPrettyFileSize(info.filesize) + "\r\nLength: " + info.filecount + " pages\r\nFavorited: " + (spl[3] == 'g' ? $('#rating_count').text() : "Null" ) + "\r\nRating: " + info.rating + "\r\n\r\nTags:\r\n";
  323. for (const namespace in tags) {
  324. output += `> ${namespace}: `;
  325. for (var i = 0; i < tags[namespace].length; i++) {
  326. output += `${tags[namespace][i]}`;
  327. if (i < tags[namespace].length - 1) output += ", ";
  328. }
  329. output += "\r\n";
  330. }
  331. output += `\r\n\r\n\r\nDownloaded on ${d2.toUTCString()}`;
  332. return output;
  333. }
  334.  
  335. function toEze(info) { // EZE
  336. const date = new Date(toNumberOrDefault(info.posted * 1000, 0));
  337. return {
  338. gallery_info: {
  339. title: toStringOrDefault(info.title, ""),
  340. title_original: toStringOrDefault(info.title_jpn, ""),
  341. uploader: toStringOrDefault(info.uploader, ""),
  342. filecount: toNumberOrDefault(info.filecount, 0),
  343. category: toStringOrDefault(info.category, ""),
  344. tags: tagsToCommonJson(tags),
  345.  
  346. language: toStringOrDefault(language, ""),
  347. translated: !!translated,
  348.  
  349. upload_date: [
  350. date.getUTCFullYear(),
  351. date.getUTCMonth() + 1,
  352. date.getUTCDate(),
  353. date.getUTCHours(),
  354. date.getUTCMinutes(),
  355. date.getUTCSeconds()
  356. ],
  357.  
  358. source: {
  359. site: toStringOrDefault(document.location.host.substr(0, document.location.host.length - 4), ""),
  360. gid: (info.identifier !== null ? toNumberOrDefault(info.gid, 0) : 0),
  361. token: (info.identifier !== null ? toStringOrDefault(info.token, 0) : 0),
  362. parent_gallery: toParentOrDefault(),
  363. newer_version: []
  364. }
  365. }
  366. };
  367. }
  368.  
  369. function toGalleryDl(info) { // Gallery-DL
  370. const date = new Date(toNumberOrDefault(info.posted * 1000, 0));
  371. return {
  372. category: toStringOrDefault(document.location.host.substr(0, document.location.host.length - 4), ""),
  373. cost: null,
  374. count: info.filecount,
  375. date: toStringOrDefault(getTimestampDateString(date), ""),
  376. extension: null,
  377. filename: null,
  378. gallery_id: toNumberOrDefault(info.gid, 0),
  379. gallery_size: toNumberOrDefault(info.filesize, 0),
  380. gallery_token: toStringOrDefault(info.token, 0),
  381. height: null,
  382. image_token: null,
  383. lang: null,
  384. language: toStringOrDefault(language, ""),
  385. num: 1,
  386. parent: toParentOrDefault(),
  387. size: toNumberOrDefault(info.filesize, 0),
  388. subcategory: 'gallery',
  389. tags: info.tags,
  390. title: toStringOrDefault(info.title, ""),
  391. title_jp: toStringOrDefault(info.title_jpn, ""),
  392. uploader: toStringOrDefault(info.uploader, ""),
  393. visible: info.expunged ? 'No' : 'Yes',
  394. width: null
  395. };
  396. }
  397.  
  398. /**
  399. * Convert the star count of a specified element to a double
  400. * @param {Object} el - A specific element within the DOM, or a double
  401. * @param {Boolean} transpose - Whether we're converting background position to double, or double to background position
  402. */
  403. function getStarNumber(el, transpose) {
  404. 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'};
  405. if (!transpose) {
  406. var stars = $(el).find('.ir').css('background-position');
  407. return Object.keys(starCount).find(key => starCount[key] === stars);
  408. } 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
  409. }
  410.  
  411. /** ------ Helper functions cannibalized from the dnsev's script ------ */
  412. function getTagAndNamespace(tag) {
  413. const pattern = /^(?:([^:]*):)?([\w\W]*)$/;
  414. const match = pattern.exec(tag);
  415. return (match !== null) ?
  416. ({ tag: match[2], namespace: match[1] || defaultNamespace }) :
  417. ({ tag: tag, namespace: defaultNamespace });
  418. }
  419.  
  420. function getJsonString(value) {
  421. if (typeof(value) === "string") { return value; }
  422. if (typeof(value) === "undefined" || value === null) { return value; }
  423. return `${value}`;
  424. }
  425.  
  426. function toProperCase(text) {
  427. return text.replace(/(^|\W)(\w)/g, (m0, m1, m2) => `${m1}${m2.toUpperCase()}`);
  428. }
  429.  
  430. function getPrettyFileSize(bytes) {
  431. const ii = fileSizeLabels.length - 1;
  432. let i = 0;
  433. while (i < ii && bytes >= 1024) {
  434. bytes /= 1024;
  435. ++i;
  436. }
  437. return `${bytes.toFixed(i === 0 ? 0 : 2)} ${fileSizeLabels[i]}`;
  438. }
  439.  
  440. function getTimestampDateString(timestamp) {
  441. const date = new Date(timestamp);
  442. const year = date.getUTCFullYear().toString();
  443. const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
  444. const day = date.getUTCDate().toString().padStart(2, "0");
  445. const hour = date.getUTCHours().toString().padStart(2, "0");
  446. const minute = date.getUTCMinutes().toString().padStart(2, "0");
  447. const seconds = date.getUTCSeconds().toString().padStart(2, "0");
  448. return `${year}-${month}-${day} ${hour}:${minute}:${seconds}`;
  449. }
  450.  
  451. function toStringOrDefault(value, defaultValue) {
  452. return typeof(value) === "string" ? value : defaultValue;
  453. }
  454.  
  455. function toNumberOrDefault(value, defaultValue) {
  456. return Number.isNaN(value) ? defaultValue : value;
  457. }
  458.  
  459. function toParentOrDefault() {
  460. if (dead) return null;
  461. if ($('.gdt2 > a').length) return {gid: $('.gdt2 > a').attr('href').split('/')[4], token: $('.gdt2 > a').attr('href').split('/')[5]};
  462. else return null
  463. }
  464.  
  465. function tagsToCommonJson(tags) {
  466. const result = {};
  467. for (const namespace in tags) {
  468. if (!Object.prototype.hasOwnProperty.call(tags, namespace)) { continue; }
  469. const tagList = tags[namespace];
  470. result[namespace] = [...tagList];
  471. }
  472. return result;
  473. }
  474.  
  475. function getShortTitle(title) {
  476. const prefixTags = /^\s*(\(([^\)]*?)\)|\[([^\]]*?)\]|\{([^\}]*?)\})\s*/i;
  477. const suffixTags = /\s*(\(([^\)]*?)(?:\)|$)|\[([^\]]*?)(?:\]|$)|\{([^\}]*?)(?:\}|$))\s*$/i;
  478.  
  479. let m;
  480. while ((m = prefixTags.exec(title))) {
  481. title = title.substr(m.index + m[0].length);
  482. }
  483. while ((m = suffixTags.exec(title))) {
  484. title = title.substr(0, m.index);
  485. }
  486. return title;
  487. }
  488.  
  489. function getGalleryUrl(id) {
  490. const loc = window.location;
  491. return `${loc.protocol}//${loc.host}/g/${id.gid}/${id.token}/`;
  492. }
  493. /** ------------ */
  494.  
  495. $(`<style data-jqstyle="exressurect">
  496. #menu {
  497. font-size: 10pt;
  498. padding: 0.5em 0 0 0;
  499. margin: 3px 5px 5px 0px;
  500. position: relative;
  501. display: inline-block;
  502. width: 100%;
  503. border-top: 1px solid #000;
  504. }
  505. .menuControl {
  506. border: none;
  507. background: rgba(0,0,0,0);
  508. cursor: pointer;
  509. color: #DDD;
  510. }
  511. .menuControl:hover {
  512. color: #EEE;
  513. }
  514. .menuContent {
  515. display: none;
  516. flex-direction: column;
  517. position: absolute;
  518. font-size: 10pt;
  519. border: 1px solid #000;
  520. text-align: left;
  521. overflow: auto;
  522. z-index: 999;
  523. background: rgb(79, 83, 91);
  524. }
  525. .menuContent a {
  526. padding: .25em 1em;
  527. line-height: 1.375em;
  528. text-decoration: none;
  529. }
  530. .menuContent a:hover {
  531. background: rgba(0,0,0,0.4);
  532. }
  533. </style>`).appendTo('head');
  534. })();