Image Host Helper

Directly upload local / rehost remote images or galleries to whatever supported image host by dropping/pasting them to target field

当前为 2020-09-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Image Host Helper
  3. // @namespace https://greasyfork.org/users/321857-anakunda
  4. // @version 1.081
  5. // @description Directly upload local / rehost remote images or galleries to whatever supported image host by dropping/pasting them to target field
  6. // @icon data:image/png;base64,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
  7. // @author Anakunda
  8. // @copyright 2020, Anakunda (https://greasyfork.org/cs/users/321857-anakunda)
  9. // @license GPL-3.0-or-later
  10. // @match https://passthepopcorn.me/*
  11. // @match https://redacted.ch/*
  12. // @match https://orpheus.network/*
  13. // @match https://broadcasthe.net/*
  14. // @match https://notwhat.cd/*
  15. // @match https://dicmusic.club/*
  16. // @match https://*/torrents.php?id=*
  17. // @match https://*/artist.php?id=*
  18. // @match https://*/artist.php?action=edit&artistid=*
  19. // @match https://*/reportsv2.php?action=report&id=*
  20. // @match https://*/forums.php?action=new*
  21. // @match https://*/forums.php?*action=viewthread*
  22. // @match https://*/requests.php?action=view*
  23. // @match https://*/collages.php?id=*
  24. // @match https://*/collages.php?action=edit&collageid=*
  25. // @match https://*/collages.php?action=comments&collageid=*
  26. // @match https://*/collages.php?action=new
  27. // @match http*://tracker.czech-server.com/*
  28. // @connect *
  29. // @grant GM_xmlhttpRequest
  30. // @grant GM_getValue
  31. // @grant GM_setValue
  32. // @grant GM_deleteValue
  33. // @require https://greasyfork.org/scripts/408084-xhrlib/code/xhrLib.js
  34. // @require https://greasyfork.org/scripts/404516-progressbars/code/progressBars.js
  35. // @require https://greasyfork.org/scripts/401726-imagehostuploader/code/imageHostUploader.js
  36. // ==/UserScript==
  37.  
  38. 'use strict';
  39.  
  40. if (document.getElementById('upload assistant') != null) return; // don't clash with Upload Assistant
  41.  
  42. Array.prototype.flatten = function() {
  43. return this.reduce(function(flat, toFlatten) {
  44. return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten);
  45. }, []);
  46. };
  47.  
  48. var cheveretoCustomHosts = GM_getValue('chevereto_custom_hosts');
  49. if (cheveretoCustomHosts !== undefined) try {
  50. JSON.parse(cheveretoCustomHosts).forEach(function(siteDef) {
  51. if (!siteDef.host_name || !siteDef.alias) {
  52. console.warn('Incomplete Chevereto custom site definition:', siteDef);
  53. return;
  54. }
  55. imageHostHandlers[siteDef.alias.replace(nonWordStripper, '').toLowerCase()] = new Chevereto(
  56. siteDef.host_name,
  57. siteDef.alias,
  58. siteDef.types,
  59. siteDef.size_limit, {
  60. sizeLimitAnonymous: siteDef.size_limit_anonymous,
  61. configPrefix: siteDef.config_prefix,
  62. apiEndpoint: siteDef.api_endpoint,
  63. apiFieldName: siteDef.api_field_name,
  64. apiResultKey: siteDef.api_result_key,
  65. jsonEndpoint: siteDef.json_endpoint,
  66. });
  67. });
  68. } catch (e) { console.warn(e) } else GM_setValue('chevereto_custom_hosts', '[]');
  69. console.log('Image host handlers:', imageHostHandlers);
  70.  
  71. ['upload_hosts', 'rehost_hosts'].forEach(propName => { if (!GM_getValue(propName)) GM_setValue(propName, [
  72. 'PTPimg', 'ImgBB', 'PixHost', 'ImgBox', 'Jerking', 'Abload', 'VgyMe', 'LightShot', 'Slowpoke', 'FunkyIMG',
  73. 'ImgURL', 'Gifyu', 'PostImage', 'Radikal', 'GeekPic', 'Z4A', 'PicaBox', 'PimpAndHost', 'Catbox', 'ImageBan',
  74. 'SVGshare', 'ImageVenue', 'Imgur', 'FastPic',
  75. ].join(', ')) });
  76. [
  77. ['passthepopcorn.me', [
  78. 'PTPimg', 'PixHost', 'ImgBB', 'Jerking', 'Gifyu', 'Slowpoke', 'ImgBox', 'Abload', 'VgyMe', 'LightShot',
  79. 'FunkyIMG', 'ImgURL', 'Radikal', 'GeekPic', 'Z4A', 'PicaBox', 'PimpAndHost', 'Catbox','ImageBan',
  80. 'ImageVenue',
  81. ]],
  82. ['notwhat.cd', ['NWCD']],
  83. ].forEach(hostDefaults => { if (!GM_getValue(hostDefaults[0])) GM_setValue(hostDefaults[0], hostDefaults[1].join(', ')) });
  84.  
  85. var imageHosts = new ImageHostManager(logFail,
  86. GM_getValue(document.domain) || GM_getValue('upload_hosts'),
  87. GM_getValue(document.domain) || GM_getValue('rehost_hosts'));
  88.  
  89. imageHostUploaderInit(inputDataHandler, textAreaDropHandler, textAreaPasteHandler, imageUrlResolver);
  90.  
  91. // Set UI handlers
  92. ['image', 'cover', 'avatar'].forEach(function(attribute) {
  93. document.querySelectorAll('input[id*="' + attribute + '"], input[name*="' + attribute + '"]').forEach(setInputHandlers);
  94. });
  95. if (document.domain == 'tracker.czech-server.com') document.querySelectorAll('input[name="urlobr"]').forEach(setInputHandlers);
  96. Array.from(document.getElementsByTagName('textarea')).forEach(setTextAreahandlers);
  97. if (document.URL.includes('/torrents.php?id=')) {
  98. let a = document.querySelector('span.additional_add_artists > a');
  99. if (a != null) a.addEventListener('click', function() {
  100. document.querySelectorAll('input[name="image[]"]').forEach(setInputHandlers);
  101. });
  102. }
  103. if (document.URL.includes('/reportsv2.php')) {
  104. setReportHandlers();
  105. var reportTypeSelect = document.querySelector('select#type');
  106. if (reportTypeSelect != null) reportTypeSelect.addEventListener('change', setReportHandlers);
  107.  
  108. function setReportHandlers(evt) {
  109. setTimeout(function() {
  110. document.querySelectorAll('input[id*="image"]').forEach(setInputHandlers);
  111. document.querySelectorAll('textarea').forEach(setTextAreahandlers);
  112. }, 2000);
  113. }
  114. }
  115.  
  116. // site-specific extensions
  117. switch (document.domain) {
  118. case 'passthepopcorn.me':
  119. // Auto-fill missing/invalid images from IMDB
  120. if (/\/artist\.php\?action=edit&artistid=(\d+)\b/i.test(document.URL)) {
  121. let artistId = parseInt(RegExp.$1), input = document.querySelector('input[name="image"]');
  122. if (input != null) verifyImageUrl(input.value).catch(() => localFetch('/artist.php?id=' + artistId).then(function(dom) {
  123. let imdb = dom.querySelector('div#artistinfo > div.panel__body > ul.list > li > a');
  124. if (imdb != null) imageUrlResolver(imdb.href).then(setCover.bind(input), reason => { logFail('No IMDB photo of this artist') });
  125. }));
  126. } else if (/\/torrents\.php??action=editgroup&groupid=(\d+)\b/i.test(document.URL)) {
  127. let groupId = parseInt(RegExp.$1), input = document.querySelector('input[name="image"]');
  128. if (input != null) verifyImageUrl(input.value).catch(() => localFetch('/torrents.php?id=' + groupId).then(function(dom) {
  129. let imdb = dom.querySelector('a#imdb-title-link');
  130. if (imdb != null) imageUrlResolver(imdb.href).then(setCover.bind(input), reason => { logFail('No IMDB poster for this movie') });
  131. }));
  132. }
  133. // HJ Toolkit patch
  134. setTimeout(function() {
  135. if (document.querySelector('div.HJ-toolkit-badge') != null) {
  136. let hjtkTimer = setInterval(function() {
  137. document.querySelectorAll('textarea[id^="HJMA"], textarea.form-control[name="screen"], textarea.form-control[name="comp"]')
  138. .forEach(setTextAreahandlers);
  139. }, 1000);
  140. }
  141. }, 1000);
  142. break;
  143. }
  144.  
  145. // Old versions adjustment
  146. ['image_size_warning', 'image_size_reduce_threshold'].forEach(itemProp => {
  147. var val = GM_getValue('itemProp');
  148. if (val < 8) GM_setValue('itemProp', val * 2**10);
  149. });
  150.  
  151. function coverPreview(input, imgUrl, size) {
  152. var child = document.getElementById('cover-preview');
  153. if (child == null) {
  154. return; // not implemented so far
  155. if (!(input instanceof HTMLElement) || input.parentNode.previousElementSibling == null) return;
  156. var elem = document.createElement('div');
  157. elem.style = 'padding-top: 10px; float: right; width: 90%;';
  158. child = document.createElement('img');
  159. child.id = 'cover-preview';
  160. elem.append(child);
  161. var div = document.createElement('div');
  162. div.id = 'cover-size';
  163. elem.append(div);
  164. input.parentNode.previousElementSibling.append(document.createElement('br'));
  165. input.parentNode.previousElementSibling.append(elem);
  166. }
  167. if ((div = div || document.getElementById('cover-size')) == null) return;
  168. if (urlParser.test(imgUrl)) {
  169. child.onload = function(evt) {
  170. this.onload = null;
  171. if (!this.naturalWidth || !this.naturalHeight) return; // invalid image
  172. (size > 0 ? Promise.resolve(size) : getRemoteFileSize(imgUrl)).then(function(size) {
  173. div.textContent = this.naturalWidth + '×' + this.naturalHeight + ' (' + formattedSize(size) + ')';
  174. }.bind(this)).catch(reason => { div.textContent = this.naturalWidth + '×' + this.naturalHeight });
  175. };
  176. child.onerror = function(evt) {
  177. this.onerror = null;
  178. div.textContent = this.src = '';
  179. console.warn('Image source cannot be updated:', evt, imgUrl);
  180. };
  181. child.src = imgUrl;
  182. } else div.textContent = child.src = '';
  183. }
  184.  
  185. function setCover(url) {
  186. return verifyImageUrl(url).then(imageUrl => {
  187. this.value = imageUrl;
  188. coverPreview(this, imageUrl);
  189. checkImageSize(imageUrl, image).then(imageUrl => {
  190. this.disabled = true;
  191. return imageHosts.rehostImages([imageUrl]).then(singleImageGetter).then(imageUrl => {
  192. if (imageUrl == null) throw 'invalid image';
  193. this.value = imageUrl;
  194. }).catch(reason => {
  195. this.value = imageUrl;
  196. logFail(reason + ' (not rehosted)');
  197. }).then(() => {
  198. this.disabled = false;
  199. return imageUrl;
  200. });
  201. });
  202. return imageUrl;
  203. });
  204. }
  205.  
  206. function inputDataHandler(evt, data) {
  207. if (!data) return true;
  208. if (data.files.length > 0) {
  209. if (data.files[0].type && !data.files[0].type.startsWith('image/')) return true;
  210. evt.target.disabled = true;
  211. if (evt.target.hTimer) {
  212. clearTimeout(evt.target.hTimer);
  213. delete evt.target.hTimer;
  214. }
  215. evt.target.style.color = 'white';
  216. evt.target.style.backgroundColor = 'darkred';
  217. let size = data.files[0].size, lastPct, lastUpdate, current;
  218. imageHosts.uploadImages([data.files[0]], function(worker, param = null) {
  219. if (param && typeof param == 'object') {
  220. if (param.readyState > 1 || current != undefined && worker !== current) return;
  221. if (Date.now() - 100 < lastUpdate) return;
  222. let pct = Math.floor(Math.min(param.done * 100 / param.total, 100));
  223. if (pct <= lastPct) return;
  224. lastPct = pct;
  225. evt.target.value = 'Uploading... [' + pct + '%]';
  226. lastUpdate = Date.now();
  227. } else if (typeof param != 'number') {
  228. lastPct = lastUpdate = undefined;
  229. current = worker;
  230. evt.target.value = 'Uploading...';
  231. }
  232. }).then(singleImageGetter).then(function(imgUrl) {
  233. evt.target.value = imgUrl;
  234. evt.target.style.backgroundColor = '#008000';
  235. evt.target.hTimer = setTimeout(function() {
  236. evt.target.style.backgroundColor = null;
  237. evt.target.style.color = null;
  238. delete evt.target.hTimer;
  239. }, 10000);
  240. coverPreview(evt.target, imgUrl, size);
  241. }, function(reason) {
  242. imageClear(evt);
  243. evt.target.style.backgroundColor = null;
  244. evt.target.style.color = null;
  245. Promise.resolve(reason).then(msg => { alert(msg) });
  246. }).then(() => { evt.target.disabled = false });
  247. return false;
  248. } else if (data.items.length > 0) {
  249. let links = data.getData('text/uri-list');
  250. if (links) links = links.split(/\r?\n/); else {
  251. links = data.getData('text/x-moz-url');
  252. if (links) links = links.split(/\r?\n/).filter((item, ndx) => ndx % 2 == 0);
  253. else if (links = data.getData('text/plain')) links = links.split(/\r?\n/);
  254. }
  255. if (Array.isArray(links) && links.length > 0) imageUrlResolver(links[0]).then(verifyImageUrl).then(function(imageUrl) {
  256. evt.target.value = imageUrl;
  257. coverPreview(evt.target, imageUrl);
  258. checkImageSize(imageUrl, evt.target).then(function(imageUrl) {
  259. evt.target.disabled = true;
  260. return imageHosts.rehostImages([imageUrl]).then(singleImageGetter).then(function(imageUrl) {
  261. if (imageUrl == null) throw 'invalid image';
  262. evt.target.value = imageUrl;
  263. }).catch(function(reason) {
  264. evt.target.value = imageUrl;
  265. Promise.resolve(reason).then(msg => { alert(msg + ' (not rehosted)') });
  266. }).then(() => { evt.target.disabled = false });
  267. });
  268. }).catch(function(e) {
  269. console.error(e);
  270. alert(e);
  271. });
  272. return false;
  273. }
  274. return true;
  275. }
  276.  
  277. function textAreaDropHandler(evt) {
  278. if (evt.dataTransfer == null || evt.shiftKey) return true;
  279. if (evt.dataTransfer.files.length > 0) {
  280. let images = Array.from(evt.dataTransfer.files).filter(file => !file.type || file.type.startsWith('image/'));
  281. if (images.length <= 0) return true;
  282. evt.target.disabled = true;
  283. if (!['notwhat.cd'].some(hostname => document.domain == hostname))
  284. var progressBar = new ULProgressBar(evt.target, images.map(image => image.size));
  285. imageHosts.uploadImages(images, ULProgressBar.prototype.update.bind(progressBar))
  286. .then(resultsHandler, reason => { Promise.resolve(reason).then(msg => { alert(msg) }) })
  287. .then(function() {
  288. ULProgressBar.prototype.cleanUp.call(progressBar);
  289. evt.target.disabled = false;
  290. });
  291. evt.stopPropagation();
  292. return false;
  293. } else if (evt.dataTransfer.items.length > 0) {
  294. let content = evt.dataTransfer.getData('text/uri-list');
  295. if (content) content = content.split(/(?:\r?\n)+/); else {
  296. content = evt.dataTransfer.getData('text/x-moz-url');
  297. if (content) content = content.split(/(?:\r?\n)+/).filter((item, ndx) => ndx % 2 == 0);
  298. };
  299. if (!Array.isArray(content) || content.length <= 0) return true;
  300. Promise.all(content.map(imageUrlResolver)).then(function(resolved) {
  301. evt.target.disabled = true;
  302. var resolvedUrls = resolved.flatten();
  303. if (resolvedUrls.length > 1 && !['notwhat.cd'].some(hostname => document.domain == hostname))
  304. progressBar = new RHProgressBar(evt.target, resolvedUrls.length);
  305. imageHosts.rehostImages(resolvedUrls, RHProgressBar.prototype.update.bind(progressBar)).catch(function(reason) {
  306. Promise.resolve(reason).then(msg => { alert(msg + ' (not rehosted)') });
  307. RHProgressBar.prototype.update.call(progressBar, -1, false);
  308. return verifyImageUrls(resolvedUrls);
  309. }).then(function(results) {
  310. resultsHandler(results, arrayGrouping(resolved).flatten());
  311. RHProgressBar.prototype.cleanUp.call(progressBar);
  312. evt.target.disabled = false;
  313. });
  314. });
  315. evt.stopPropagation();
  316. return false;
  317. }
  318. return true;
  319.  
  320. function resultsHandler(results, groups = undefined) {
  321. if (results.length <= 0) return;
  322. if (evt.altKey && !evt.target.noBBCode) {
  323. let modal = document.createElement('div');
  324. modal.id = 'ihh-template-selector-background';
  325. modal.style = 'position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-color: #0008;' +
  326. 'opacity: 0; transition: opacity 0.15s linear;';
  327. modal.innerHTML = `
  328. <div id="ihh-template-selector" style="background-color: darkslategray; position: absolute; top: 30%; left: 35%; border-radius: 0.5em; padding: 20px 30px;">
  329. <div style="color: white;margin-bottom: 20px;">Insert as:</div>
  330. <input id="btn-insert" type="button" value="Insert" style="margin-top: 30px"/>
  331. <input id="btn-cancel" type="button" value="Cancel" style="margin-top: 30px"/>
  332. </div>
  333. `;
  334. document.body.append(modal);
  335. let form = document.getElementById('ihh-template-selector'),
  336. btnInsert = document.querySelector('div#ihh-template-selector input#btn-insert'),
  337. btnCancel = document.querySelector('div#ihh-template-selector input#btn-cancel');
  338. if (form == null || btnInsert == null || btnCancel == null) {
  339. console.warn('Dialog creation error');
  340. insertResults();
  341. return;
  342. }
  343. [
  344. ['BBcode: original size', 1],
  345. ['BBcode: thumbnails with link to original', 2],
  346. ['BBcode: thumbnails with link to share page', 3],
  347. ['BBcode: screenshot comparison (PTP)', 4],
  348. ['BBcode: screenshot comparison + encode images (PTP)', 5],
  349. ['Markdown: original size', 9],
  350. ['HTML: original size', 6],
  351. ['HTML: thumbnails with link to original', 7],
  352. ['HTML: thumbnails with link to share page', 8],
  353. ['Raw links', 0],
  354. ].forEach(function(item) {
  355. var radio = document.createElement('input');
  356. radio.type = 'radio';
  357. radio.name = 'template';
  358. radio.value = item[1];
  359. radio.style = 'margin: 5px 15px 5px 0px; cursor: pointer;';
  360. var label = document.createElement('label');
  361. label.style = 'color: white; cursor: pointer; -webkit-user-select: none; ' +
  362. '-moz-user-select: none; -ms-user-select: none; user-select: none;';
  363. label.append(radio);
  364. label.append(item[0]);
  365. form.insertBefore(label, btnInsert);
  366. var br = document.createElement('br');
  367. form.insertBefore(br, btnInsert);
  368. });
  369. if (!results.some(result => typeof result == 'object'
  370. && urlParser.test(result.original) && urlParser.test(result.thumb))) disableItem(2, 7);
  371. if (!results.some(result => typeof result == 'object'
  372. && urlParser.test(result.original) && urlParser.test(result.share))) disableItem(3, 8);
  373. if (results.length % 2 != 0) disableItem(4, 5);
  374. form.onclick = evt => { evt.stopPropagation() };
  375. btnInsert.onclick = function(evt) {
  376. var template = document.querySelector('div#ihh-template-selector input[name="template"]:checked');
  377. if (template != null) template = parseInt(template.value);
  378. modal.remove();
  379. insertResults(template);
  380. };
  381. modal.onclick = btnCancel.onclick = evt => { modal.remove() };
  382. window.setTimeout(() => { modal.style.opacity = 1 }, 0);
  383.  
  384. function disableItem(...n) {
  385. n.forEach(function(n) {
  386. var radio = document.querySelector('div#ihh-template-selector input[type="radio"][value="' + n + '"]');
  387. if (radio == null) return;
  388. radio.parentNode.style.opacity = 0.5;
  389. radio.disabled = true;
  390. });
  391. }
  392. } else insertResults();
  393.  
  394. function insertResults(template = 1) {
  395. if (evt.target.noBBCode) template = 0;
  396. if (typeof template != 'number' || isNaN(template)) return;
  397. var code = '', nl = [6, 7, 8].includes(template) ? '<br>\n' : '\n', _template;
  398. results.forEach(function(result, index) {
  399. if (_template == 1 && /\[img\]\[\/img\]/i.test(evt.target.value)) {
  400. evt.target.value = RegExp.leftContext + '[img]' + getImgUrl(result) + '[/img]' + RegExp.rightContext;
  401. return;
  402. }
  403. _template = template;
  404. if (template == 2 && (typeof result != 'object' || !urlParser.test(result.original) || !urlParser.test(result.thumb))
  405. || template == 3 && (typeof result != 'object' || !urlParser.test(result.share) || !urlParser.test(result.thumb)))
  406. _template = 1;
  407. else if (template == 7 && (typeof result != 'object' || !urlParser.test(result.original) || !urlParser.test(result.thumb))
  408. || template == 8 && (typeof result != 'object' || !urlParser.test(result.share) || !urlParser.test(result.thumb)))
  409. _template = 6;
  410. else _template = template;
  411. if (index > 0) {
  412. let thumb = [2, 3, 7, 8].includes(_template);
  413. code += isGroupBoundary(groups, index) ? thumb ? nl : nl + nl : thumb ? ' ' : nl;
  414. }
  415. switch (_template) {
  416. case 0: case 4: case 5: code += getImgUrl(result); break;
  417. case 1: code += '[img]' + getImgUrl(result) + '[/img]'; break;
  418. case 2: code += '[url=' + getImgUrl(result) + '][img]' + result.thumb + '[/img][/url]'; break;
  419. case 3: code += '[url=' + result.share + '][img]' + result.thumb + '[/img][/url]'; break;
  420. case 6: code += '<img src="' + getImgUrl(result) + '">'; break;
  421. case 7: code += '<a href="' + getImgUrl(result) + '" target="_blank"><img src="' + result.thumb + '"></a>'; break;
  422. case 8: code += '<a href="' + result.share + '" target="_blank"><img src="' + result.thumb + '"></a>'; break;
  423. case 9: code += '![](' + getImgUrl(result) + ')'; break;
  424. }
  425. });
  426. if ([4, 5].includes(template)) {
  427. code = '[comparison=Source, Encode]' + code + '[/comparison]';
  428. if (template == 5) {
  429. code += nl;
  430. results.forEach((result, index) => { if (index % 2 != 0) code += nl + '[img]' + getImgUrl(result) + '[/img]' });
  431. }
  432. }
  433. if (evt.target.value.trimRight().length <= 0) evt.target.value = code; else if (evt.ctrlKey) {
  434. evt.target.value = evt.target.value.slice(0, evt.rangeOffset) + code + evt.target.value.slice(evt.rangeOffset);
  435. } else evt.target.value = evt.target.value.trimRight() + nl + nl + code;
  436.  
  437. function getImgUrl(result) {
  438. if (typeof result == 'object' && urlParser.test(result.original)) return result.original;
  439. if (typeof result == 'string' && urlParser.test(result)) return result;
  440. throw 'Invalid result format';
  441. }
  442. }
  443. }
  444. }
  445.  
  446. function textAreaPasteHandler(evt) {
  447. if (evt.clipboardData == null) return true;
  448. if (evt.clipboardData.files.length > 0) {
  449. let images = Array.from(evt.clipboardData.files).filter(file => !file.type || file.type.startsWith('image/'));
  450. if (images.length <= 0) return true;
  451. evt.target.disabled = true;
  452. if (!['notwhat.cd'].some(hostname => document.domain == hostname))
  453. var progressBar = new ULProgressBar(evt.target, images.map(image => image.size));
  454. imageHosts.uploadImages(images, ULProgressBar.prototype.update.bind(progressBar))
  455. .then(insert, reason => { Promise.resolve(reason).then(msg => { alert(msg) }) })
  456. .then(function() {
  457. ULProgressBar.prototype.cleanUp.call(progressBar);
  458. evt.target.disabled = false;
  459. });
  460. evt.stopPropagation();
  461. return false;
  462. } else if (evt.clipboardData.items.length > 0) {
  463. let urls = evt.clipboardData.getData('text/plain').split(/(?:\r?\n)+/);
  464. if (urls.length <= 0 || !urls.every(RegExp.prototype.test.bind(urlParser))) return true;
  465. // Promise.all(urls.map(imageUrlResolver)).then(function(resolved) {
  466. // evt.target.disabled = true;
  467. // var resolvedUrls = resolved.flatten();
  468. // if (resolvedUrls.length > 1 && !['notwhat.cd'].some(hostname => document.domain == hostname))
  469. // progressBar = new RHProgressBar(evt.target, resolvedUrls.length);
  470. // imageHosts.rehostImages(resolvedUrls, RHProgressBar.prototype.update.bind(progressBar)).catch(function(reason) {
  471. // Promise.resolve(reason).then(msg => { alert(msg + ' (not rehosted)') });
  472. // RHProgressBar.prototype.update.call(progressBar, -1, false);
  473. // return verifyImageUrls(resolvedUrls);
  474. // }).then(function(results) {
  475. // insert(results, arrayGrouping(resolved).flatten());
  476. // progressBar.cleanUp.call(progressBar);
  477. // evt.target.disabled = false;
  478. // });
  479. // });
  480. // evt.stopPropagation();
  481. // return false;
  482. }
  483. return true;
  484.  
  485. function insert(results, groups = undefined) {
  486. var selStart = evt.target.selectionStart, phpBB = '';
  487. results.forEach(function(result, index) {
  488. var thumb = evt.altKey && !evt.target.noBBCode && typeof result == 'object'
  489. && urlParser.test(result.originasl) && urlParser.test(result.thumb);
  490. if (index > 0) phpBB += isGroupBoundary(groups, index) ? thumb ? '\n' : '\n\n' : thumb ? ' ' : '\n';
  491. if (typeof result == 'object' && result.original) var imgUrl = result.original;
  492. else if (typeof result == 'string') imgUrl = result;
  493. else throw 'Invalid result format';
  494. phpBB += evt.target.noBBCode ? phpBB += imgUrl : !thumb ? '[img]' + imgUrl + '[/img]'
  495. : '[url=' + imgUrl + '][img]' + result.thumb + '[/img][/url]';
  496. });
  497. if (phpBB.length <= 0) return;
  498. evt.target.value = evt.target.value.slice(0, selStart) + phpBB + evt.target.value.slice(evt.target.selectionEnd);
  499. evt.target.setSelectionRange(selStart + phpBB.length, selStart + phpBB.length);
  500. }
  501. }
  502.  
  503. function arrayGrouping(arr) {
  504. return Array.isArray(arr) ? arr.map(function(elem) {
  505. if (!Array.isArray(elem)) return 1;
  506. return elem.every(elem => !Array.isArray(elem)) ? elem.length : arrayGrouping(elem);
  507. }) : null;
  508. }
  509.  
  510. function isGroupBoundary(groups, index) {
  511. return index > 0 && Array.isArray(groups)
  512. && groups.some((len, ndx, arr) => index == arr.slice(0, ndx).reduce((acc, len) => acc + len, 0));
  513. }
  514.  
  515. function checkImageSize(imageUrl, node, size) {
  516. var imageSizeReduce = GM_getValue('image_size_reduce_threshold');
  517. if (!(imageSizeReduce > 0)) return Promise.resolve(imageUrl);
  518. if (!(node instanceof Node)) node = null;
  519. if (node != null) node.disabled = true;
  520. return (size > 0 ? Promise.resolve(size) : getRemoteFileSize(imageUrl)).then(function(size) {
  521. if (size <= imageSizeReduce * 2**10) return Promise.resolve(imageUrl);
  522. return reduceImageSize(imageUrl, GM_getValue('image_reduce_maxheight', 2160),
  523. GM_getValue('image_reduce_jpegquality', 90)).then(function(output) {
  524. if (node != null) node.value = output.uri;
  525. Promise.resolve(output.size).then(reducedSize => {
  526. console.log('cover size reduced by ' + Math.round((size - reducedSize) * 100 / size) +
  527. '% (' + Math.ceil(size / 2**10) + ' → ' + Math.ceil(reducedSize / 2**10) + ' KiB)');
  528. });
  529. return output.uri;
  530. });
  531. }).catch(function(reason) {
  532. logFail('failed to get remote image size or optimize the image: ' + reason + ' (size reduction was not performed)');
  533. return imageUrl;
  534. }).then(function(finalUrl) {
  535. if (node != null) node.disabled = false;
  536. return finalUrl;
  537. });
  538. }
  539.  
  540. function imageUrlResolver(url) {
  541. return urlResolver(url).then(url => verifyImageUrl(url).catch(function(reason) {
  542. const notFound = Promise.reject('No title image for this URL');
  543. function getFromMeta(dom) {
  544. var meta = root instanceof Document || root instanceof Element ? [
  545. 'meta[property="og:image:secure_url"][content]',
  546. 'meta[property="og:image"][content]',
  547. 'meta[name="og:image"][content]',
  548. 'meta[itemprop="og:image"][content]',
  549. 'meta[itemprop="image"][content]',
  550. ].reduce((elem, selector) => elem || root.querySelector(selector), null) : null;
  551. return meta != null && urlParser.test(meta.content) ? meta.content : undefined;
  552. }
  553.  
  554. try { url = new URL(url) } catch(e) { return Promise.reject(e) }
  555. if (url.hostname.endsWith('pinterest.com'))
  556. return pinterestResolver(url);
  557. else if (url.hostname.endsWith('free-picload.com')) {
  558. if (url.pathname.startsWith('/album/')) return imageHostHandlers.picload.galleryResolver(url);
  559. } else if (url.hostname.endsWith('bandcamp.com')) return globalFetch(url).then(function(response) {
  560. var ref = response.document.querySelector('div#tralbumArt > a.popupImage');
  561. ref = ref != null ? ref.href : getFromMeta(response.document);
  562. return ref ? Promise.resolve(ref.replace(/_\d+(?=\.\w+$)/, '_0')) : notFound;
  563. }); else if (url.hostname.endsWith('7digital.com') && url.pathname.startsWith('/artist/'))
  564. return globalFetch(url).then(function(response) {
  565. var img = response.document.querySelector('img[itemprop="image"]');
  566. return img != null ? img.src : notFound;
  567. });
  568. else if (url.hostname.endsWith('geekpic.net')) return globalFetch(url).then(function(response) {
  569. var a = response.document.querySelector('div.img-upload > a.mb');
  570. return a != null ? a.href : notFound;
  571. }); else if (url.hostname.endsWith('qq.com') && url.pathname.includes('/album/')) return globalFetch(url).then(function(response) {
  572. var img = response.document.querySelector('img#albumImg');
  573. return img != null ? img.src.replace(/(?:_\d+)?(\.\w+)(?:\?.*)?$/, '$1').replace(/R\d+x\d+/, '') : notFound;
  574. }); else switch (url.hostname) {
  575. // general image hostings
  576. case 'www.imgur.com': case 'imgur.com':
  577. if (url.pathname.startsWith('/a/')) return globalFetch(url, { responseType: 'text' }).then(function(response) {
  578. if (/^\s*(?:image)\s*:\s*(\{.+\}),\s*$/m.test(response.responseText)) try {
  579. return JSON.parse(RegExp.$1).album_images.images.map(image => 'https://i.imgur.com/' + image.hash + image.ext);
  580. } catch(e) { debug.warn(e) }
  581. return notFound;
  582. });
  583. return globalFetch(url).then(response => response.document.querySelector('link[rel="image_src"]').href);
  584. case 'pixhost.to':
  585. if (url.pathname.startsWith('/gallery/')) return globalFetch(url).then(response =>
  586. Promise.all(Array.from(response.document.querySelectorAll('div.images > a')).map(a => imageUrlResolver(a.href))));
  587. if (url.pathname.startsWith('/show/')) return globalFetch(url)
  588. .then(response => response.document.querySelector('img#image').src);
  589. break;
  590. case 'malzo.com':
  591. if (url.pathname.startsWith('/al/')) return imageHostHandlers.malzo.galleryResolver(url);
  592. break;
  593. case 'imgbb.com': case 'ibb.co':
  594. if (url.pathname.startsWith('/album/')) return imageHostHandlers.imgbb.galleryResolver(url);
  595. break;
  596. case 'jerking.empornium.ph':
  597. if (url.pathname.startsWith('/album/')) return imageHostHandlers.jerking.galleryResolver(url);
  598. break;
  599. case 'imgbox.com':
  600. if (url.pathname.startsWith('/g/')) return globalFetch(url).then(response =>
  601. Promise.all(Array.from(response.document.querySelectorAll('div#gallery-view-content > a'))
  602. .map(a => imageUrlResolver('https://imgbox.com' + a.pathname))));
  603. break;
  604. case 'postimage.org': case 'postimg.cc':
  605. if (!url.pathname.startsWith('/gallery/')) break;
  606. return PostImage.galleryResolver(url);
  607. case 'www.imagevenue.com': case 'imagevenue.com':
  608. return globalFetch(url, { headers: { Referer: 'http://www.imagevenue.com/' } }).then(function(response) {
  609. var images = Array.from(response.document.querySelectorAll('div.card img')).map(function(img) {
  610. return img.src.includes('://cdn-images') ? Promise.resolve(img.src) : imageUrlResolver(img.parentNode.href);
  611. });
  612. return images.length > 1 ? Promise.all(images) : images.length == 1 ? images[0] : notFound;
  613. });
  614. case 'www.imageshack.us': case 'imageshack.us':
  615. return globalFetch(url).then(response => response.document.querySelector('a#share-dl').href);
  616. case 'www.flickr.com': case 'flickr.com':
  617. if (!url.pathname.startsWith('/photos/')) break;
  618. return globalFetch(url).then(function(response) {
  619. if (/\b(?:modelExport)\s*:\s*(\{.+\}),/.test(response.responseText)) try {
  620. var urls = JSON.parse(RegExp.$1).main['photo-models'].map(function(photoModel) {
  621. var sizes = Object.keys(photoModel.sizes).sort((a, b) => photoModel.sizes[b].width * photoModel.sizes[b].height
  622. - photoModel.sizes[a].width * photoModel.sizes[a].height);
  623. return sizes.length > 0 ? 'https:'.concat(photoModel.sizes[sizes[0]].url) : null;
  624. });
  625. if (urls.length == 1) return urls[0]; else if (urls.length > 1) return urls;
  626. } catch(e) { console.warn(e) }
  627. return notFound;
  628. });
  629. case 'photos.google.com':
  630. return googlePhotosResolver(url);
  631. case 'www.500px.com': case 'web.500px.com': case '500px.com':
  632. if (/^\/photo\/(\d+)\b/i.test(url.pathname))
  633. return _500pxUrlHandler('photos?ids='.concat(RegExp.$1));
  634. else if (/\/galleries\/([\w\-]+)/i.test(url.pathname)) {
  635. let galleryId = RegExp.$1;
  636. return globalFetch(url, { rsponseType: 'text' }).then(function(response) {
  637. if (!/\b(?:App\.CuratorId)\s*=\s*"(\d+)"/.test(response.responseText)) return Promise.reject('Unexpected page structure');
  638. return _500pxUrlHandler('users/' + RegExp.$1 + '/galleries/' + galleryId + '/items?sort=position&sort_direction=asc&rpp=999');
  639. });
  640. }
  641. break;
  642. case 'www.pxhere.com': case 'pxhere.com':
  643. if (url.pathname.includes('/photo/')) return globalFetch(url).then(response =>
  644. JSON.parse(response.document.querySelector('div.hub-media-content > script[type="application/ld+json"]').text).contentUrl);
  645. else if (url.pathname.includes('/collection/')) return pxhereCollectionResolver(url);
  646. break;
  647. case 'www.unsplash.com': case 'unsplash.com':
  648. if (url.pathname.startsWith('/photos/')) return globalFetch(url.origin + url.pathname + '/download', { method: 'HEAD' })
  649. .then(response => response.finalUrl.replace(/\?.*$/, ''));
  650. else if (url.pathname.includes('/collections/')) return unsplashCollectionResolver(url);
  651. break;
  652. case 'www.pexels.com': case 'pexels.com':
  653. if (url.pathname.startsWith('/photo/')) return globalFetch(url)
  654. .then(response => response.document.querySelector('meta[property="og:image"][content]').content.replace(/\?.*$/, ''));
  655. else if (url.pathname.startsWith('/collections/')) return pexelsCollectionResolver(url);
  656. break;
  657. case 'www.piwigo.org': case 'piwigo.org':
  658. /*if (url.pathname.includes('/picture/')) */return globalFetch(url, { responseType: 'text' }).then(function(response) {
  659. if (/^(?:RVAS)\s*=\s*(\{[\S\s]+?\})$/m.test(response.responseText)) try {
  660. var derivatives = eval('(' + RegExp.$1 + ')').derivatives.sort((a, b) => b.w * b.h - a.w * a.h);
  661. return derivatives.length > 0 ? 'https://piwigo.org/demo/'.concat(derivatives[0].url) : notFound;
  662. } catch(e) { console.warn(e) }
  663. return Promise.reject('Unexpected page structure');
  664. });
  665. break;
  666. case 'www.freeimages.com': case 'freeimages.com':
  667. if (url.pathname.startsWith('/photo/')) return globalFetch(url).then(function(response) {
  668. var types = Array.from(response.document.querySelectorAll('ul.download-type > li > span.reso'))
  669. .sort((a, b) => eval(b.textContent.replace('x', '*')) - eval(a.textContent.replace('x', '*')));
  670. return types.length > 0 ? url.origin.concat(types[0].parentNode.querySelector('a').pathname) : notFound;
  671. });
  672. break;
  673. case 'redacted.ch':
  674. if (url.pathname != '/image.php') break;
  675. return globalFetch(url, { method: 'HEAD' }).then(response => response.finalUrl);
  676. case 'demo.cloudimg.io':
  677. if (!/\b(https?:\/\/\S+)$/.test(url.pathname.concat(url.search, url.hash))) break;
  678. var resolved = RegExp.$1;
  679. if (/\b(?:https?):\/\/(?:\w+\.)*discogs\.com\//i.test(resolved)) break;
  680. return imageUrlResolver(resolved);
  681. case 'www.pimpandhost.com': case 'pimpandhost.com':
  682. if (!url.pathname.startsWith('/image/')) break;
  683. return globalFetch(url).then(function(response) {
  684. var elem = resopnse.document.querySelector('div.main-image-wrapper');
  685. if (elem != null && elem.dataset.src) return 'https:'.concat(elem.dataset.src);
  686. elem = resopnse.document.querySelector('div.img-wrapper > a > img');
  687. return elem != null ? 'https:'.concat(elem.src) : notFound;
  688. });
  689. case 'www.screencast.com': case 'screencast.com':
  690. return globalFetch(url).then(function(response) {
  691. var ref = response.document.querySelectorAll('ul#containerContent > li a.media-link');
  692. if (ref.length <= 0) return getFromMeta(response.document) || notFound;
  693. return Promise.all(Array.from(ref).map(a => imageUrlResolver('https://www.screencast.com' + a.href)));
  694. });
  695. case 'abload.de':
  696. if (!url.pathname.startsWith('/image.php')) break;
  697. return globalFetch(url).then(function(response) {
  698. var elem = response.document.querySelector('img#image');
  699. if (elem == null) return notFound;
  700. var src = new URL(elem.src);
  701. return imageHostHandlers.abload.origin + src.pathname + src.search;
  702. });
  703. case 'fastpic.ru':
  704. if (url.pathname.startsWith('/view/'))
  705. return globalFetch(url).then(response => imageUrlResolver(response.document.querySelector('a.img-a').href));
  706. if (url.pathname.startsWith('/fullview/')) return globalFetch(url).then(function(response) {
  707. var node = response.document.getElementById('image');
  708. if (node != null) return node.src;
  709. return /\bvar\s+loading_img\s*=\s*'(\S+?)';/.test(response.responseText) ? RegExp.$1 : notFound;
  710. });
  711. break;
  712. case 'www.radikal.ru': case 'radikal.ru': case 'a.radikal.ru':
  713. return globalFetch(url).then(response => response.document.querySelector('div.mainBlock img').src);
  714. case 'imageban.ru': case 'ibn.im':
  715. return globalFetch(url).then(response => response.document.querySelector('a[download]').href);
  716. case 'svgshare.com':
  717. return globalFetch(url).then(function(response) {
  718. var link;
  719. response.document.querySelectorAll('ul#shares > li > input[type="text"]')
  720. .forEach(input => { if (!link && /^(?:https?:\/\/.+\.svg)$/.test(input.value)) link = input.value; });
  721. return link || notFound;
  722. });
  723. case 'slow.pics':
  724. if (!url.pathname.startsWith('/c/')) break;
  725. return globalFetch(url).then(function(response) {
  726. var nodes = response.document.querySelectorAll('img.card-img-top');
  727. if (nodes.length > 1) return Array.from(nodes).map(img => img.src);
  728. else if (nodes.length > 0) return nodes[0].src;
  729. nodes = response.document.querySelectorAll('a#comparisons + div.dropdown-menu > a.dropdown-item');
  730. if (nodes.length > 0) return Promise.all(Array.from(nodes).map(a => globalFetch(url.origin + a.pathname).then(response =>
  731. Array.from(response.document.querySelectorAll('div#preload-images > img')).map(img => img.src))));
  732. return notFound;
  733. });
  734. // music-related
  735. case 'www.musicbrainz.org': case 'musicbrainz.org':
  736. if (!['release', 'release-group'].some(branch => url.pathname.includes('/' + branch + '/'))) break;
  737. return globalFetch(url).then(function(response) {
  738. var node = response.document.querySelector('a.artwork-image');
  739. if (node != null) return node.href;
  740. return (node = response.document.querySelector('div.cover-art > img')) != null ? node.src : notFound;
  741. });
  742. case 'music.apple.com':
  743. if (!/^https?:\/\/(?:\w+\.)*apple\.com\/.*\/(\d+)(?=$|\?)/i.test(url)) break;
  744. return globalFetch(url).then(function(response) {
  745. var meta = response.document.querySelector('meta[property="og:image"][content]');
  746. if (meta == null || !meta.content) return notFound;
  747. return verifyImageUrl(meta.content.replace(/\/\d+x\d+\w*(?=\.\w+$)/, '/100000x100000-999')).catch(reason => meta.content);
  748. });
  749. case 'www.deezer.com': case 'deezer.com':
  750. return globalFetch(url).then(function(response) {
  751. var meta = response.document.querySelector('meta[property="og:image"][content]');
  752. if (meta == null || !meta.content) return notFound;
  753. return verifyImageUrl(meta.content.replace(/\/\d+x\d+\w*(?=\.\w+$)/, '/1400x1400-000000-100-0-0'))
  754. .catch(reason => meta.content);
  755. });
  756. case 'www.qobuz.com': case 'qobuz.com':
  757. if (!url.pathname.includes('/album/')) break;
  758. return globalFetch(url).then(function(response) {
  759. var img = response.document.querySelector('div.album-cover > img');
  760. if (img == null) return notFound;
  761. return verifyImageUrl(img.src.replace(/_\d{3}(?=\.\w+$)/, '_org'))
  762. .catch(reason => verifyImageUrl(img.src.replace(/_\d{3}(?=\.\w+$)/, '_max')))
  763. .catch(reason => img.src);
  764. });
  765. case 'www.discogs.com': case 'discogs.com':
  766. return globalFetch(url).then(function(response) {
  767. const dcOrigin = 'https://www.discogs.com';
  768. let master = response.document.getElementById('all-versions-link');
  769. return (master != null ?
  770. globalFetch(dcOrigin + master.pathname).then(response => getFromMeta(response.document) || notFound)
  771. : Promise.reject('no master')).catch(reason => getFromMeta(response.document) || notFound)
  772. .then(imgUrl => /^(?:https?):\/\/(?:img\.discogs\.com)\/.+\/(\S+?\.\w+)\b/i.test(imgUrl) ?
  773. dcOrigin + '/image/' + RegExp.$1 : imgUrl);
  774. });
  775. case 'www.boomkat.com': case 'boomkat.com':
  776. if (!url.pathname.startsWith('/products/')) break;
  777. return globalFetch(url).then(function(response) {
  778. var img = response.document.querySelector('img[itemprop="image"]');
  779. if (img == null) return notFound;
  780. return verifyImageUrl(img.src.replace(/\/large\//i, '/original/')).catch(reason => img.src);
  781. });
  782. case 'www.bleep.com': case 'bleep.com':
  783. if (!url.pathname.startsWith('/release/')) break;
  784. return globalFetch(url).then(function(response) {
  785. var meta = getFromMeta(response.document);
  786. return meta ? verifyImageUrl(meta.replace(/\/r\/[a-z]\//i, '/r/')).catch(reason => meta) : notFound;
  787. });
  788. case 'www.soundcloud.com': case 'soundcloud.com':
  789. return globalFetch(url).then(function(response) {
  790. var meta = getFromMeta(response.document);
  791. return meta ? verifyImageUrl(meta.replace(/\bt\d+x\d+(?=\.\w+$)/, 'original')).catch(reason => meta) : notFound;
  792. });
  793. case 'www.prestomusic.com': case 'prestomusic.com':
  794. if (!url.pathname.includes('/products/')) break;
  795. return globalFetch(url)
  796. .then(response => verifyImageUrl(response.document.querySelector('div.c-product-block__aside > a').href.replace(/\?\d+$/)));
  797. case 'www.bontonland.cz':case 'bontonland.cz':
  798. return globalFetch(url).then(response => response.document.querySelector('a.detailzoom').href);
  799. case 'www.nativedsd.com':case 'nativedsd.com':
  800. if (!url.pathname.includes('/albums/')) break;
  801. return globalFetch(url).then(response => response.document.querySelector('a#album-cover').href);
  802. case 'www.prostudiomasters.com': case 'prostudiomasters.com':
  803. if (!url.pathname.includes('/album/')) break;
  804. return globalFetch(url).then(function(response) {
  805. var a = response.document.querySelector('img.album-art');
  806. return verifyImageUrl(a.currentSrc).catch(reason => a.src);
  807. });
  808. case 'www.e-onkyo.com': case 'e-onkyo.com':
  809. if (!url.pathname.includes('/album/')) break;
  810. return globalFetch(url).then(function(response) {
  811. var meta = getFromMeta(response.document);
  812. return meta ? meta.replace(/\/s\d+\//, '/s0/') : notFound;
  813. })
  814. case 'store.acousticsounds.com':
  815. return globalFetch(url).then(function(response) {
  816. var link = response.document.querySelector('div#detail > link[rel="image_src"]');
  817. return verifyImageUrl(link.href.replace(/\/medium\//i, '/xlarge/')).catch(reason => link.href);
  818. });
  819. case 'www.indies.eu': case 'indies.eu':
  820. if (!url.pathname.includes('/alba/')) break;
  821. return globalFetch(url).then(response => verifyImageUrl)(response.document.querySelector('div.obrazekDetail > img').src);
  822. case 'www.beatport.com': case 'classic.beatport.com': case 'beatport.com':
  823. if (!url.pathname.includes('/release/')) break;
  824. return globalFetch(url).then(function(response) {
  825. var elem = getFromMeta(response.document);
  826. return elem || ((elem = response.document.querySelector('div.artwork')) != null ?
  827. 'https:' + elem.dataset.modalArtwork : notFound);
  828. }).then(imgUrl => imgUrl.replace(/\/image_size\/\d+x\d+\//i, '/image/'));
  829. case 'www.supraphonline.cz': case 'supraphonline.cz':
  830. if (!url.pathname.includes('/album/')) break;
  831. return globalFetch(url).then(response => verifyImageUrl(response.document.querySelector('meta[itemprop="image"]')
  832. .content.replace(/\?.*$/, '')).catch(reason => notFound));
  833. case 'vgmdb.net':
  834. if (!url.pathname.includes('/album/')) break;
  835. return globalFetch(url).then(function(response) {
  836. var div = response.document.querySelector('div#coverart');
  837. return verifyImageUrl(/\b(?:url)\s*\(\"(.*)"\)/i.test(div.style['background-image']) && RegExp.$1).catch(reason => notFound);
  838. });
  839. case 'www.ototoy.jp': case 'ototoy.jp':
  840. return globalFetch(url).then(function(response) {
  841. var img = response.document.querySelector('div#tralbumArt > a.popupImage');
  842. return verifyImageUrl(img.dataset.src).catch(reason => img.src);
  843. });
  844. case 'music.yandex.ru':
  845. if (!url.pathname.includes('/album/')) break;
  846. return globalFetch(url).then(function(response) {
  847. var script = response.document.querySelector('script.light-data');
  848. return verifyImageUrl(JSON.parse(script.text).image).catch(reason => notFound);
  849. });
  850. case 'www.pias.com': case 'store.pias.com': case 'pias.com':
  851. return globalFetch(url).then(function(response) {
  852. var node = getFromMeta(response.document);
  853. if (node) return verifyImage(node.replace(/\/[sbl]\//i, '/')).catch(reason => node);
  854. node = response.document.querySelector('img[itemprop="image"]');
  855. return node != null ? verifyImage(node.src.replace(/\/[sbl]\//i, '/')).catch(reason => node.src) : notFound;
  856. });
  857. case 'www.eclassical.com': case 'eclassical.com':
  858. return globalFetch(url).then(function(response) {
  859. var a = response.document.querySelector('div#articleImage > a');
  860. return a != null ? a.href : notFound;
  861. });
  862. case 'www.hdtracks.com': case 'hdtracks.com':
  863. if (!/\/album\/(\w+)\b/.test(url)) break;
  864. return fetch('https://hdtracks.azurewebsites.net/api/v1/album/' + RegExp.$1).then(response => response.json())
  865. .then(result => result.status.toLowerCase() == 'ok' ? result.cover : Promise.reject(result.status));
  866. case 'www.muziekweb.nl': case 'muziekweb.nl':
  867. if (!/\/Link\/(\w+)\b/i.test(url)) break;
  868. return globalFetch(url).then(function(response) {
  869. let meta = getFromMeta(response.document)
  870. return meta ? meta.replace(/\/COVER\/\w+\b/i, '/COVER/SUPERLARGE') : notFound;
  871. });
  872. case 'www.deejay.de': case 'deejay.de':
  873. return globalFetch(url).then(function(response) {
  874. var elem = response.document.querySelector('div#gallery > a') || response.document.querySelector('div.cover a');
  875. if (elem != null) return 'https://www.deejay.de' + elem.pathname;
  876. return (elem = getFromMeta(response.document)) ? elem : notFound;
  877. }).then(imgUrl => verifyImageUrl(imgUrl.replace(/\/images\/\w+\//i, '/images/xxl/')).catch(() => imgUrl));
  878. case 'music.163.com':
  879. if (!url.href.includes('/album')) break;
  880. return globalFetch(url.href.replace('/#/', '/')).then(response => getFromMeta(response.document) || notFound);
  881. // movie-related
  882. case 'www.imdb.com': case 'imdb.com':
  883. if (!['title/tt', 'name/nm'].some(cat => url.pathname.startsWith('/' + cat))) break;
  884. return globalFetch(url).then(function(response) {
  885. const galleryDetector = /\/mediaindex(?:[\/\?].*)?$/i, imgStripper = /\._V\d+_[\w\,]*(?=\.)/;
  886. if (!galleryDetector.test(response.finalUrl)) {
  887. let node = response.document.head.querySelector(':scope > script[type="application/ld+json"]');
  888. if (node != null) try {
  889. let image = JSON.parse(node.text).image;
  890. if (typeof image == 'string') return verifyImageUrl(image.replace(imgStripper, '')).catch(reason => notFound);
  891. } catch(e) { console.warn(e) }
  892. node = response.document.querySelector('meta[property="og:image"][content]');
  893. return node != null && !/\/imdb\w*_logo\./i.test(node.content) ?
  894. node.content.replace(imgStripper, '') : notFound;
  895. }
  896. var titleId = /\/title\/(tt\d+)\//i.test(response.finalUrl) && RegExp.$1;
  897. return titleId ? globalFetch(response.finalUrl.replace(galleryDetector, '/mediaviewer'), { responseType: 'text' }).then(function(response) {
  898. if (/\b(?:window\.IMDbMediaViewerInitialState)\s*=\s*(\{.*\});/.test(response.responseText)) try {
  899. let allImages = eval('(' + RegExp.$1 + ')').mediaviewer.galleries[titleId].allImages;
  900. if (allImages.length > 0) return allImages.map(image => image.src.replace(imgStripper, ''));
  901. } catch(e) { console.warn(e) }
  902. return notFound;
  903. }) : Promise.reject('title id not found');
  904. });
  905. case 'www.themoviedb.org': case 'themoviedb.org':
  906. if (!['movie', 'person'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  907. return globalFetch(url).then(function(response) {
  908. var node = response.document.querySelector('meta[property="og:image"][content]');
  909. return verifyImageUrl(node.content.replace(/\/p\/\w+\//i, '/p/original/')).catch(function(reason) {
  910. node = response.document.querySelector('div.image_content > img');
  911. return verifyImageUrl(node.dataset.src.replace(/\/p\/\w+\//i, '/p/original/'))
  912. .catch(reason => verifyImageUrl(node.src.replace(/\/p\/\w+\//i, '/p/original/')))
  913. .catch(reason => verifyImageUrl(dataset.src)).catch(reason => node.src);
  914. }).catch(reason => notFound);
  915. });
  916. case 'www.omdb.org': case 'omdb.org':
  917. if (!['movie', 'person'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  918. return globalFetch(url).then(function(response) {
  919. var node = response.document.querySelector('meta[property="og:image"][content]');
  920. return node != null ? verifyImageUrl(node.content) : notFound;
  921. });
  922. case 'www.thetvdb.com': case 'thetvdb.com':
  923. if (!['movies', 'series', 'people'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  924. return globalFetch(url).then(response => verifyImageUrl(response.document.querySelector('img.img-responsive').src));
  925. case 'www.rottentomatoes.com': case 'rottentomatoes.com':
  926. if (!['m', 'celebrity', 'tv'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  927. return globalFetch(url).then(function(response) {
  928. // if (/\b(?:context\.shell)\s*=\s*(\{.+?});/.test(response.responseText)) try {
  929. // return JSON.parse(RegExp.$1).header.certifiedMedia.certifiedFreshMovieInTheater4.media.posterImg;
  930. // } catch(e) { console.warn(e) }
  931. return verifyImageUrl(response.document.querySelector('meta[property="og:image"]').content);
  932. });
  933. case 'www.bcdb.com': case 'bcdb.com':
  934. if (!['cartoon'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  935. return globalFetch(url).then(response =>
  936. verifyImageUrl(document.location.protocol.concat(response.document.querySelector('meta[property="og:image"]').content)));
  937. case 'www.boxofficemojo.com': case 'boxofficemojo.com':
  938. if (!['releasegroup'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  939. return globalFetch(url).then(response => verifyImageUrl(response.document.querySelector('div.mojo-primary-image img').src));
  940. case 'www.metacritic.com': case 'metacritic.com':
  941. return globalFetch(url).then(function(response) {
  942. var image = response.document.querySelector('meta[property="og:image"]').content;
  943. return verifyImageUrl(image.replace(/-\d+h(?=(?:\.\w+)?$)/, '')).catch(reason => image);
  944. });
  945. case 'www.csfd.cz': case 'csfd.cz':
  946. if (!['film', 'tvurce'].some(cat => url.pathname.startsWith('/' + cat + '/'))) break;
  947. return globalFetch(url).then(function(response) {
  948. const gallerySel = 'div.ct-general.photos > div.content > ul > li > div.photo';
  949. if (response.document.querySelectorAll(gallerySel).length > 0) return new Promise(function(resolve, reject) {
  950. var urls = [], domParser = new DOMParser, origin = new URL(response.finalUrl).origin;
  951. loadPage(response.finalUrl.replace(/\/strana-\d+(?=$|\/|\?)/, ''));
  952.  
  953. function loadPage(url) {
  954. GM_xmlhttpRequest({ method: 'GET', url: url,
  955. onload: function(response) {
  956. if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
  957. var dom = domParser.parseFromString(response.responseText, 'text/html');
  958. Array.prototype.push.apply(urls, Array.from(dom.querySelectorAll(gallerySel))
  959. .map(div => /^(?:url)\s*\("?(.+?)"?\)$/i.test(div.style.backgroundImage) ?
  960. 'https:'.concat(RegExp.$1).replace(/\?.*$/, '') : null));
  961. var nextPage = dom.querySelector('div.paginator > a.next[href]');
  962. if (nextPage != null) loadPage(origin.concat(nextPage.pathname, nextPage.search)); else resolve(urls);
  963. },
  964. onerror: response => { reject(defaultErrorHandler(response)) },
  965. ontimeout: response => { reject(defaultTimeoutHandler(response)) },
  966. });
  967. }
  968. });
  969. var img = ['img.film-poster', 'img.creator-photo', 'div.image > img']
  970. .reduce((acc, selector) => acc || response.document.querySelector(selector), null);
  971. return img != null ? verifyImageUrl(img.src.replace(/\?.*$/, '')) : notFound;
  972. });
  973. case 'www.fdb.cz': case 'fdb.cz':
  974. //if (!url.pathname.startsWith('/film/')) break;
  975. return globalFetch(url).then(function(response) {
  976. var a = response.document.querySelector('a.boxPlakaty');
  977. if (a == null) return Promise.reject('Invalid page structure');
  978. a.hostname = 'www.fdb.cz';
  979. return globalFetch(a.href).then(function(response) {
  980. var imgs = response.document.querySelectorAll('span#popup_plakaty > img');
  981. return imgs.length > 0 ? verifyImageUrl(imgs[0].src) : notFound;
  982. });
  983. });
  984. case 'www.caps-a-holic.com': case 'caps-a-holic.com':
  985. if (url.pathname != '/c.php') break;
  986. return globalFetch(url).then(function(response) {
  987. function heightExtractor(n) {
  988. var node = response.document.querySelector('div.main > div.c_table > div[style]:nth-of-type(' + n + ')');
  989. if (node != null && /\b(\d{3,})\s?[x×]\s?(\d{3,})\b/.test(node.textContent)) return parseInt(RegExp.$2);
  990. console.warn(response.finalUrl, 'failed to get resolution (' + n + ')', node);
  991. return null;
  992. }
  993. const baseUrl = 'https://caps-a-holic.com/c_image.php?a=0&x=0&y=0&l=1';
  994. return Array.from(response.document.querySelectorAll('div.main > div[style] > a > img.thumb')).map(function(img) {
  995. var query = new URLSearchParams(new URL(img.parentNode.href).search);
  996. return [
  997. `${baseUrl}&s=${parseInt(query.get('s1'))}&max_height=${heightExtractor(2)}`,
  998. `${baseUrl}&s=${parseInt(query.get('s2'))}&max_height=${heightExtractor(3)}`,
  999. ];
  1000. });
  1001. });
  1002. case 'www.screenshotcomparison.com': case 'screenshotcomparison.com':
  1003. if (url.pathname.startsWith('/comparison/')) return globalFetch(url).then(function(response) {
  1004. const origin = new URL(response.finalUrl).origin;
  1005. return Array.from(response.document.querySelectorAll('div#img_nav li > a')).map(function(a) {
  1006. return globalFetch(origin.concat(a.pathname), { responseType: 'text' }).then(response => [
  1007. /\b(?:images)\[1\]='(\S+?)'/.test(response.responseText) && RegExp.$1,
  1008. /\b(?:images)\[0\]='(\S+?)'/.test(response.responseText) && RegExp.$1,
  1009. ].map(src => origin.concat(src)));
  1010. });
  1011. });
  1012. break;
  1013. case 'www.dvdbeaver.com': case 'dvdbeaver.com':
  1014. if (url.pathname.startsWith('/film')) return globalFetch(url).then(function(response) {
  1015. const origin = new URL(response.finalUrl).origin;
  1016. return Array.from(response.document.querySelectorAll('div[align="center"] > table > tbody > tr > td > a[target="_blank"] > img'))
  1017. .map(img => origin.concat(img.parentNode.pathname));
  1018. });
  1019. break;
  1020. }
  1021. return globalFetch(url, { headers: { 'Referer': url.origin } }).then(function(response) {
  1022. if (url.pathname.startsWith('/album/')
  1023. && response.document.querySelector('div#tabbed-content-group > div.content-listing > div.pad-content-listing') != null)
  1024. return new Chevereto(url.hostname).galleryResolver(url);
  1025. return getFromMeta(response.document) || notFound;
  1026. });
  1027. }));
  1028. }
  1029.  
  1030. function logFail(message) {
  1031. var log = document.getElementById('ihh-console');
  1032. if (log == null) {
  1033. log = document.createElement('div');
  1034. log.id = 'ihh-console';
  1035. log.style = 'position: fixed; bottom: 20px; right: 20px; width: 64em; border: solid lightsalmon 4px;' +
  1036. ' background-color: antiquewhite; padding: 10px; opacity: 1;' +
  1037. ' transition: opacity 1000ms linear; -webkit-transition: opacity 1000ms linear;';
  1038. document.body.append(log);
  1039. } else if (log.hTimer) {
  1040. clearTimeout(log.hTimer);
  1041. log.style.opacity = 1;
  1042. }
  1043. var div = document.createElement('div');
  1044. div.style = 'font: 600 9pt Verdana, sans-serif; color: red;';
  1045. div.textContent = message;
  1046. log.append(div);
  1047. log.hTimer = setTimeout(function(node) {
  1048. node.style.opacity = 0;
  1049. node.hTimer = setTimeout(function(node) { node.remove() }, 1000, node);
  1050. }, 30000, log);
  1051. }