PTT web image enhanced

Enhance PTT web image load performance

目前為 2021-10-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name         PTT web image enhanced
// @namespace    2CF9973A-28C9-11EC-9EA6-98F49F6E8EAB
// @version      2.2
// @description  Enhance PTT web image load performance
// @author       Rick0
// @match        https://www.ptt.cc/bbs/*/*.html*
// @match        https://www.ptt.cc/ask/over18?*
// @grant        GM.xmlHttpRequest
// @connect      imgur.com
// ==/UserScript==

(function() {
  'use strict'

  // == independent methods ==

  function createElement(html) {
    let template = document.createElement('template')
    template.innerHTML = html
    
    return template.content.firstChild
  }

  function getHeaders (url, headers = {}) {
    return new Promise((resolve, reject) => {
      GM.xmlHttpRequest({
        url,
        method: 'HEAD',
        headers,
        onload: function (res) {
          if ([200, 304].includes(res.status)) {
            let headers = res.responseHeaders
              .split(/\r?\n/)
              .slice(0, -1)
              .reduce((result, item) => {
                let [, key, val] = item.match(/(.+?): +(.+)/)
                result[key] = val
                return result
              }, {})
            resolve(headers)
          } else {
            reject(res)
          }
        },
        onerror: function (err) {
          reject(err)
        },
      })
    })
  }

  function insertElementToTextNode (textNode, element) {
    if (textNode.nodeType === 3) {
      textNode.parentNode.replaceChild(element, textNode)
      let textMatchList = textNode.data.match(/^([^\n]*)\n?(.*)$/s)
      if (textMatchList[1] !== undefined) element.insertAdjacentText('beforebegin', textMatchList[1])
      if (textMatchList[2] !== undefined) element.insertAdjacentText('afterend', textMatchList[2])
    } else {
      textNode.insertAdjacentElement('beforebegin', element)
    }
  }

  function getTypeExt (type) {
    try {
      let ext = type.match(/[^\/]+?\/(.+)/)[1]
      switch (ext) {
        case 'jpeg': {
          ext = 'jpg'
          break
        }
      }
      return ext
    } catch (e) {
      return
    }
  }

  function getImgurInfo (originalUrl) {
    return new Promise((resolve, reject) => {
      let imgurInfo = {
        originalUrl,
        id: undefined,
        ext: undefined,
        get imgurUrl () {
          return [this.id, this.ext].includes(undefined) ? undefined : `https://i.imgur.com/${this.id}.${this.ext}`
        },
        get embedUrl () {
          let quality = this.ext !== 'gif' ? 'h' : ''
          let ext = this.ext !== 'gif' ? 'jpg' : 'mp4'
          return [this.id, this.ext].includes(undefined) ? undefined : `https://i.imgur.com/${this.id}${quality}.${ext}`
        },
      }
      let infoUrl
      let infoHeaders = {
        referer: 'https://imgur.com/',
      }
  
      let link = new URL(originalUrl)
      // URL 的 pathname 最少會有 / ,所以利用正則來去頭尾 / 後切割,最後面的 / 的後面如果沒有值不會被列入
      let pathList = link.pathname !== '/' ? link.pathname.match(/^\/(.*?)\/?$/)[1].split('/') : []
      // let imgurIdExtRegExp = /^(\w{7})\w?(?:\.(\w+))?$/
      let imgurIdRegExp = /^\w{7}/
  
      // 取得 id
      switch (pathList.length) {
        // 按照 pathname 的層數來分類處理
        // 只有一層,只可能是 id / id.ext 的格式
        case 1: {
          let idMatchList = pathList[0].match(imgurIdRegExp)
          if (idMatchList !== null) {
            imgurInfo.id = idMatchList[0]
            infoUrl = `https://i.imgur.com/${imgurInfo.id}.jpg`
          } else {
            reject(imgurInfo)
            retrun
          }
          break
        }

        default:
          reject(imgurInfo)
          retrun
      }

      // 取得 imgurInfo.ext
      getHeaders(infoUrl, infoHeaders)
        .then(headers => {
          imgurInfo.ext = getTypeExt(headers['content-type'])
          resolve(imgurInfo)
        })
        .catch(err => {
          reject(imgurInfo)
        })
    })
  }

  // == dependent methods ==

  function pttImageEnhanced () {
    function embedImg (href, richcontent) {
      getImgurInfo(href)
        .then(imgurInfo => {
          switch (imgurInfo.ext) {
            case 'gif': {
              richcontent.innerHTML = `<video data-src="${imgurInfo.embedUrl}" autoplay loop muted style="max-width: 100%;max-height: 800px;"></video>`
              videoLazyObserver.observe(richcontent.querySelector(':scope > video'))
              break
            }
      
            default: {
              richcontent.innerHTML = `<img src="${imgurInfo.embedUrl}" alt loading="lazy">`
            }
          }
        })
        .catch(err => err)
    }

    // == 取消所有 ptt web 原生的 imgur 圖片載入 ==
    for (let img of document.querySelectorAll('.richcontent > img[src*="imgur.com"]')) {
      img.src = ''
    }

    // == 取消外連資源的 referrer ==
    document.head.appendChild(createElement('<meta name="referrer" content="no-referrer">'))

    // == 建立 video lazy observer ==
    let onEnterView = function (entries, observer) {
      for (let entry of entries) {
        if (entry.isIntersecting) {
          // 目標進入畫面
          let video = entry.target
          video.src = video.dataset.src
          video.removeAttribute('data-src')
          observer.unobserve(video)
        }
      }
    }
    let options = {
      rootMargin: '50%',
    }
    let videoLazyObserver = new IntersectionObserver(onEnterView, options)

    // == 處理內文的部分 ==
    for (let a of document.querySelectorAll('.bbs-screen.bbs-content > a[href*="imgur.com"]')) {
      // 當不存在原生展開的圖片元素時,自行建立
      if (!a.nextElementSibling.classList.contains('richcontent')) {
        insertElementToTextNode(a.nextSibling, createElement('<div class="richcontent"></div>'))
      }
      let richcontent = a.nextElementSibling
      embedImg(a.href, richcontent)
    }

    // == 處理推/噓文的部分 ==
    for (let a of document.querySelectorAll('.f3.push-content > a[href*="imgur.com"]')) {
      // 當不存在原生展開的圖片元素時,自行建立
      if (!a.closest('.push').nextElementSibling.classList.contains('richcontent')) {
        insertElementToTextNode(a.closest('.push').nextSibling, createElement('<div class="richcontent"></div>'))
      }
      let richcontent = a.closest('.push').nextElementSibling
      embedImg(a.href, richcontent)
    }
  }

  function agreeOver18 () {
    document.cookie = `over18=1;path=/;expires=${(new Date(2100, 0)).toUTCString()};`
    location.replace(`https://www.ptt.cc/${decodeURIComponent(location.search.replace(/\?from=([^&]+)/, '$1'))}`) 
  }
  
  switch (location.hostname) {
    case 'www.ptt.cc': {
      switch (location.pathname) {
        case '/ask/over18': {
          agreeOver18()
        }
        break

        default: {
          pttImageEnhanced()
        }
      }
    }
    break
  }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址