Greasy Fork

Greasy Fork is available in English.

NGA 图片浏览器

收集指定楼层的图片,改善图片浏览体验,并支持批量下载

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         NGA 图片浏览器
// @namespace    http://greasyfork.icu/zh-CN/users/164691-shy07
// @version      1.91
// @description  收集指定楼层的图片,改善图片浏览体验,并支持批量下载
// @author       Shy07
// @match        *://nga.178.com/*
// @match        *://bbs.ngacn.cc/*
// @match        *://bbs.nga.cn/*
// @match        *://ngabbs.com/*
// @grant        GM.xmlHttpRequest
// @grant        GM_xmlHttpRequest
// jshint        esversion:6
// ==/UserScript==

((ui, self) => {
  'use strict'

  if (ui === undefined) return

  let imageSources = []
  let currentImage = 0
  let napTimer = null
  let zoomRate = 1
  let rotateDeg = 0

  const callerId = '_shy07_gallery_caller'
  const containerClass = '_shy07_gallery_container'
  const imgClass = '_shy07_gallery_img'
  const progressID = '_shy07_progress_id'
  const topLeftMenuID = '_shy07_top_left_menu'
  const leftArrowID = '_shy07_left_arrow'
  const rightArrowID = '_shy07_right_arrow'
  const closeBtnID = '_shy07_close_btn'
  const galleryContainerStyle = `
    display: flex;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
  `
  const galleryMaskStyle = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: rgba(0, 0, 0, 0.9);
    z-index: -1;
  `
  const galleryImgStyle = `
    top: 0;
    left: 0;
    width: auto;
    height: 100vh;
  `
  const arrowStyle = `
    display: block;
    position: fixed;
    top: 0;
    line-height: 100vh;
    color: rgba(255, 255, 255, 0.6);
    font-size: 5rem;
    text-decoration-line: none;
  `
  const leftArrowStyle = `
    left: 0;
    padding-left: 1rem;
  `
  const rightArrowStyle = `
    right: 0;
    padding-right: 1rem;
  `
  const closeBtnStyle = `
    display: text-block;
    position: fixed;
    top: 0;
    right: 0;
    padding: .5rem 1rem;
    color: #fff;
    text-decoration-line: none;
  `
  const topLeftMenuStyle = `
    display: flex;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    padding: .5rem 1rem;
    background: rgba(0, 0, 0, 0.3);
    transition: all .4s linear;
  `
  const topLeftMenuItemStyle = `
    display: text-block;
    margin-right: 1rem;
    color: #fff;
    text-decoration-line: none;
  `

  const showCollapseContent = container => {
    const elements = container.querySelectorAll('.collapse_btn')
    if (elements && elements.length > 0) {
      elements.forEach(ele => {
        const btn = ele.querySelector('button')
        btn && btn.click()
      })
    }
  }

  const setImageSrc = (index, ele = null) => {
    const progress = document.querySelector('#' + progressID)
    if (progress) progress.innerHTML = `${index + 1}/${imageSources.length}`
    const src = imageSources[index]
    const img = ele || document.querySelector('.' + imgClass)
    if (img) {
      img.src = src
      img.style.transform = `scale(.75) rotate(0deg)`
    }
    zoomRate = .75
    rotateDeg = 0
    wakeAll()
    if (napTimer) clearTimeout(napTimer)
    napTimer = setTimeout(() => {
      napAll()
      zoomIn(1)
    }, 1000 * 5)
  }
  const prevImage = ev => {
    currentImage = currentImage === 0 ? imageSources.length - 1 : currentImage - 1
    setImageSrc(currentImage)
    ev.stopPropagation()
  }
  const nextImage = ev => {
    currentImage = currentImage === imageSources.length - 1 ? 0 : currentImage + 1
    setImageSrc(currentImage)
    ev.stopPropagation()
  }
  const handleKeydown = ev => {
    const code = ev.keyCode
    if (code === 27 || code === 32) {
      hideGallery()
      ev.preventDefault()
    } else if (code === 37) {
      prevImage()
      ev.preventDefault()
    } else if (code === 38) {
      zoomIn()
      ev.preventDefault()
    } else if (code === 39) {
      nextImage()
      ev.preventDefault()
    } else if (code === 40) {
      zoomOut()
      ev.preventDefault()
    }
  }

  function throttle (final, delay, time) {
    let timeout = null
    let startTime = new Date()
    let skip = false
    return e => {
      e.preventDefault()
      e.stopPropagation()
      let curTime = new Date()
      clearTimeout(timeout)
      if (curTime - startTime >= time) {
        if (!skip) {
          final(e)
          setTimeout(() => (skip = false), delay)
        }
        skip = true
        startTime = curTime
      } else {
        !skip && (timeout = setTimeout(() => final(e), time))
      }
    }
  }

  const rotateImage = ev => {
    ev.stopPropagation()
    const img = document.querySelector('.' + imgClass)
    if (img) {
      rotateDeg += 90
      img.style.transform = `scale(${zoomRate}) rotate(${rotateDeg}deg)`
    }
  }
  const zoomIn = (rate = null) => {
    const img = document.querySelector('.' + imgClass)
    if (img) {
      zoomRate = zoomRate === 5 ? zoomRate : zoomRate + .25
      if (rate) zoomRate = rate
      img.style.transform = `scale(${zoomRate}) rotate(${rotateDeg}deg)`
    }
  }
  const zoomOut = () => {
    const img = document.querySelector('.' + imgClass)
    if (img) {
      zoomRate = zoomRate === .25 ? zoomRate : zoomRate - .25
      img.style.transform = `scale(${zoomRate}) rotate(${rotateDeg}deg)`
    }
  }
  const handleMousewheel = throttle(e => {
    if (e.wheelDelta) { // IE/Opera/Chrome
      if (e.wheelDelta > 0) {
        zoomIn()
      }
      if (e.wheelDelta < 0) {
        zoomOut()
      }
    } else if (e.detail) { // Firefox
      if (e.detail > 0) {
        zoomIn()
      }
      if (e.detail < 0) {
        zoomOut()
      }
    }
  }, 400, 400)

  const ajaxDownload = (url, callback, args, tryTimes = 0) => {
    const GM_download = GM.xmlHttpRequest || GM_xmlHttpRequest
    const clearUrl = url.replace(/[&\?]?download_timestamp=\d+/, '')
    const retryUrl = clearUrl + (clearUrl.indexOf('?') === -1 ? '?' : '&') + 'download_timestamp=' + new Date().getTime()
    GM_download({
      method: 'GET',
      responseType: 'blob',
      url: url,
      onreadystatechange: (responseDetails) => {
        if (responseDetails.readyState === 4) {
          if (responseDetails.status === 200 || responseDetails.status === 304 || responseDetails.status === 0) {
            const blob = responseDetails.response
            const size = blob && blob.size
            if (size && (size / 1024 >= 5)) {
              callback(blob, args)
            } else if (tryTimes++ === 3) {
              callback(blob, args)
            } else {
              ajaxDownload(retryUrl, callback, args, tryTimes)
            }
          } else {
            if (tryTimes++ === 3) {
              callback(null, args)
            } else {
              ajaxDownload(retryUrl, callback, args, tryTimes)
            }
          }
        }
      },
      onerror: (responseDetails) => {
        if (tryTimes++ === 3) {
          callback(null, args)
        } else {
          ajaxDownload(retryUrl, callback, args, tryTimes)
        }
        console.log(responseDetails.status)
      }
    })
  }

  const downloadBlobFile = (content, fileName) => {
    if ('msSaveOrOpenBlob' in navigator) {
      navigator.msSaveOrOpenBlob(content, fileName)
    } else {
      const aLink = document.createElement('a')
      aLink.download = fileName
      aLink.style = 'display:none;'
      const blob = new Blob([content])
      aLink.href = window.URL.createObjectURL(blob)
      document.body.appendChild(aLink)
      if (document.all) {
        aLink.click() //IE
      } else {
        const evt = document.createEvent('MouseEvents')
        evt.initEvent('click', true, true)
        aLink.dispatchEvent(evt) // 其它浏览器
      }
      window.URL.revokeObjectURL(aLink.href)
      document.body.removeChild(aLink)
    }
  }

  const downloadImage = ev => {
    const url = imageSources[currentImage]
    const filename = url.split('/').pop()
    ajaxDownload(url, downloadBlobFile, filename)
    ev.stopPropagation()
  }

  const downloadAllImage = (blob = null, { list, filename } = {}) => {
    if (blob && filename) downloadBlobFile(blob, filename)
    const [first, ...newList] = list || imageSources
    if (!first) return
    const f = first.split('/').pop()
    ajaxDownload(first, downloadAllImage, { list: newList, filename: f })
  }
  const handleDownloadAll = ev => {
    downloadAllImage()
    ev.stopPropagation()
  }

  const createGalleryImage = () => {
    currentImage = 0
    const img = document.createElement('img')
    img.className = imgClass
    img.style = galleryImgStyle
    img.addEventListener('wheel', handleMousewheel)
    setImageSrc(0, img)
    return img
  }
  const createLeftArrow = () => {
    const ele = document.createElement('a')
    ele.id = leftArrowID
    ele.style = arrowStyle + leftArrowStyle
    ele.innerHTML = '<'
    ele.href = 'javascript:;'
    ele.addEventListener('click', prevImage)
    ele.addEventListener('mouseover', wakeAll)
    ele.addEventListener('mouseleave', napAll)
    return ele
  }
  const createRightArrow = () => {
    const ele = document.createElement('a')
    ele.id = rightArrowID
    ele.style = arrowStyle + rightArrowStyle
    ele.innerHTML = '>'
    ele.href = 'javascript:;'
    ele.addEventListener('click', nextImage)
    ele.addEventListener('mouseover', wakeAll)
    ele.addEventListener('mouseleave', napAll)
    return ele
  }
  const createCloseBtn = () => {
    const ele = document.createElement('a')
    ele.id = closeBtnID
    ele.style = closeBtnStyle
    ele.innerHTML = '关闭'
    ele.href = 'javascript:;'
    ele.addEventListener('click', hideGallery)
    ele.addEventListener('mouseover', wakeAll)
    ele.addEventListener('mouseleave', napAll)
    return ele
  }
  const createTopLeftMenu = () => {
    const ele = document.createElement('div')
    ele.id = topLeftMenuID
    ele.style = topLeftMenuStyle
    const progress = document.createElement('span')
    progress.id = progressID
    progress.style= topLeftMenuItemStyle
    progress.innerHTML = `${currentImage + 1}/${imageSources.length}`
    const downloadBtn = document.createElement('a')
    downloadBtn.style= topLeftMenuItemStyle
    downloadBtn.innerHTML = '下载'
    downloadBtn.href = 'javascript:;'
    downloadBtn.addEventListener('click', downloadImage)
    const downloadAllBtn = document.createElement('a')
    downloadAllBtn.style= topLeftMenuItemStyle
    downloadAllBtn.innerHTML = '全部下载'
    downloadAllBtn.href = 'javascript:;'
    downloadAllBtn.addEventListener('click', handleDownloadAll)
    const clockwiseBtn = document.createElement('a')
    clockwiseBtn.style= topLeftMenuItemStyle
    clockwiseBtn.innerHTML = '旋转'
    clockwiseBtn.href = 'javascript:;'
    clockwiseBtn.addEventListener('click', rotateImage)
    ele.appendChild(progress)
    ele.appendChild(downloadBtn)
    ele.appendChild(downloadAllBtn)
    ele.appendChild(clockwiseBtn)
    ele.addEventListener('mouseover', wakeAll)
    ele.addEventListener('mouseleave', napAll)
    return ele
  }

  const napElement = id => () => {
    const ele = document.querySelector('#' + id)
    if (ele) ele.style.opacity = 0
  }
  const wakeElement = id => () => {
    const ele = document.querySelector('#' + id)
    if (ele) ele.style.opacity = 1
  }

  const napAll = () => {
    napElement(topLeftMenuID)()
    napElement(leftArrowID)()
    napElement(rightArrowID)()
    napElement(closeBtnID)()
  }
  const wakeAll = () => {
    wakeElement(topLeftMenuID)()
    wakeElement(leftArrowID)()
    wakeElement(rightArrowID)()
    wakeElement(closeBtnID)()
  }

  const showGallery = () => {
    if (!imageSources.length) {
      window.alert('这层楼没有图片  ̄□ ̄||')
      return
    }
    document.addEventListener('keydown', handleKeydown)
    const galleryMask = document.querySelector('.' + containerClass)
    if (galleryMask) {
      currentImage = 0
      setImageSrc(currentImage)
      galleryMask.style = galleryContainerStyle
    } else {
      const ele = document.createElement('div')
      ele.className = containerClass
      ele.style = galleryContainerStyle
      const mask = document.createElement('div')
      mask.style = galleryMaskStyle
      mask.addEventListener('click', hideGallery)
      const img = createGalleryImage()
      const leftArrow = createLeftArrow()
      const rightArrow = createRightArrow()
      const closeBtn = createCloseBtn()
      const topLeftMenu = createTopLeftMenu()
      ele.appendChild(mask)
      ele.appendChild(img)
      ele.appendChild(leftArrow)
      ele.appendChild(rightArrow)
      ele.appendChild(topLeftMenu)
      ele.appendChild(closeBtn)
      document.body.appendChild(ele)
    }
  }
  const hideGallery = () => {
    document.removeEventListener('keydown', handleKeydown)
    const galleryMask = document.querySelector('.' + containerClass)
    if (galleryMask) {
      galleryMask.style.display = 'none'
    }
  }

  const getExtname = url => {
    const filename = url.split('/').pop()
    const extname = filename.split('.').pop()
    return extname
  }
  const getOriginFile = srcUrl => {
    const fileExtname = getExtname(srcUrl)
    const url = srcUrl
      .replace(`.medium.${fileExtname}`, '')
      .replace(`.thumb.${fileExtname}`, '')
      .replace(`.thumb_s.${fileExtname}`, '')
      .replace(`.thumb_ss.${fileExtname}`, '')
    return url
  }
  const collectImages = container => {
    showCollapseContent(container)
    imageSources = []
    const imgs = container.querySelectorAll('.postcontent img')
    const temp = []
    imgs.forEach(img => {
      const src = img.src
      const lazySrc = img.dataset ? img.dataset.srclazy : ''
      if (lazySrc) {
        const url = getOriginFile(lazySrc)
        if (!imageSources.includes(url)) imageSources.push(url)
        return
      }
      if (src.includes('/attachments/')) {
        const arr = img.src.replace(/https:/g, 'http:').split('http:')
        const tmp = arr.filter(s => !!s)[0]
        const url = getOriginFile(tmp)
        if (!imageSources.includes(url)) imageSources.push(url)
      }
    })
  }

  const callerButton = container => {
    const a = document.createElement('a')
    const handleClick = () => {
      collectImages(container.parentElement)
      showGallery()
    }
    a.addEventListener('click', handleClick)
    a.id = callerId + container.id
    a.className = 'small_colored_text_btn block_txt_c0 stxt'
    a.href = 'javascript:;'
    a.title = '图片浏览'
    a.innerHTML = `
      <span>&nbsp;
        <span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">
          图集
        </span>&nbsp;
      </span>
    `
    return a
  }

  const createCallerButton = postData => {
    const checkExist = document.querySelector('#' + callerId + postData.i)
    if (!checkExist) {
      const container = postData.pInfoC
      container.appendChild(callerButton(container))
    }
  }

  if (ui.postArg) {
    Object.keys(ui.postArg.data).forEach(key => {
      createCallerButton(ui.postArg.data[key])
    })
  }

  // 钩子
  const hookFunction = (object, functionName, callback) => {
    ((originalFunction) => {
      object[functionName] = function () {
        const returnValue = originalFunction.apply(this, arguments)
        callback.apply(this, [returnValue, originalFunction, arguments])
        return returnValue
      }
    })(object[functionName])
  }

  let initialized = false

  hookFunction(ui, 'eval', () => {
    if (initialized) return
    if (ui.postDisp) {
      hookFunction(
        ui,
        'postDisp',
        (returnValue, originalFunction, args) => {
          createCallerButton(ui.postArg.data[args[0]])
        }
      )
      initialized = true
    }
  })

})(commonui, __CURRENT_UID)