la-image-extractor

copy image source in hitomi.la notomi.la e-hentai.org to clipboard

  1. // ==UserScript==
  2. // @name la-image-extractor
  3. // @name:zh-CN la 图片地址复制
  4. // @description copy image source in hitomi.la notomi.la e-hentai.org to clipboard
  5. // @description:zh-CN 复制 hitoma.la notomi.la e-hentai 图片链接到剪贴板
  6. // @version 0.2.5
  7. // @author jferroal
  8. // @license GPL-3.0
  9. // @require https://greasyfork.org/scripts/31793-jmul/code/JMUL.js?version=209567
  10. // @include https://hitomi.la/reader/*
  11. // @include https://nozomi.la/tag/*
  12. // @include https://e-hentai.org/s/*
  13. // @grant GM_xmlhttpRequest
  14. // @run-at document-idle
  15. // @namespace https://greasyfork.org/users/34556-jferroal
  16. // ==/UserScript==
  17.  
  18. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  19. class Button {
  20. constructor(label, eventHandlers = []) {
  21. this.element = document.createElement('div');
  22. this.addCss(Button.DefaultCss);
  23. this.element.innerText = label;
  24. this.listeners = {};
  25. this.listen(eventHandlers);
  26. this.parent = null;
  27. }
  28.  
  29. onClick(handler) {
  30. this.listen({'click': handler});
  31. }
  32.  
  33. listen(handlers) {
  34. for (const event in handlers) {
  35. if (handlers.hasOwnProperty(event)) {
  36. this.element.addEventListener(event, handlers[event]);
  37. }
  38. }
  39. }
  40.  
  41. addCss(css) {
  42. for (const styleName in css) {
  43. if (css.hasOwnProperty(styleName)) {
  44. this.element.style[styleName] = css[styleName];
  45. }
  46. }
  47. }
  48.  
  49. appendTo(element) {
  50. this.parent = element;
  51. element.appendChild(this.element);
  52. }
  53.  
  54. removeFrom() {
  55. this.parent.removeChild(this.element);
  56. }
  57.  
  58. removeListener() {
  59. for (const event in this.listeners) {
  60. if (this.listeners.hasOwnProperty(event)) {
  61. this.element.removeEventListener(event, this.listeners[event]);
  62. }
  63. }
  64. }
  65. }
  66.  
  67. Button.DefaultCss = {
  68. textAlign: 'center',
  69. width: '120px',
  70. lineHeight: '40px',
  71. backgroundColor: 'skyblue',
  72. color: 'white',
  73. cursor: 'pointer',
  74. borderRadius: '8px',
  75. boxShadow: '0px 0px 8px 4px rgba(0, 0, 0, .2)',
  76. position: 'fixed',
  77. right: '80px',
  78. bottom: '80px'
  79. };
  80.  
  81. module.exports = Button;
  82.  
  83. },{}],2:[function(require,module,exports){
  84. const {createFullScreenElement, href, innerText, isEhentai} = require('./utils');
  85.  
  86. let _EhentaiState = {};
  87.  
  88. function constructImage(src) {
  89. const img = document.createElement('img');
  90. img.style.width = '100vw';
  91. img.style.maxWidth = '100vw';
  92. img.setAttribute('src', src);
  93. return img;
  94. }
  95.  
  96. function loadMore() {
  97. _EhentaiState.ImageContainer.removeChild(_EhentaiState.LoadMoreBtn);
  98. const targets = _EhentaiState.ImageSources
  99. .slice(_EhentaiState.ImageAppendedCount, _EhentaiState.ImageAppendedCount + _EhentaiState.ImagePerPage);
  100. let i = 0;
  101. for (const src of targets) {
  102. let timer = setTimeout(() => {
  103. const img = constructImage(src);
  104. document.querySelector(_EhentaiState.ImageContainerSelector).appendChild(img);
  105. clearTimeout(timer);
  106. timer = null;
  107. }, i * _EhentaiState.ImageElementCreationDefer);
  108. _EhentaiState.ImageAppendedCount += 1;
  109. i += 1;
  110. }
  111. if (_EhentaiState.ImageAppendedCount < _EhentaiState.ImageSources.length) {
  112. _EhentaiState.ImageContainer.appendChild(_EhentaiState.LoadMoreBtn);
  113. }
  114. }
  115.  
  116.  
  117.  
  118.  
  119. module.exports = {
  120. initEhentai() {
  121. if (!isEhentai()) return;
  122. const [
  123. MainContainerSelector,
  124. TopPaginationSelector,
  125. ImageContainerSelector,
  126. BottomPaginationSelector,
  127. ImagesSelector,
  128. TitleSelector
  129. ] = ['#i1', '#i2', '#i3', '#i4', '#i3 img', 'title'];
  130. const NextPagePattern = /<div id="i3"><a onclick="return load_image\((\d+), '([\w\d]+)'\)"/gi;
  131. const ImageSourcePattern = /<img id="img" src="(.*)" style=".*?" onerror=".*?" \/>/gi;
  132. const ImageElementCreationDefer = 200;
  133. const ImagePerPage = 20;
  134. const PostId = href().split('/')[5].split('-')[0];
  135. _EhentaiState.ImageContainer = document.querySelector(ImageContainerSelector);
  136. _EhentaiState.LoadMoreBtn = document.createElement('div');
  137. _EhentaiState.LoadMoreBtn.style.width = '100%';
  138. _EhentaiState.LoadMoreBtn.style.lineHeight = '48px';
  139. _EhentaiState.LoadMoreBtn.style.margin = '24px 60px';
  140. _EhentaiState.LoadMoreBtn.style.cursor = 'pointer';
  141. _EhentaiState.LoadMoreBtn.style.backgroundColor = 'lightskyblue';
  142. _EhentaiState.LoadMoreBtn.style.borderRadius = '8px';
  143. _EhentaiState.LoadMoreBtn.innerText = 'LOAD MORE';
  144. _EhentaiState.LoadMoreBtn.addEventListener('click', loadMore);
  145. _EhentaiState.FetchAllRunning = false;
  146. _EhentaiState.ImageAppendedCount = 1;
  147. _EhentaiState.ImageSources = ['first_image_placeholder'];
  148.  
  149. _EhentaiState = Object.assign(_EhentaiState, {
  150. MainContainerSelector,
  151. TopPaginationSelector,
  152. ImageContainerSelector,
  153. BottomPaginationSelector,
  154. ImagesSelector,
  155. TitleSelector,
  156. NextPagePattern,
  157. ImageSourcePattern,
  158. ImageElementCreationDefer,
  159. ImagePerPage,
  160. PostId,
  161. });
  162. },
  163. fetchEhentaiAll() {
  164. if (_EhentaiState.FetchAllRunning) return;
  165. _EhentaiState.FetchAllRunning = true;
  166.  
  167. function getNextImage(page, hash) {
  168. const url = `https://e-hentai.org/s/${hash}/${_EhentaiState.PostId}-${page}`;
  169. const xmlhttp = new XMLHttpRequest();
  170. xmlhttp.open('GET', url);
  171. xmlhttp.onreadystatechange = function () {
  172. if (this.readyState !== 4) return;
  173. if (this.status === 200) {
  174. const [, p, h] = _EhentaiState.NextPagePattern.exec(this.responseText);
  175. _EhentaiState.NextPagePattern.lastIndex = -1;
  176. const hasNext = parseInt(p, 10) !== _EhentaiState.ImageSources.length;
  177. if (!hasNext) {
  178. const loadedElem = createFullScreenElement('ALL IMAGE SOURCES LOADED');
  179. document.body.appendChild(loadedElem);
  180. let timer = setTimeout(() => {
  181. document.body.removeChild(loadedElem);
  182. clearTimeout(timer);
  183. timer = null;
  184. }, 3000);
  185. _EhentaiState.FetchAllRunning = false;
  186. return;
  187. }
  188. const [, imageSource] = _EhentaiState.ImageSourcePattern.exec(this.responseText);
  189. _EhentaiState.ImageSourcePattern.lastIndex = -1;
  190. _EhentaiState.ImageSources.push(imageSource);
  191. if (_EhentaiState.ImageAppendedCount < _EhentaiState.ImagePerPage) {
  192. // load IMAGE PER PAGE COUNT image at first
  193. const img = constructImage(imageSource);
  194. _EhentaiState.ImageContainer.appendChild(img);
  195. _EhentaiState.ImageAppendedCount += 1;
  196. } else {
  197. _EhentaiState.ImageContainer.appendChild(_EhentaiState.LoadMoreBtn);
  198. }
  199. getNextImage(p, h);
  200. }
  201. };
  202. xmlhttp.send();
  203. }
  204.  
  205. const mainContainer = document.querySelector(_EhentaiState.MainContainerSelector);
  206. window.addEventListener('resize', () => {
  207. document.querySelector(_EhentaiState.MainContainerSelector).style.maxWidth = '100vw';
  208. });
  209. mainContainer.style.width = '100vw';
  210. mainContainer.style.maxWidth = '100vw';
  211. document.querySelector(_EhentaiState.TopPaginationSelector).style.display = 'none';
  212. document.querySelector(_EhentaiState.BottomPaginationSelector).style.display = 'none';
  213. const [, page, hash] = _EhentaiState.NextPagePattern.exec(
  214. document.querySelector(_EhentaiState.MainContainerSelector).innerHTML
  215. );
  216. getNextImage(page, hash);
  217. },
  218. extractEhentaiImages() {
  219. const img = document.querySelector(_EhentaiState.ImagesSelector);
  220. const title = innerText(document.querySelector(_EhentaiState.TitleSelector));
  221. return `${title}\n${[img.src, ..._EhentaiState.ImageSources.slice(1)].join('\n')}\n${'= ='.repeat(20)}`;
  222. }
  223. };
  224.  
  225. },{"./utils":6}],3:[function(require,module,exports){
  226. const {href, innerText, isHitomi} = require('./utils');
  227.  
  228. let _HitomiState = {};
  229.  
  230. module.exports = {
  231. initHitomi() {
  232. if (!isHitomi()) return;
  233. _HitomiState.ImgsSrcSelector = '.img-url';
  234. _HitomiState.TitleSelector = 'title';
  235. _HitomiState.Adapost = false;
  236. _HitomiState.NumberOfFrontEnds = 2;
  237.  
  238. },
  239. extractHitomiImages() {
  240. const {ImgsSrcSelector, TitleSelector, Adapost, NumberOfFrontEnds} = _HitomiState;
  241. let images = Array.from(document.querySelectorAll(ImgsSrcSelector));
  242. let title = encodeURIComponent(innerText(document.querySelector(TitleSelector), '- | -').split(' | ')[0]);
  243. const mat = /\/\d*(\d)\.html/.exec(href());
  244. let lv = mat && parseInt(mat[1], 10);
  245. if (!lv || Number.isNaN(lv)) {
  246. lv = '1';
  247. }
  248. const magic = Adapost ? 'a' : String.fromCharCode(((lv === 1 ? 0 : lv) % NumberOfFrontEnds) + 97);
  249. images = images.map(s => s.innerText.replace('//g.hitomi.la', `https://${magic}a.hitomi.la`));
  250. return `${title}\n${images.join('\n')}\n${'= ='.repeat(20)}`;
  251. }
  252. };
  253.  
  254. },{"./utils":6}],4:[function(require,module,exports){
  255. const {isHitomi, isNozomi, isEhentai, copyToClipboard} = require('./utils');
  256. const Button = require('./Button');
  257. const {initHitomi, extractHitomiImages} = require('./hitomi');
  258. const {initNozomi, extractNozomiImages, fetchNozomiAll} = require('./nozomi');
  259. const {initEhentai, extractEhentaiImages, fetchEhentaiAll} = require('./ehentai');
  260.  
  261. (function () {
  262. initHitomi();
  263. initNozomi();
  264. initEhentai();
  265. // create button to click
  266. if (isNozomi() || isEhentai()) {
  267. const btn = new Button('Fetch All');
  268. btn.addCss({bottom: '160px', zIndex: '999'});
  269. btn.appendTo(document.body);
  270. btn.onClick(() => {
  271. if (isNozomi()) {
  272. fetchNozomiAll();
  273. }
  274. if (isEhentai()) {
  275. fetchEhentaiAll();
  276. }
  277. });
  278. }
  279. const btn = new Button('Copy Sources');
  280. btn.addCss({zIndex: '999'});
  281. btn.onClick(() => {
  282. // prepare str2Paste
  283. let str2paste = '';
  284. if (isHitomi()) {
  285. str2paste = extractHitomiImages();
  286. } else if (isNozomi()) {
  287. str2paste = extractNozomiImages();
  288. } else {
  289. str2paste = extractEhentaiImages();
  290. }
  291. copyToClipboard(str2paste);
  292. });
  293. btn.appendTo(document.body);
  294. })();
  295.  
  296. },{"./Button":1,"./ehentai":2,"./hitomi":3,"./nozomi":5,"./utils":6}],5:[function(require,module,exports){
  297. const {innerText, createFullScreenElement} = require('./utils');
  298.  
  299. // TODO: refactor
  300.  
  301. const [
  302. ImgSrcSelector,
  303. TitleSelector,
  304. ContentSelector,
  305. ThumbnailDivsSelector
  306. ] = ['.tag-list-img', 'h1', '.content', '#thumbnail-divs'];
  307.  
  308. function JSPack() {
  309. // Module-level (private) variables
  310. var el, bBE = false, m = this;
  311.  
  312. // Raw byte arrays
  313. m._DeArray = function (a, p, l) {
  314. return [a.slice(p, p + l)];
  315. };
  316. m._EnArray = function (a, p, l, v) {
  317. for (var i = 0; i < l; a[p + i] = v[i] ? v[i] : 0, i++) ;
  318. };
  319.  
  320. // ASCII characters
  321. m._DeChar = function (a, p) {
  322. return String.fromCharCode(a[p]);
  323. };
  324. m._EnChar = function (a, p, v) {
  325. a[p] = v.charCodeAt(0);
  326. };
  327.  
  328. // Little-endian (un)signed N-byte integers
  329. m._DeInt = function (a, p) {
  330. var lsb = bBE ? (el.len - 1) : 0, nsb = bBE ? -1 : 1, stop = lsb + nsb * el.len, rv, i, f;
  331. for (rv = 0, i = lsb, f = 1; i != stop; rv += (a[p + i] * f), i += nsb, f *= 256) ;
  332. if (el.bSigned && (rv & Math.pow(2, el.len * 8 - 1))) { rv -= Math.pow(2, el.len * 8); }
  333. return rv;
  334. };
  335. m._EnInt = function (a, p, v) {
  336. var lsb = bBE ? (el.len - 1) : 0, nsb = bBE ? -1 : 1, stop = lsb + nsb * el.len, i;
  337. v = (v < el.min) ? el.min : (v > el.max) ? el.max : v;
  338. for (i = lsb; i != stop; a[p + i] = v & 0xff, i += nsb, v >>= 8) ;
  339. };
  340.  
  341. // ASCII character strings
  342. m._DeString = function (a, p, l) {
  343. for (var rv = new Array(l), i = 0; i < l; rv[i] = String.fromCharCode(a[p + i]), i++) ;
  344. return rv.join('');
  345. };
  346. m._EnString = function (a, p, l, v) {
  347. for (var t, i = 0; i < l; a[p + i] = (t = v.charCodeAt(i)) ? t : 0, i++) ;
  348. };
  349.  
  350. // Little-endian N-bit IEEE 754 floating point
  351. m._De754 = function (a, p) {
  352. var s, e, m, i, d, nBits, mLen, eLen, eBias, eMax;
  353. mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1;
  354.  
  355. i = bBE ? 0 : (el.len - 1);
  356. d = bBE ? 1 : -1;
  357. s = a[p + i];
  358. i += d;
  359. nBits = -7;
  360. for (e = s & ((1 << (-nBits)) - 1), s >>= (-nBits), nBits += eLen; nBits > 0; e = e * 256 + a[p + i], i += d, nBits -= 8) ;
  361. for (m = e & ((1 << (-nBits)) - 1), e >>= (-nBits), nBits += mLen; nBits > 0; m = m * 256 + a[p + i], i += d, nBits -= 8) ;
  362.  
  363. switch (e) {
  364. case 0:
  365. // Zero, or denormalized number
  366. e = 1 - eBias;
  367. break;
  368. case eMax:
  369. // NaN, or +/-Infinity
  370. return m ? NaN : ((s ? -1 : 1) * Infinity);
  371. default:
  372. // Normalized number
  373. m = m + Math.pow(2, mLen);
  374. e = e - eBias;
  375. break;
  376. }
  377. return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
  378. };
  379. m._En754 = function (a, p, v) {
  380. var s, e, m, i, d, c, mLen, eLen, eBias, eMax;
  381. mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1;
  382.  
  383. s = v < 0 ? 1 : 0;
  384. v = Math.abs(v);
  385. if (isNaN(v) || (v === Infinity)) {
  386. m = isNaN(v) ? 1 : 0;
  387. e = eMax;
  388. }
  389. else {
  390. e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value
  391. if (v * (c = Math.pow(2, -e)) < 1) {
  392. e--;
  393. c *= 2;
  394. } // Math.log() isn't 100% reliable
  395.  
  396. // Round by adding 1/2 the significand's LSD
  397. if (e + eBias >= 1) { v += el.rt / c; } // Normalized: mLen significand digits
  398. else { v += el.rt * Math.pow(2, 1 - eBias); } // Denormalized: <= mLen significand digits
  399. if (v * c >= 2) {
  400. e++;
  401. c /= 2;
  402. } // Rounding can increment the exponent
  403.  
  404. if (e + eBias >= eMax) {
  405. // Overflow
  406. m = 0;
  407. e = eMax;
  408. }
  409. else if (e + eBias >= 1) {
  410. // Normalized - term order matters, as Math.pow(2, 52-e) and v*Math.pow(2, 52) can overflow
  411. m = (v * c - 1) * Math.pow(2, mLen);
  412. e = e + eBias;
  413. }
  414. else {
  415. // Denormalized - also catches the '0' case, somewhat by chance
  416. m = v * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
  417. e = 0;
  418. }
  419. }
  420.  
  421. for (i = bBE ? (el.len - 1) : 0, d = bBE ? -1 : 1; mLen >= 8; a[p + i] = m & 0xff, i += d, m /= 256, mLen -= 8) ;
  422. for (e = (e << mLen) | m, eLen += mLen; eLen > 0; a[p + i] = e & 0xff, i += d, e /= 256, eLen -= 8) ;
  423. a[p + i - d] |= s * 128;
  424. };
  425.  
  426.  
  427. // Class data
  428. m._sPattern = '(\\d+)?([AxcbBhHsfdiIlL])';
  429. m._lenLut = {
  430. 'A': 1,
  431. 'x': 1,
  432. 'c': 1,
  433. 'b': 1,
  434. 'B': 1,
  435. 'h': 2,
  436. 'H': 2,
  437. 's': 1,
  438. 'f': 4,
  439. 'd': 8,
  440. 'i': 4,
  441. 'I': 4,
  442. 'l': 4,
  443. 'L': 4
  444. };
  445. m._elLut = {
  446. 'A': {en: m._EnArray, de: m._DeArray},
  447. 's': {en: m._EnString, de: m._DeString},
  448. 'c': {en: m._EnChar, de: m._DeChar},
  449. 'b': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: true, min: -Math.pow(2, 7), max: Math.pow(2, 7) - 1},
  450. 'B': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: false, min: 0, max: Math.pow(2, 8) - 1},
  451. 'h': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: true, min: -Math.pow(2, 15), max: Math.pow(2, 15) - 1},
  452. 'H': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: false, min: 0, max: Math.pow(2, 16) - 1},
  453. 'i': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1},
  454. 'I': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1},
  455. 'l': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1},
  456. 'L': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1},
  457. 'f': {en: m._En754, de: m._De754, len: 4, mLen: 23, rt: Math.pow(2, -24) - Math.pow(2, -77)},
  458. 'd': {en: m._En754, de: m._De754, len: 8, mLen: 52, rt: 0}
  459. };
  460.  
  461. // Unpack a series of n elements of size s from array a at offset p with fxn
  462. m._UnpackSeries = function (n, s, a, p) {
  463. for (var fxn = el.de, rv = [], i = 0; i < n; rv.push(fxn(a, p + i * s)), i++) ;
  464. return rv;
  465. };
  466.  
  467. // Pack a series of n elements of size s from array v at offset i to array a at offset p with fxn
  468. m._PackSeries = function (n, s, a, p, v, i) {
  469. for (var fxn = el.en, o = 0; o < n; fxn(a, p + o * s, v[i + o]), o++) ;
  470. };
  471.  
  472. // Unpack the octet array a, beginning at offset p, according to the fmt string
  473. m.Unpack = function (fmt, a, p) {
  474. // Set the private bBE flag based on the format string - assume big-endianness
  475. bBE = (fmt.charAt(0) != '<');
  476.  
  477. p = p ? p : 0;
  478. var re = new RegExp(this._sPattern, 'g'), m, n, s, rv = [];
  479. while (m = re.exec(fmt)) {
  480. n = ((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1]);
  481. s = this._lenLut[m[2]];
  482. if ((p + n * s) > a.length) {
  483. return undefined;
  484. }
  485. switch (m[2]) {
  486. case 'A':
  487. case 's':
  488. rv.push(this._elLut[m[2]].de(a, p, n));
  489. break;
  490. case 'c':
  491. case 'b':
  492. case 'B':
  493. case 'h':
  494. case 'H':
  495. case 'i':
  496. case 'I':
  497. case 'l':
  498. case 'L':
  499. case 'f':
  500. case 'd':
  501. el = this._elLut[m[2]];
  502. rv.push(this._UnpackSeries(n, s, a, p));
  503. break;
  504. }
  505. p += n * s;
  506. }
  507. return Array.prototype.concat.apply([], rv);
  508. };
  509.  
  510. // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string
  511. m.PackTo = function (fmt, a, p, values) {
  512. // Set the private bBE flag based on the format string - assume big-endianness
  513. bBE = (fmt.charAt(0) !== '<');
  514.  
  515. var re = new RegExp(this._sPattern, 'g'), m, n, s, i = 0, j;
  516. while (m = re.exec(fmt)) {
  517. n = ((m[1] === undefined) || (m[1] === '')) ? 1 : parseInt(m[1]);
  518. s = this._lenLut[m[2]];
  519. if ((p + n * s) > a.length) {
  520. return false;
  521. }
  522. switch (m[2]) {
  523. case 'A':
  524. case 's':
  525. if ((i + 1) > values.length) { return false; }
  526. this._elLut[m[2]].en(a, p, n, values[i]);
  527. i += 1;
  528. break;
  529. case 'c':
  530. case 'b':
  531. case 'B':
  532. case 'h':
  533. case 'H':
  534. case 'i':
  535. case 'I':
  536. case 'l':
  537. case 'L':
  538. case 'f':
  539. case 'd':
  540. el = this._elLut[m[2]];
  541. if ((i + n) > values.length) { return false; }
  542. this._PackSeries(n, s, a, p, values, i);
  543. i += n;
  544. break;
  545. case 'x':
  546. for (j = 0; j < n; j++) { a[p + j] = 0; }
  547. break;
  548. }
  549. p += n * s;
  550. }
  551. return a;
  552. };
  553.  
  554. // Pack the supplied values into a new octet array, according to the fmt string
  555. m.Pack = function (fmt, values) {
  556. return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values);
  557. };
  558.  
  559. // Determine the number of bytes represented by the format string
  560. m.CalcLength = function (fmt) {
  561. var re = new RegExp(this._sPattern, 'g'), m, sum = 0;
  562. while (m = re.exec(fmt)) {
  563. sum += (((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1])) * this._lenLut[m[2]];
  564. }
  565. return sum;
  566. };
  567. }
  568.  
  569.  
  570. var nozomiextension = '.nozomi';
  571. var nozomidir = 'nozomi';
  572. var domain = 'n.nozomi.la';
  573. var postdir = 'post';
  574.  
  575. var results_array = {};
  576. var outstanding_requests = {};
  577. var number_of_outstanding_requests = 0;
  578. var nozomi, total_items;
  579. var tag, tag_for_hash = '', area, page_number;
  580. var nozomi_address;
  581.  
  582.  
  583. function dgebi(id) { return document.getElementById(id); }
  584.  
  585. function path_from_postid(postid) {
  586. if (postid.length < 3) return postid;
  587.  
  588. return postid.replace(/^(.*(..)(.))$/, '$3/$2/$1');
  589. }
  590.  
  591. function resize_thumbnails() {
  592. var c = document.getElementsByClassName('content')[0];
  593. if (!c) {
  594. return;
  595. }
  596.  
  597. var s = c.clientWidth;
  598.  
  599. var n = Math.ceil(6.0 * s / 1000.0);
  600. var w = ((s - (10.0 * n + 10.0) - 0.5) / n) - 2.0;
  601.  
  602. var divs = document.getElementsByClassName('thumbnail-div');
  603. for (var i = 0; i < divs.length; i++) {
  604. var div = divs[i];
  605. div.style.width = (w + 'px');
  606. div.style.height = (w + 'px');
  607. }
  608. }
  609.  
  610. function check_dates() {
  611. var format = 'lll';
  612.  
  613. var localize_title = function (el) {
  614. if (!el) {
  615. return;
  616. }
  617.  
  618. var str = el.title;
  619. if (str) {
  620. var m = moment(str);
  621. el.title = m.format(format);
  622. }
  623. };
  624. //post view
  625. localize_title(document.querySelectorAll('.post img')[0]);
  626. }
  627.  
  628. function urlencode(str) {
  629. return str.replace(/[;\/?:@=&]/g, function (c) {
  630. return '%' + c.charCodeAt(0).toString(16);
  631. return '%' + c.charCodeAt(0).toString(16);
  632. });
  633. }
  634.  
  635. function fetch_nozomi(totalImageCount) {
  636. var filepath = decodeURIComponent(document.location.href.replace(/.*nozomi\.la\//, ''));
  637. if (!filepath) {
  638. tag = 'index';
  639. page_number = 1;
  640. } else {
  641. var elements = filepath.replace(/\.html$/, '').split('-');
  642. if (elements.length < 2) return;
  643. while (elements.length > 2) {
  644. elements[1] = elements[0] + '-' + elements[1];
  645. elements.shift();
  646. }
  647.  
  648. tag = elements[0];
  649. if (tag.match(/\//)) {
  650. var area_elements = tag.split(/\//);
  651. if (area_elements.length !== 2) return;
  652.  
  653. area = area_elements[0];
  654. if (!area || area.match(/[^A-Za-z0-9_]/)) return;
  655.  
  656. tag = area_elements[1];
  657. }
  658. if (!tag || tag.match(/\//)) return;
  659.  
  660. if (tag === 'index-Popular') {
  661. tag_for_hash = '-Popular';
  662. } else if (area) {
  663. tag_for_hash = tag;
  664. }
  665.  
  666. page_number = parseInt(elements[1]);
  667. if (!page_number || page_number < 1) return;
  668. }
  669. nozomi_address = '//' + [domain, urlencode(tag)].join('/') + nozomiextension;
  670. if (area) {
  671. nozomi_address = '//' + [domain, nozomidir, urlencode(tag)].join('/') + nozomiextension;
  672. }
  673.  
  674. var start_byte = 0;
  675. var end_byte = totalImageCount * 4 - 1;
  676. var xhr = new XMLHttpRequest();
  677. const loadingElement = createFullScreenElement();
  678. document.body.appendChild(loadingElement);
  679. xhr.open('GET', nozomi_address);
  680. xhr.responseType = 'arraybuffer';
  681. xhr.setRequestHeader('Range', 'bytes=' + start_byte.toString() + '-' + end_byte.toString());
  682. xhr.onreadystatechange = function (oEvent) {
  683. if (xhr.readyState === 4) {
  684. if (xhr.status === 200 || xhr.status === 206) {
  685. var arrayBuffer = xhr.response; // Note: not oReq.responseText
  686. if (arrayBuffer) {
  687. var arr = new Uint8Array(arrayBuffer); //e.g. [0x00, 0x5D, 0x39, 0x72, 0x00, 0x5D, 0x39, 0x82, ...]
  688. var jspack = new JSPack();
  689. var total = arr.length / 4; //32-bit unsigned integers
  690. nozomi = jspack.Unpack(total + 'I', arr);
  691. total_items = parseInt(xhr.getResponseHeader('Content-Range').replace(/^[Bb]ytes \d+-\d+\//, '')) / 4;
  692. get_jsons();
  693. document.body.removeChild(loadingElement);
  694. }
  695. }
  696. }
  697. };
  698. xhr.send();
  699. }
  700.  
  701. function get_jsons() {
  702. var datas = [];
  703. for (var i in nozomi) {
  704. var postid = nozomi[i];
  705. if (postid in results_array) {
  706. datas.push(results_array[postid]);
  707. continue;
  708. }
  709. if (!outstanding_requests[postid]) {
  710. outstanding_requests[postid] = 1;
  711. ++number_of_outstanding_requests;
  712. get_json(postid); //calling a function is REQUIRED to give postid its own scope
  713. }
  714. }
  715. if (number_of_outstanding_requests) return;
  716.  
  717. results_to_page(datas);
  718. }
  719.  
  720. function get_json(postid) {
  721. var url = '//j.nozomi.la/' + postdir + '/' + path_from_postid(postid.toString()) + '.json';
  722.  
  723. var xmlhttp = new XMLHttpRequest();
  724. xmlhttp.open('GET', url);
  725. xmlhttp.onreadystatechange = function () {
  726. if (this.readyState === 4) {
  727. if (this.status === 200) {
  728. results_array[postid] = JSON.parse(this.responseText);
  729. } else {
  730. results_array[postid] = '';
  731. }
  732. delete outstanding_requests[postid];
  733. --number_of_outstanding_requests;
  734. get_jsons();
  735. }
  736. };
  737. xmlhttp.send();
  738. }
  739.  
  740. function results_to_page(datas) {
  741. var tc = Object.create(null);
  742. var to = {};
  743.  
  744. dgebi('thumbnail-divs').innerHTML = '';
  745.  
  746. var ul = document.getElementsByClassName('title')[0].nextElementSibling;
  747. ul.innerHTML = '';
  748.  
  749. var p = function (l) {
  750. var areas = ['general', 'artist', 'copyright', 'character'];
  751. for (var a in areas) {
  752. var area = areas[a];
  753. for (var i in l[area]) {
  754. var tag = l[area][i];
  755.  
  756. if (tag.tag in tc === false) {
  757. tc[tag.tag] = 1;
  758. } else {
  759. tc[tag.tag]++;
  760. }
  761.  
  762. to[tag.tag] = tag;
  763. }
  764. }
  765. };
  766.  
  767. for (var d in datas) {
  768. var data = datas[d];
  769. if (!data) continue;
  770.  
  771. var div = document.createElement('div');
  772. div.classList.add('thumbnail-div');
  773.  
  774. var a = document.createElement('a');
  775. a.href = '/post/' + data.postid + '.html#' + tag_for_hash;
  776.  
  777. var img = document.createElement('img');
  778. img.classList.add('tag-list-img');
  779. img.crossOrigin = 'Anonymous';
  780. img.src = data.imageurl.replace(/\/\/i\.nozomi\.la\//, '//tn.nozomi.la/') + '.jpg';
  781. img.title = data.date;
  782.  
  783. a.appendChild(img);
  784. div.appendChild(a);
  785. dgebi('thumbnail-divs').appendChild(div);
  786.  
  787. p(data);
  788. }
  789.  
  790.  
  791. resize_thumbnails();
  792. setTimeout(resize_thumbnails, 10);
  793.  
  794. check_dates();
  795. }
  796.  
  797.  
  798. module.exports = {
  799. initNozomi() {
  800. return;
  801. },
  802. fetchNozomiAll() {
  803. const totalPage = parseInt(innerText(document.querySelector('.page-container li:last-child'), 1), 10);
  804. const totalImageCount = totalPage * 64;
  805. // prettify page
  806. const elementsToRemove = [
  807. document.querySelector('.page-container'),
  808. document.querySelector('.navbar'),
  809. document.querySelector('.sidebar')
  810. ];
  811. for (const elem of elementsToRemove) {
  812. elem.style.display = 'none';
  813. }
  814. const mainContent = document.querySelector('.content');
  815. mainContent.style.margin = '0 auto';
  816. const thumbnailsContainer = document.getElementById('thumbnail-divs');
  817. for (const e of Array.from(thumbnailsContainer.getElementsByClassName('thumbnail-div'))) {
  818. thumbnailsContainer.removeChild(e);
  819. }
  820. fetch_nozomi(totalImageCount);
  821. },
  822. extractNozomiImages() {
  823. let images = Array.from(document.querySelectorAll(ImgSrcSelector));
  824. let title = document.querySelector(TitleSelector).innerText;
  825. images = images.map(s => s.src.replace('//tn', '//i').split('.').slice(0, 4).join('.'));
  826. return `${title}\n${images.join('\n')}\n${'= ='.repeat(20)}`;
  827. }
  828. };
  829.  
  830. },{"./utils":6}],6:[function(require,module,exports){
  831. module.exports = {
  832. isHitomi() {
  833. return window && window.location && /hitomi/gi.test(window.location.href);
  834. },
  835. isNozomi() {
  836. return window && window.location && /nozomi.la\/tag/gi.test(window.location.href);
  837. },
  838. isEhentai() {
  839. return window && window.location && /e-hentai.org\/s/gi.test(window.location.href);
  840. },
  841. copyToClipboard(content) {
  842. const el = document.createElement('textarea');
  843. el.value = content;
  844. document.body.appendChild(el);
  845. el.select();
  846. document.execCommand('copy');
  847. document.body.removeChild(el);
  848. },
  849. href(d = '') {
  850. return (window && window.location && window.location.href) || d;
  851. },
  852. innerText(elem, d = '') {
  853. return (elem && elem.innerText) || d;
  854. },
  855. matchAll(content, pat) {
  856. let matches = [];
  857. let match = pat.exec(content);
  858. while (match) {
  859. matches.push(match[1]);
  860. match = pat.exec(content);
  861. }
  862. pat.lastIndex = -1;
  863. return matches;
  864. },
  865. createFullScreenElement(text) {
  866. const elem = document.createElement('div');
  867. elem.style.position = 'fixed';
  868. elem.style.top = '0';
  869. elem.style.right = '0';
  870. elem.style.bottom = '0';
  871. elem.style.left = '0';
  872. elem.style.display = 'flex';
  873. elem.style.backgroundColor = 'rgba(0, 0, 0, 0.56)';
  874. elem.style.justifyContent = 'center';
  875. elem.style.alignItems = 'center';
  876. elem.style.fontSize = '72px';
  877. elem.style.zIndex = '999';
  878. elem.innerText = text;
  879. return elem;
  880. }
  881. };
  882.  
  883. },{}]},{},[4]);