Enhance PTT web image load performance
当前为
// ==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
}
})()