Nyaa.si - Load More Thumbnail

Load image from cover/screenshot links.

  1. // ==UserScript==
  2. // @name Nyaa.si - Load More Thumbnail
  3. // @name:zh-CN Nyaa.si - 自动加载更多预览图
  4. // @namespace none
  5. // @description Load image from cover/screenshot links.
  6. // @description:zh-CN 从封面/截图链接加载图片并显示。
  7. // @icon https://www.google.com/s2/favicons?sz=64&domain=sukebei.nyaa.si
  8. // @version 0.9.7
  9. // @license MIT
  10. // @author York Wang
  11. // @match https://sukebei.nyaa.si/*
  12. // @match https://hentai-covers.site/*
  13. // @match https://www.dlsite.com/*
  14. // @match https://e-hentai.org/*
  15. // @match https://pics.dmm.co.jp/*
  16. // @match https://javtenshi.com/*
  17. // @match https://3xplanet.net/*
  18. // @match https://3xplanet.com/*
  19. // @match https://xpic.org/*
  20. // @match https://imgrock.pw/*
  21. // @match https://picrok.com/*
  22. // @match https://picbaron.com/*
  23. // @match https://imgbaron.com/*
  24. // @match https://kvador.com/*
  25. // @match https://kropic.com/*
  26. // @match https://imgsto.com/*
  27. // @match https://imgsen.com/*
  28. // @match https://imgstar.eu/*
  29. // @match https://picdollar.com/*
  30. // @match https://pics4you.net/*
  31. // @match https://silverpic.com/*
  32. // @match https://fotokiz.com/*
  33. // @match https://premalo.com/*
  34. // @match https://piczhq.com/*
  35. // @match https://trypicz.com/*
  36. // @match http://imglord.com/*
  37. // @match https://croea.com/*
  38. // @match https://imgtaxi.com/*
  39. // @match https://imgadult.com/*
  40. // @match https://imgdrive.net/*
  41. // @match https://xxxwebdlxxx.org/*
  42. // @match https://xxxwebdlxxx.top/*
  43. // @match https://uvonahaze.xyz/*
  44. // @match https://trans.firm.in/*
  45. // @match https://imgdawgknuttz.com/*
  46. // @match https://imagetwist.netlify.app/*
  47. // @match https://imagetwist.com/*
  48. // @match https://imagexport.com/*
  49. // @match https://imagenimage.com/*
  50. // @match https://hentai4free.net/*
  51. // @match https://pixhost.to/*
  52. // @match https://imgair.net/*
  53. // @match http://imgair.net/*
  54. // @match http://imgfrost.net/*
  55. // @match http://imgblaze.net/*
  56. // @match https://pig69.com/*
  57. // @match https://ai18.pics/*
  58. // @match https://porn4f.com/*
  59. // @match https://javball.com/*
  60. // @match https://ovabee.com/*
  61. // @match https://idol69.net/*
  62. // @match https://cnpics.org/*
  63. // @match https://cnxx.me/*
  64. // @match https://cosplay18.pics/*
  65. // @match https://kin8-av.com/*
  66. // @match https://555fap.com/*
  67. // @run-at document-end
  68. // @grant unsafeWindow
  69. // @grant GM_xmlhttpRequest
  70. // ==/UserScript==
  71.  
  72. (function() {
  73. 'use strict';
  74.  
  75. function Handler(pattern, process, processNyaa) {
  76. this.pattern = pattern
  77. this.process = process
  78. this.processNyaa = processNyaa
  79. }
  80. Handler.prototype.canHandle = function(url) {
  81. return this.pattern.test(url)
  82. }
  83. Handler.prototype.handle = function(url) {
  84. this.process && this.process((href, referer) => {
  85. document.location.href = href
  86. unsafeWindow.top.postMessage({"LMT": href, "LMT_SRC": referer||url}, '*')
  87. })
  88. }
  89. Handler.prototype.handleNyaa = function(url) {
  90. if(this.processNyaa) {
  91. this.processNyaa(url, href => {
  92. unsafeWindow.top.postMessage({"LMT": href, "LMT_SRC": url}, '*')
  93. })
  94. } else {
  95. unsafeWindow.LMT_Frame.src = url
  96. }
  97. }
  98. const handlers = []
  99. const addHandler = (pattern, process, processNyaa) => handlers.push(new Handler(pattern, process, processNyaa))
  100.  
  101. const doGet = async (url, pattern) => {
  102. return new Promise((resolve, reject) => {
  103. GM_xmlhttpRequest({
  104. method: 'GET',
  105. url: url,
  106. onload: res => {
  107. const src = res.responseText.match(pattern)
  108. if(src.length > 1) resolve(src[1])
  109. },
  110. onerror: err => {
  111. console.error(err)
  112. }
  113. })
  114. })
  115. }
  116.  
  117. addHandler(/^https?:\/\/(hentai-covers\.site)\/image\/\w+/, null, async (url, callback) => {
  118. callback(await doGet(url, /id="image-main" src="(.+?)"/))
  119. })
  120. addHandler(/^https?:\/\/(www\.dlsite\.com)\/maniax\/work\/=\/product_id\/RJ\d+.html$/, null, async (url, callback) => {
  121. callback(await doGet(url, /twitter:image:src" content="(.+?)"/))
  122. })
  123. addHandler(/^https?:\/\/(e-hentai\.org)\/g\/\w+\/\w+\//, null, async (url, callback) => {
  124. callback(await doGet(url, /url\((https:\/\/.+?)\) no-repeat/))
  125. })
  126. addHandler(/^https?:\/\/c\.fantia\.jp\/uploads\/product\/image\/\d+\/[\w-]+\.webp/, null, (url, callback) => {
  127. callback(url)
  128. })
  129. addHandler(/^https?:\/\/(iwtf1\.caching\.ovh|i\.postimg\.cc|i\.imgur\.com|cdn\.faleno\.net|img169\.com|qpic\.ws|img\.blr844\.com|3xplanetimg2\.com|pics4you\.net|www\.javbus\.com|\w+\.turboimg\.net|cctv123456\.com|images\d\.imagebam\.com|files\.catbox\.moe|tezimg\.campus-av\.com|(\w+\.)+steamstatic\.com|pics\.dmm\.co\.jp)(\/[\w-]+)+\.(jpg|png)$/, null, (url, callback) => {
  130. callback(url)
  131. })
  132. addHandler(/^https?:\/\/(javtenshi\.com|3xplanet\.net|3xplanet\.com)\/viewimage\/\d+\.html/, null, async (url, callback) => {
  133. callback(await doGet(url, /scale\(this\);" src="(.+)/))
  134. })
  135. addHandler(/^https?:\/\/xpic\.org(\/\w+)+/, callback => {
  136. unsafeWindow.wuLu && unsafeWindow.wuLu()
  137. const img = document.querySelector('img.attachment-original.size-original')
  138. if(img) {
  139. callback(img.src)
  140. }
  141. }, async (url, callback) => {
  142. callback(await doGet(url, /src="(.*)" class="attachment-original size-original"/))
  143. })
  144. addHandler(/^https?:\/\/(imgrock\.pw)(\/[\w\-]+)+(\.[\w\-]+)+/, callback => {
  145. // pause on CAPTCHA
  146. const iframe = document.querySelector('iframe')
  147. if(iframe && iframe.src.indexOf('captcha') > -1) return
  148.  
  149. const img = document.querySelector('.picview')
  150. if(img) {
  151. callback(img.src)
  152. } else {
  153. const btns = document.querySelectorAll('input[name=fnext]')
  154. for(let i=0;i<btns.length;i++) {if(!btns[i].style.display) btns[i].click()}
  155. const forms = document.querySelectorAll('form')
  156. for(let i=0;i<forms.length;i++) {if(forms[i].hito) {forms[i].submit()}}
  157. }
  158. })
  159. addHandler(/^https?:\/\/(picrok\.com)(\/[\w\-]+)+\.php/, callback => {
  160. // pause on CAPTCHA
  161. const iframe = document.querySelector('iframe')
  162. if(iframe && iframe.src.indexOf('captcha') > -1) return
  163.  
  164. const img = document.querySelector('.picview')
  165. if(img) {
  166. callback(img.src)
  167. } else {
  168. unsafeWindow.setTimeout(() => {
  169. const forms = document.querySelectorAll('form')
  170. const btns = document.querySelectorAll('form>button')
  171. // for(let i=0;i<btns.length;i++) {if(btns[i].style.display) forms[i-1].submit()}
  172. }, 5000)
  173. }
  174. })
  175. addHandler(/^https?:\/\/(picbaron\.com|imgbaron\.com|kvador\.com|kropic\.com|imgsto\.com|imgsen\.com|imgstar\.eu|picdollar\.com|pics4you\.net|silverpic\.com|fotokiz\.com|premalo\.com|piczhq\.com|trypicz\.com|imglord\.com)(\/.+)+(\.[\w\-]+)+/, callback => {
  176. const img = document.querySelector('.pic')
  177. if(img) {
  178. callback(img.src)
  179. } else {
  180. const form = document.querySelector('form')
  181. form && form.submit()
  182. }
  183. })
  184. addHandler(/^https?:\/\/(croea\.com)(\/\w+)+/, callback => {
  185. const img = document.querySelector('.pic')
  186. if(img) {
  187. callback(img.src)
  188. } else {
  189. const form = document.querySelector('form')
  190. form && form.submit()
  191. }
  192. }, async (url, callback) => {
  193. const src = await doGet(url, /src="(.*)" class="pic img img-responsive"/)
  194. if(src) {
  195. GM_xmlhttpRequest({
  196. method: 'GET',
  197. responseType: "blob",
  198. url: src,
  199. onload: res => {
  200. const reader = new FileReader()
  201. reader.onload = () => {
  202. callback(reader.result)
  203. }
  204. reader.readAsDataURL(res.response)
  205. }
  206. })
  207. }
  208. })
  209. addHandler(/^https?:\/\/(imgtaxi\.com|imgadult\.com|imgdrive\.net)(\/\w+)+/, callback => {
  210. unsafeWindow.ctipops = []
  211. unsafeWindow.adbctipops = []
  212. const img = document.querySelector('img.centred') || document.querySelector('img.centred_resized')
  213. if(img) {
  214. callback(img.src)
  215. } else {
  216. unsafeWindow.setTimeout(() => {
  217. const btn = document.querySelector('.overlay_ad_link')
  218. if(btn) {
  219. btn.focus()
  220. btn.click()
  221. }
  222. }, 1000)
  223. }
  224. }, async (url, callback) => {
  225. callback(await doGet(url, /og:image:secure_url" content="(.*)"/))
  226. })
  227. addHandler(/^https?:\/\/(xxxwebdlxxx\.org|xxxwebdlxxx\.top)(\/\w+)+/, callback => {
  228. unsafeWindow.ctipops = []
  229. unsafeWindow.adbctipops = []
  230. const img = document.querySelector('img.centred') || document.querySelector('img.centred_resized')
  231. if(img) {
  232. callback(img.src)
  233. } else {
  234. unsafeWindow.setTimeout(() => {
  235. const btn = document.querySelector('.overlay_ad_link')
  236. if(btn) {
  237. btn.focus()
  238. btn.click()
  239. }
  240. }, 1000)
  241. }
  242. })
  243. addHandler(/^https?:\/\/(uvonahaze\.xyz|trans\.firm\.in||imgdawgknuttz\.com)(\/\w+)+/, callback => {
  244. const img = document.querySelector('img.centred') || document.querySelector('img.centred_resized')
  245. if(img) {
  246. callback(img.src)
  247. } else {
  248. const btn = document.querySelector('input[name=imgContinue]')
  249. btn && btn.click()
  250. }
  251. })
  252. addHandler(/^https?:\/\/(imagetwist\.netlify\.app)(\/\w+)+/, async callback => {
  253. const redirect = await doGet(document.location.href, /<center><a href="(https?:\/\/imagetwist\.com(\/[\w-]+)+\.jpg)"/)
  254. document.location.href = redirect + '#' + document.location.href
  255. })
  256. addHandler(/^https?:\/\/(imagetwist\.com|imagexport\.com|imagenimage\.com)(\/[\w-]+)+\.jpg#https?:\/\/(imagetwist\.netlify\.app)(\/[\w-]+)+\.jpg/, callback => {
  257. const img = document.querySelector('.img-responsive')
  258. if(img) {
  259. const referer = document.location.href.split('#')[1]
  260. callback(img.src, referer)
  261. }
  262. })
  263. addHandler(/^https?:\/\/(imagetwist\.com|imagexport\.com|imagenimage\.com)(\/[\w-]+)+\.jpg$/, callback => {
  264. const img = document.querySelector('.img-responsive')
  265. if(img) {
  266. callback(img.src)
  267. }
  268. })
  269. addHandler(/^https?:\/\/hentai4free\.net(\/\w+)+/, callback => {
  270. unsafeWindow.wuLu && unsafeWindow.wuLu()
  271. const img = document.querySelector('#image-viewer-container>img')
  272. if(img) {
  273. callback(img.src)
  274. }
  275. })
  276. addHandler(/^https?:\/\/pixhost\.to(\/\w+)+/, callback => {
  277. const img = document.querySelector('img.image-img')
  278. if(img) {
  279. callback(img.src)
  280. } else {
  281. const btn = document.querySelector('a.continue')
  282. btn && btn.click()
  283. }
  284. })
  285. addHandler(/^https?:\/\/(imgair\.net|imgfrost\.net|imgblaze\.net)(\/\w+)$/, callback => {
  286. unsafeWindow.wuLu && unsafeWindow.wuLu()
  287. const img = document.querySelector('#newImgE')
  288. if(img) {
  289. callback(img.src)
  290. }
  291. }, (url, callback) => {
  292. GM_xmlhttpRequest({
  293. method: 'GET',
  294. url: url.replace(/^https?:\/\/(imgfrost\.net|imgblaze\.net)/, 'https://imgair.net'),
  295. onload: res => {
  296. const txt = res.responseText
  297. let title = txt.match(/<title>(.*)<\/title>/)
  298. if(!title) return false
  299. title = title[1].trim()
  300. const badChar = /[\[\(\$\^\.\]\*\\\?\+\{\}\\|\)]/gi
  301. const pattern = new RegExp("(http.*" + title.replace(badChar, (c) => `\\${c}`) + ")")
  302. const src = txt.match(pattern)
  303. if(src.length > 0) callback(src[0])
  304. }
  305. })
  306. })
  307. addHandler(/^https?:\/\/(pig69\.com|ai18\.pics|porn4f\.com|javball\.com|ovabee\.com|idol69\.net|cnpics\.org|cnxx\.me|cosplay18\.pics|kin8\-av\.com|555fap\.com)(\/\w+)+/, callback => {
  308. const img = document.querySelector('#fileOriginalModal img')
  309. if(img) {
  310. callback(img.src)
  311. } else {
  312. const btn = document.querySelector('a.continue')
  313. btn && btn.click()
  314. }
  315. }, (url, callback) => {
  316. GM_xmlhttpRequest({
  317. method: 'GET',
  318. url: url,
  319. onload: res => {
  320. const src = res.responseText.match(/"https?:\/\/((pig69\.com|ai18\.pics|porn4f\.com|javball\.com|ovabee\.com|idol69\.net|cnpics\.org|cnxx\.me|cosplay18\.pics|kin8\-av\.com|555fap\.com)\/upload\/Application\/storage\/app\/public\/uploads\/users\/.*)"/)
  321. if(src.length > 1) callback("http://"+src[1])
  322. }
  323. })
  324. })
  325. addHandler(/^https:\/\/manko\.fun\|/, callback => {
  326. return false
  327. }, (url, callback) => {
  328. if(/^https:\/\/sukebei\.nyaa\.si\/(\?.*)?$/.test(href)) {
  329. callback(url.substr(18))
  330. }
  331. })
  332.  
  333. const href = document.location.href
  334. if(/^https?:\/\/(sukebei\.nyaa\.si).+/g.test(href)) {
  335.  
  336. let LMT_Wrap, LMT_Frame, LMT_Loading, LMT_panel, LMT_img
  337. const panelWidth = 480
  338. const panelHeight = 480
  339. function createWrap(parent) {
  340. parent.parentNode.insertAdjacentHTML('afterend', '<div class="panel panel-default"><div class="panel-body" id="LMT_Wrap"></div></div>')
  341. LMT_Wrap = document.querySelector('#LMT_Wrap')
  342.  
  343. LMT_Loading = document.createElement('div')
  344. LMT_Loading.innerText = 'Loading Images...'
  345. LMT_Wrap.appendChild(LMT_Loading)
  346. }
  347. function createPanel() {
  348. LMT_panel = document.createElement('div')
  349. LMT_panel.style.position = 'fixed'
  350. LMT_panel.style.top = '-1000px'
  351. LMT_panel.style.left = '-1000px'
  352. LMT_panel.style.backgroundColor = '#f5f5f5'
  353. LMT_panel.style.backgroundSize = 'contain'
  354. LMT_panel.style.backgroundRepeat = 'no-repeat'
  355. LMT_panel.style.backgroundPosition = 'center'
  356. LMT_panel.style.border = '1px solid #ddd'
  357. LMT_panel.style.borderRadius = '6px'
  358. LMT_panel.style.boxShadow = '0 1px 1px rgba(0,0,0,.05)'
  359. LMT_panel.style.width = `${panelWidth}px`
  360. LMT_panel.style.height = `${panelHeight}px`
  361. document.body.appendChild(LMT_panel)
  362.  
  363. LMT_img = document.createElement('img')
  364. LMT_img.style.width = '100%'
  365. LMT_img.style.height = '100%'
  366. LMT_img.style.objectFit = 'contain'
  367. LMT_img.onerror = (e) => {
  368. LMT_panel.style.top = '-1000px'
  369. LMT_panel.style.left = '-1000px'
  370. LMT_img.style.display = 'none'
  371. const a = document.querySelector(`a[data-lmt="${decodeURI(e.target.src)}"]`)
  372. if(a && a.dataset.lmtSrc) {
  373. // extract bad url locally, stop retry
  374. delete a.dataset.lmt
  375. a.dataset.lmtSrc = '#'
  376. } else if(a) {
  377. // load bad url from server, do retry
  378. delete a.dataset.lmt
  379. delete a.dataset.lmtSrc
  380. }
  381. const span = document.querySelector(`span[data-lmt="${decodeURI(e.target.src)}"]`)
  382. if(span) span.remove()
  383. }
  384. LMT_panel.appendChild(LMT_img)
  385.  
  386. LMT_Frame = document.createElement('iframe')
  387. LMT_Frame.id = 'LMT_Frame'
  388. LMT_Frame.sandbox = 'allow-forms allow-scripts allow-same-origin'
  389. LMT_Frame.style.display = 'none'
  390. document.body.appendChild(LMT_Frame)
  391. }
  392.  
  393. const imgList = []
  394. function addToImgQueue(q) {
  395. if(imgList.filter(a=>a.href===q.href).length === 0) imgList.push(q)
  396. }
  397. let running = false
  398. let timeoutCounter = 0
  399. const imgListConsumer = setInterval(() => {
  400. if(timeoutCounter > 500) {
  401. running = false
  402. timeoutCounter = 0
  403. }
  404. timeoutCounter++
  405. if(running) return
  406. timeoutCounter = 0
  407. if(imgList.length) {
  408. let url = imgList.shift()
  409. url.handler.handleNyaa(url.href)
  410. running = true
  411. } else if(LMT_Loading) {
  412. LMT_Loading.innerText = ''
  413. }
  414. }, 10)
  415. function process() {
  416. if(imgList.length) {
  417. let url = imgList.shift()
  418. url.handler.handleNyaa(url.href)
  419. } else {
  420. if(LMT_Wrap) {
  421. LMT_Loading.innerText = ''
  422. LMT_Frame.remove()
  423.  
  424. if(!Array.apply(null, document.querySelectorAll('#LMT_Wrap > img')).length) {
  425. document.querySelector('.panel:has(#LMT_Wrap)').style.display = 'none'
  426. }
  427. } else {
  428. LMT_Frame.src = 'about:blank'
  429. }
  430. }
  431. }
  432.  
  433. unsafeWindow.addEventListener('message', function (e) {
  434. if(!e.data.LMT) return false
  435. if(LMT_Wrap) {
  436. LMT_Frame.src = ''
  437. const img = document.createElement('img')
  438. img.src = e.data.LMT
  439. img.style['max-width'] = '100%'
  440. LMT_Wrap.appendChild(img)
  441. }
  442. if(e.data.LMT_SRC) {
  443. const url_src = e.data.LMT_SRC.toLowerCase()
  444. const a = document.querySelector(`a[data-lmt-src="${decodeURI(url_src)}"]`)
  445. if(a && !a.dataset.lmt) {
  446. a.dataset.lmt = e.data.LMT
  447. const span = document.createElement("span");
  448. span.innerHTML='🖼️'
  449. span.style.cursor = 'pointer'
  450. span.dataset.lmt = e.data.LMT
  451. a.before(span)
  452.  
  453. if(/^https?:\/\/(sukebei\.nyaa\.si\/view\/).+/g.test(a.href)) {
  454. const id = a.href.substr(a.href.lastIndexOf('/')+1)
  455. saveThumb(id, e.data.LMT)
  456. }
  457. }
  458. }
  459. // process()
  460. running = false
  461. })
  462.  
  463. let windowWidth = unsafeWindow.innerWidth
  464. let windowHeight = unsafeWindow.innerHeight
  465. unsafeWindow.addEventListener('resize', function (e) {
  466. windowWidth = unsafeWindow.innerWidth
  467. windowHeight = unsafeWindow.innerHeight
  468. })
  469. unsafeWindow.addEventListener('mouseover', function (e) {
  470. const a = e.target
  471. if(a.dataset.lmt) {
  472. LMT_panel.style.backgroundImage = `url('${a.dataset.lmt}')`
  473. LMT_img.src = a.dataset.lmt
  474. LMT_img.style.display = 'block'
  475. }
  476. })
  477. unsafeWindow.addEventListener('mousemove', function (e) {
  478. const a = e.target
  479. if(LMT_img.style.display == 'block') {
  480. const offset = 10
  481. if(e.clientX + offset + panelWidth < windowWidth) {
  482. LMT_panel.style.left = e.clientX + offset + 'px'
  483. } else {
  484. LMT_panel.style.left = e.clientX - offset - panelWidth + 'px'
  485. }
  486. if(e.clientY + offset + panelHeight < windowHeight) {
  487. LMT_panel.style.top = e.clientY + offset + 'px'
  488. } else {
  489. LMT_panel.style.top = e.clientY - offset - panelHeight + 'px'
  490. }
  491. }
  492. })
  493. unsafeWindow.addEventListener('mouseout', function (e) {
  494. const a = e.target
  495. if(a.dataset.lmt) {
  496. LMT_panel.style.top = '-1000px'
  497. LMT_panel.style.left = '-1000px'
  498. LMT_img.style.display = 'none'
  499. }
  500. })
  501.  
  502. const CLOUD_URL = 'https://oc1.bigsm.art'
  503. const getThumbs = ids => {
  504. return new Promise((resolve, reject) => {
  505. GM_xmlhttpRequest({
  506. method: 'GET',
  507. url: `${CLOUD_URL}/thumbs/?ids=${ids}`,
  508. onload: res => { resolve(JSON.parse(res.responseText)) },
  509. onError: err => { resolve([]) }
  510. })
  511. })
  512. }
  513. const saveThumb = (id, thumb) => {
  514. GM_xmlhttpRequest({
  515. method: 'POST',
  516. url: `${CLOUD_URL}/thumb/${id}`,
  517. data: `{"url": "${thumb}"}`,
  518. headers: {
  519. "Content-Type": "application/json"
  520. }
  521. })
  522. }
  523.  
  524. if(/^https?:\/\/(sukebei\.nyaa\.si\/view\/).+/g.test(href)) {
  525. // Detail Page
  526. if(document.title === '429 Too Many Requests') {
  527. document.location.href = document.location.href
  528. return
  529. }
  530. const desc = document.querySelector('#torrent-description')
  531. const links = desc.querySelectorAll('a')
  532. if(!desc || !links) return
  533.  
  534. for(let i = 0; i < links.length; i++) {
  535. if(!links[i].href) continue
  536. links[i].dataset.lmtSrc = decodeURI(links[i].href)
  537. handlers.forEach(h => {
  538. if(h.canHandle(links[i].href)) {
  539. links[i].dataset.lmtSrc = links[i].href.toLowerCase()
  540. addToImgQueue({href:links[i].href,handler:h})
  541. }
  542. })
  543. }
  544.  
  545. if(!LMT_Frame) {
  546. createWrap(desc)
  547. }
  548. createPanel()
  549. // process()
  550. } else {
  551. // List Page
  552. // try to load thumb from cloud
  553. const links = document.querySelectorAll('.torrent-list>tbody>tr>td:nth-child(2)>a:last-child')
  554. const ids = Array.apply(null, links).map(a => a.href.substr(a.href.lastIndexOf('/')+1)).join(',')
  555. getThumbs(ids).then(thumbs => {
  556. for(let i in thumbs) {
  557. if(!thumbs[i]) continue
  558. links[i].dataset.lmt = thumbs[i]
  559. const span = document.createElement("span");
  560. span.innerHTML='🖼️'
  561. span.style.cursor = 'pointer'
  562. span.dataset.lmt = thumbs[i]
  563. links[i].before(span)
  564. }
  565. })
  566.  
  567. document.querySelector('.torrent-list').addEventListener('mouseover', async (e) => {
  568. const a = e.target
  569. if(a.dataset.lmt || a.dataset.lmtSrc || !a.href || !/.*\/view\/\d+$/.test(a.href)) return
  570.  
  571. // Serialize requests, to avoid '429 Too Many Requests'
  572. const unlock = await lock()
  573. const detail = await getDetail(a.href)
  574. unlock()
  575. let desc = detail.responseText.match(/id="torrent-description">(.*?)<\/div>/)
  576. if(!desc) return false
  577. desc = desc[1].replaceAll('&#10;', '\n').replaceAll(')]', ' )]').replaceAll('\*\*', ' \*\* ')
  578. let hrefs = desc.match(/(https?:\/\/[^\s\)]+)/g) || []
  579. let info = detail.responseText.match(/noopener noreferrer nofollow" href="(https?:\/\/.+?)"/)
  580. if(info) hrefs = [...hrefs, info[1]]
  581. console.log(hrefs)
  582.  
  583. let flag = false
  584. for (let i in hrefs) {
  585. let href = hrefs[i]
  586. // for links '[![Visit manko.fun](https://...)](https://manko.fun)'
  587. if (href.trim() === 'https://manko.fun') {
  588. href = desc.match(/(https?:\/\/.+?)\)\]\(https:\/\/manko\.fun\)/)
  589. if(href) {
  590. href = 'https://manko.fun|' + href[1].trim()
  591. } else {
  592. continue
  593. }
  594. }
  595.  
  596. for(let j in handlers) {
  597. const h = handlers[j]
  598. if(href.indexOf('nyaa') < 0 && h.canHandle(href)) {
  599. a.dataset.lmtSrc = href.toLowerCase()
  600. addToImgQueue({href:href,handler:h})
  601. flag = true
  602. break
  603. }
  604. }
  605. if(flag) {
  606. // process()
  607. break
  608. }
  609. }
  610. if(!flag) {
  611. a.dataset.lmtSrc = '#'
  612. }
  613. })
  614. createPanel()
  615.  
  616. let isLock = false;
  617. let lockList = [];
  618. async function lock() {
  619. function unlock() {
  620. let waitFunc = lockList.shift();
  621. if (waitFunc) {
  622. waitFunc.resolve(unlock);
  623. } else {
  624. isLock = false;
  625. }
  626. }
  627. if (isLock) {
  628. return new Promise((resolve, reject) => {
  629. lockList.push({ resolve, reject });
  630. });
  631. } else {
  632. isLock = true;
  633. return unlock;
  634. }
  635. }
  636. const getDetail = url => {
  637. return new Promise((resolve, reject) => {
  638. GM_xmlhttpRequest({
  639. method: 'GET',
  640. url: url,
  641. onload: res => { resolve(res) },
  642. onError: err => { reject(err) }
  643. })
  644. })
  645. }
  646. }
  647. } else {
  648. // Image Host Websites
  649. handlers.forEach(h=>{h.canHandle(href) && h.handle(document.location.href)})
  650. }
  651. })();