Greasy Fork

NGA 图片浏览器

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

目前为 2021-03-11 提交的版本。查看 最新版本

// ==UserScript==
// @name         NGA 图片浏览器
// @namespace    https://greasyfork.org/zh-CN/users/164691-shy07
// @version      0.04
// @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==

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

  if (n === undefined) return

  const targetNode = document.querySelector('body')
  const config = {
    childList: true
  }
  let imageSources = []
  let currentImage = 0
  const callerId = '_shy07_gallery_caller'
  const containerClass = '_shy07_gallery_container'
  const imgClass = '_shy07_gallery_img'
  const galleryContainerStyle = `
    display: block;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    position: fixed;
    background: rgba(0, 0, 0, 0.9);
`
  const galleryImgStyle = `
    display: block;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background-repeat: no-repeat;
    background-size: contain;
    background-position: center;
  `
  const arrowStyle = `
    display: block;
    position: fixed;
    top: 0;
    line-height: 100vh;
    color: #fff;
    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: 1rem 2rem;
    color: #fff;
    text-decoration-line: none;
  `
  const topLeftMenuStyle = `
    position: fixed;
    top: 0;
    left: 0;
    padding: 1rem 2rem;
  `
  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 = src => {
    const img = document.querySelector('.' + imgClass)
    if (img) img.style.backgroundImage = `url(${src})`
  }
  const prevImage = () => {
    currentImage = currentImage === 0 ? imageSources.length - 1 : currentImage - 1
    setImageSrc(imageSources[currentImage])
  }
  const nextImage = () => {
    currentImage = currentImage === imageSources.length - 1 ? 0 : currentImage + 1
    setImageSrc(imageSources[currentImage])
  }
  const handleKeydown = ev => {
    const code = ev.keyCode
    if (code === 37) {
      prevImage()
    } else if (code === 39) {
      nextImage()
    }
  }

  const ajaxDownload = (url, callback, args, tryTimes) => {
    tryTimes = 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, 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 fileNameFromHeader = (disposition, url) => {
    if (disposition && /filename=.*/ig.test(disposition)) {
      const result = disposition.match(/filename=.*/ig)
      return decodeURI(result[0].split("=")[1])
    }
    return url.substring(url.lastIndexOf('/') + 1)
  }

  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 downloadUrlFile = (url, fileName) => {
    const aLink = document.createElement('a')
    if (fileName) {
      aLink.download = fileName
    } else {
      aLink.download = url.substring(url.lastIndexOf('/') + 1)
    }
    aLink.target = '_blank'
    aLink.style = 'display:none;'
    aLink.href = url
    document.body.appendChild(aLink)
    if (document.all) {
      aLink.click() //IE
    } else {
      const evt = document.createEvent('MouseEvents')
      evt.initEvent('click', true, true)
      aLink.dispatchEvent(evt) // 其它浏览器
    }
    document.body.removeChild(aLink)
  }

  const downloadImage = () => {
    const url = imageSources[currentImage]
    const tmp = url.split('/')
    const filename = tmp[tmp.length - 1]
    ajaxDownload(url, downloadBlobFile, filename)
  }

  const downloadAllImage = (blob = null, { list, filename } = {}) => {
    if (blob && filename) downloadBlobFile(blob, filename)
    const [first, ...newList] = list || imageSources
    const tmp = first.split('/')
    const f = tmp[tmp.length - 1]
    ajaxDownload(first, downloadAllImage, { list: newList, filename: f })
  }

  const openInNewTab = () => {
    window.open(imageSources[currentImage], '_blank')
  }

  const fixedMMC = value => {
    const ele = document.querySelector('#mmc')
    if (ele) ele.style.position =  value ? 'fixed' : 'inherit'
  }

  const createGalleryImage = () => {
    currentImage = 0
    const img = document.createElement('div')
    img.className = imgClass
    img.style = galleryImgStyle
    img.style.backgroundImage = `url(${imageSources[0]})`
    return img
  }
  const createLeftArrow = () => {
    const ele = document.createElement('a')
    ele.style = arrowStyle + leftArrowStyle
    ele.innerHTML = '<'
    ele.href = 'javascript:void(0)'
    ele.addEventListener('click', prevImage)
    return ele
  }
  const createRightArrow = () => {
    const ele = document.createElement('a')
    ele.style = arrowStyle + rightArrowStyle
    ele.innerHTML = '>'
    ele.href = 'javascript:void(0)'
    ele.addEventListener('click', nextImage)
    return ele
  }
  const createCloseBtn = () => {
    const ele = document.createElement('a')
    ele.style = closeBtnStyle
    ele.innerHTML = '关闭'
    ele.href = 'javascript:void(0)'
    ele.addEventListener('click', hideGallery)
    return ele
  }
  const createTopLeftMenu = () => {
    const ele = document.createElement('div')
    ele.style = topLeftMenuStyle
    const downloadBtn = document.createElement('a')
    downloadBtn.style= topLeftMenuItemStyle
    downloadBtn.innerHTML = '下载'
    downloadBtn.href = 'javascript:void(0)'
    downloadBtn.addEventListener('click', downloadImage)
    const downloadAllBtn = document.createElement('a')
    downloadAllBtn.style= topLeftMenuItemStyle
    downloadAllBtn.innerHTML = '全部下载'
    downloadAllBtn.href = 'javascript:void(0)'
    downloadAllBtn.addEventListener('click', downloadAllImage)
    const newTabBtn = document.createElement('a')
    newTabBtn.style= topLeftMenuItemStyle
    newTabBtn.innerHTML = '新页面打开'
    newTabBtn.href = 'javascript:void(0)'
    newTabBtn.addEventListener('click', openInNewTab)
    ele.appendChild(downloadBtn)
    // ele.appendChild(downloadAllBtn)
    ele.appendChild(newTabBtn)
    return ele
  }

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

  const collectImages = container => {
    showCollapseContent(container)
    imageSources = []
    const imgs = container.querySelectorAll('img')
    const temp = []
    imgs.forEach(img => {
      const src = img.src
      const lazySrc = img.dataset ? img.dataset.srclazy : ''
      if (lazySrc) {
        imageSources.push(lazySrc)
        return
      }
      if (src.includes('/attachments/')) {
        const arr = img.src.replace(/https:/g, 'http:').split('http:')
        const src = arr.filter(s => !!s)
        imageSources.push(src[0])
      }
    })
  }

  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:void(0)'
    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 = container => {
    const checkExist = document.querySelector('#' + callerId + container.id)
    if (!checkExist) {
      container.appendChild(callerButton(container))
    }
  }

  const createCallerButtons = () => {
    const elements = document.querySelectorAll('.postInfo')
    if (elements && elements.length > 0) {
      elements.forEach(createCallerButton)
    }
  }

  const observer = new MutationObserver((mutationsList, observer) => {
    createCallerButtons()
  })
  observer.observe(targetNode, config)
  console.log('hello')

})(commonui, __CURRENT_UID)