Greasy Fork is available in English.
Enhance user experience of PTT web
- // ==UserScript==// @name PTT web enhanced// @namespace 2CF9973A-28C9-11EC-9EA6-98F49F6E8EAB// @version 2.8.1// @description Enhance user experience of PTT web// @author Rick0// @match https://www.ptt.cc/*// @grant GM.xmlHttpRequest// @connect imgur.com// @connect ptt.cc// @run-at document-start// @compatible firefox Tampermonkey, Violentmonkey, Greasemonkey 4.11+// @compatible chrome Tampermonkey, Violentmonkey// @license MIT// ==/UserScript==(function() {'use strict'// == basic methods ==function createElement(html) {let template = document.createElement('template')template.innerHTML = htmlreturn template.content.firstChild}function insertElementToNextLine (positionElement, element) {let positionNextSibling = positionElement.nextSiblingswitch (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])breakcase Node.ELEMENT_NODE:case undefined:positionElement.insertAdjacentElement('afterend', element)breakdefault:throw new Error('insertElementToNextLine receive invalid positionElement')}}function addStyle (cssCode) {document.head.append(createElement(`<style>${cssCode}</style>`))}function getImgurInfo (imgurUrl) {return new Promise((resolve, reject) => {let urlData = new URL(imgurUrl)if (regExpData.imgur.idExt.test(urlData.pathname)) {let imageId = RegExp.$1fetch(`https://api.imgur.com/3/image/${imageId}`, {method: 'GET',referrerPolicy: 'no-referrer',headers: {Authorization: 'Client-ID b654e1b04c90bc8'},}).then(res => res.json()).then(json => resolve(json.data)).catch(err => reject(err))} else if (regExpData.imgur.album.test(urlData.pathname)) {let albumId = RegExp.$1fetch(`https://api.imgur.com/3/album/${albumId}/images`, {method: 'GET',referrerPolicy: 'no-referrer',headers: {Authorization: 'Client-ID b654e1b04c90bc8'},}).then(res => res.json()).then(json => resolve(json.data[0])).catch(err => reject(err))} else if (regExpData.imgur.gallery.test(urlData.pathname)) {let galleryId = RegExp.$1fetch(`https://api.imgur.com/3/gallery/${galleryId}/images`, {method: 'GET',referrerPolicy: 'no-referrer',headers: {Authorization: 'Client-ID b654e1b04c90bc8'},}).then(res => res.json()).then(json => resolve(Array.isArray(json.data) ? json.data[0] : json.data)).catch(err => reject(err))} else {reject(new Error(`不支援的格式: ${imgurUrl}`))}})}// == dependent methods ==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 addHeadlines () {let boardToolsEl = document.querySelector('.btn-group.btn-group-dir')let headlin###rl = `/bbs/${boardData.name}/search?q=recommend%3A100`let headlinesEl = createElement(`<a class="btn" href="${headlin###rl}">爆文</a>`)// 如果在爆文搜尋頁面,按鈕加上樣式if (/[\?&]q=recommend%3A100/.test(location.search)) headlinesEl.classList.add('selected')boardToolsEl.append(headlinesEl)}function addSearch () {// 設定 cssaddStyle(`#navigation {display: flex;}#navigation > * {white-space: nowrap;}.ellipsis {text-overflow: ellipsis;overflow: hidden;}`)// 系列文let title = document.querySelectorAll('.article-metaline')[1].querySelector('.article-meta-value').textContent.match(/^(?:(?:Re|Fw): +)?(.+)$/)[1]let titleEl = createElement(`<a class="board ellipsis" style="cursor: pointer;">系列 ${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(/^[^ ]+/)[0]let authorEl = createElement(`<a class="board" style="cursor: pointer;">作者 ${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.targetlet imgurUrl = triggerRichcontent.dataset.imgurUrlgetImgurInfo(imgurUrl).then(imgurInfo => {let attachmentif (imgurInfo.animated) {attachment = createElement(`<video src="https://i.imgur.com/${imgurInfo.id}.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/${imgurInfo.id}h.jpg" alt>`)attachment.addEventListener('load', function (e) {triggerRichcontent.removeAttribute('style')})}triggerRichcontent.append(attachment)}).catch(err => {triggerRichcontent.remove()})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"]')) {// 建立 richcontentlet prevRichcontentEl = getPrevRichcontentEl(link)let richcontent = createElement(`<div class="richcontent" style="min-height: 30vh;" data-imgur-url="${link.href}"></div>`)lazyObserver.observe(richcontent)insertElementToNextLine(prevRichcontentEl, richcontent)}}// == main ==var regExpData = {imgur: {idExt: /^\/(\w+)(?:\.(\w+))?$/,album: /\/a\/(\w+)/,gallery: /\/gallery\/(\w+)/,},}var pageData = {set metaReferrer (value) {if (this.metaReferrer !== undefined) {document.querySelector('meta[name="referrer"]').content = value} else {document.head.append(createElement(`<meta name="referrer" content="${value}">`))}},get metaReferrer () {return document.querySelector('meta[name="referrer"]')?.content},get isMobile () {return navigator.userAgentData.mobile},}var boardData = (() => {let r###lt = {}if (/^\/(bbs|man)\/([^\/]+)(?:\/[^\/]+)*\/(?:M|G)\.\d+\.A\.[0-9A-F]{3}\.html/.test(location.pathname)) {r###lt = {type: 'post',area: RegExp.$1,name: RegExp.$2,is404: document.title === '404',}} else if (/^\/(bbs|man)\/([^\/]+)(?:\/[^\/]+)*\/index(\d*).html/.test(location.pathname)) {r###lt = {type: 'index',area: RegExp.$1,name: RegExp.$2,pageNum: RegExp.$3 === '' ? 0 : parseInt(RegExp.$3, 10),}} else if (/^\/(bbs|man)\/([^\/]+)\/search/.test(location.pathname)) {r###lt = {type: 'search',area: RegExp.$1,name: RegExp.$2,isHeadline: /[\?&]q=recommend%3A100/.test(location.search),}} else if (location.pathname === '/ask/over18') {r###lt = {type: 'over18',}}return r###lt})()switch (boardData.type) {case 'over18':agreeOver18()break}document.addEventListener('DOMContentLoaded', function () {switch (boardData.type) {case 'post':if (!boardData.is404) {pageData.metaReferrer = 'no-referrer'pttImageEnhanced()// 只有一般看板頁面需要,排除精華區if (boardData.area === 'bbs') {addSearch()}}breakcase 'index':case 'search':addHeadlines()// 手機因為排版關係,使用最新來被爆文取代,但精華區並沒有最新按鈕,所以要排除if (pageData.isMobile && boardData.area === 'bbs') {let oldestEl = document.querySelector('.btn.wide')oldestEl.insertAdjacentElement('beforebegin', document.querySelectorAll('.btn')[2])oldestEl.remove()}break}}, { once: true })})()