Nyaa.si - Load More Thumbnail

Load image from cover/screenshot links.

La data de 06-04-2024. Vezi ultima versiune.

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