Enhance user experience of PTT web
当前为
// ==UserScript==
// @name PTT web enhanced
// @namespace 2CF9973A-28C9-11EC-9EA6-98F49F6E8EAB
// @version 2.7
// @description Enhance user experience of PTT web
// @author Rick0
// @match https://www.ptt.cc/*
// @grant GM.xmlHttpRequest
// @connect imgur.com
// @run-at document-start
// @compatible firefox Tampermonkey, Violentmonkey
// @compatible chrome Tampermonkey, Violentmonkey
// @license Beerware
// ==/UserScript==
(function() {
'use strict'
// == independent methods ==
function createElement(html) {
let template = document.createElement('template')
template.innerHTML = html
return template.content.firstChild
}
function isImgurMp4Exist (imgurId) {
return new Promise((resolve) => {
GM.xmlHttpRequest({
url: `https://i.imgur.com/${imgurId}.mp4`,
method: 'HEAD',
headers: {
referer: 'https://imgur.com/',
},
onload: function (res) {
if ([200, 304].includes(res.status) && res.finalUrl !== 'https://i.imgur.com/removed.png') {
resolve(true)
} else {
resolve(false)
}
},
onerror: function (err) {
resolve(false)
},
})
})
}
function insertElementToNextLine (positionElement, element) {
let positionNextSibling = positionElement.nextSibling
switch (positionNextSibling?.nodeType) {
case Node.TEXT_NODE:
positionNextSibling.parentNode.replaceChild(element, positionNextSibling)
let textMatchList = positionNextSibling.data.match(/^([^\n]*)\n?(.*)$/s)
if (textMatchList[1] !== undefined) element.insertAdjacentText('beforebegin', textMatchList[1])
if (textMatchList[2] !== undefined) element.insertAdjacentText('afterend', textMatchList[2])
break
case Node.ELEMENT_NODE:
case undefined:
positionElement.insertAdjacentElement('afterend', element)
break
default:
throw new Error('insertElementToNextLine receive invalid positionElement')
}
}
function agreeOver18 () {
document.cookie = `over18=1;path=/;expires=${(new Date(2100, 0)).toUTCString()}`
location.replace(`https://www.ptt.cc/${decodeURIComponent(location.search.match(/[?&]from=([^&]+)/)[1])}`)
}
function addStyle (cssCode) {
document.head.append(createElement(cssCode))
}
function setNoReferrer () {
document.head.append(createElement('<meta name="referrer" content="no-referrer">'))
}
// == dependent methods ==
function addSearch () {
// 系列文
let title = document.querySelectorAll('.article-metaline')[1]
.querySelector('.article-meta-value')
.textContent.match(/^(?:(?:Re|Fw): +)?(.+)$/)[1]
let titleEl = createElement(`<a class="board ellipsis" href="javascript:void(0);">系列 ${title}</a>`)
let titleUrl = `${location.pathname.match(/^(.+\/).+?$/)[1]}search?q=${encodeURIComponent(`thread:${title}`).replace(/%20/g, '+')}`
titleEl.addEventListener('click', function (e) {
location.href = titleUrl
})
// 同作者
let author = document.querySelectorAll('.article-metaline')[0]
.querySelector('.article-meta-value')
.textContent.match(/^(.+) +\([^\)]*?\)$/)[1]
let authorEl = createElement(`<a class="board" href="javascript:void(0);">作者 ${author}</a>`)
let authorUrl = `${location.pathname.match(/^(.+\/).+?$/)[1]}search?q=${encodeURIComponent(`author:${author}`).replace(/%20/g, '+')}`
authorEl.addEventListener('click', function (e) {
location.href = authorUrl
})
// 插入到畫面中
let navigation = document.querySelector('#navigation')
navigation.firstElementChild.remove()
navigation.insertAdjacentElement('afterbegin', titleEl)
navigation.insertAdjacentElement('afterbegin', createElement('<div class="bar"></div>'))
navigation.insertAdjacentElement('beforeend', authorEl)
}
function pttImageEnhanced () {
function getPrevRichcontentEl (el) {
while (el.parentElement.id !== 'main-content') {
el = el.parentElement
}
return el
}
// == 取消所有 ptt web 原生的 imgur 圖片載入 ==
for (let img of document.querySelectorAll('.richcontent > img[src*="imgur.com"]')) {
img.src = ''
img.parentElement.remove()
}
// == 建立 lazy observer ==
let onEnterView = function (entries, observer) {
for (let entry of entries) {
if (entry.isIntersecting) {
// 目標進入畫面
let triggerRichcontent = entry.target
let imgurId = triggerRichcontent.dataset.imgurId
isImgurMp4Exist(imgurId)
.then(hasVideo => {
let attachment
if (hasVideo) {
attachment = createElement(`<video src="https://i.imgur.com/${imgurId}.mp4" autoplay loop muted style="max-width: 100%;max-height: 800px;"></video>`)
attachment.addEventListener('loadedmetadata', function (e) {
triggerRichcontent.removeAttribute('style')
})
} else {
attachment = createElement(`<img src="https://i.imgur.com/${imgurId}h.jpg" alt>`)
attachment.addEventListener('load', function (e) {
triggerRichcontent.removeAttribute('style')
})
}
triggerRichcontent.append(attachment)
})
.catch(err => err)
observer.unobserve(triggerRichcontent)
}
}
}
let options = {
rootMargin: '200%',
}
let lazyObserver = new IntersectionObserver(onEnterView, options)
for (let link of document.querySelectorAll('.bbs-screen.bbs-content a[href*="imgur.com"]')) {
let urlData = new URL(link)
if (globalRegExpData.imgurIdExtRegExp.test(urlData.pathname)) {
let imgurId = RegExp.$1
// 建立 richcontent
let prevRichcontentEl = getPrevRichcontentEl(link)
let richcontent = createElement(`<div class="richcontent" style="min-height: 30vh;" data-imgur-id="${imgurId}"></div>`)
lazyObserver.observe(richcontent)
insertElementToNextLine(prevRichcontentEl, richcontent)
}
}
}
// == main ==
var globalRegExpData = {
imgurIdExtRegExp: /^\/(\w{7})\w?(?:\.(\w+))?$/,
}
if (/^(?:\/[^\/]+?)+\/(?:M|G)\.\d+\.A\.[0-9A-F]{3}\.html/.test(location.pathname)) {
document.addEventListener('DOMContentLoaded', function () {
addStyle(
`<style>
#navigation {
display: flex;
}
#navigation > * {
white-space: nowrap;
}
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
}
</style>`
)
setNoReferrer()
pttImageEnhanced()
addSearch()
// document.querySelector('.article-metaline-right')?.remove?.()
}, { once: true })
// 看板主頁
// } else if (/^\/bbs\/[^\/]+?\/index.html/.test(location.pathname)) {
// console.log('看板主頁')
// // 搜尋結果
// } else if (/^\/bbs\/[^\/]+?\/search/.test(location.pathname)) {
// console.log('搜尋頁面')
// 已成年同意
} else if (location.pathname === '/ask/over18') {
agreeOver18()
}
})()