您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically shows spoilers on media when browsing Pawoo through the web client.
// ==UserScript== // @name Pawoo Always Show Spoilers // @namespace https://github.com/TaleirOfDeynai/ // @version 1.0 // @description Automatically shows spoilers on media when browsing Pawoo through the web client. // @author Taleir // @match https://pawoo.net/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Helper functions. function logError(ex) { console.error(`[UserScript:Pawoo Always Show Spoilers] Encountered an error; ${ex.message}`); console.error(ex); } function isDetached(node) { while (node) { if (node === document.documentElement) return false; node = node.parentNode; } return true; } function createTrial(fn) { return function(arg) { try { fn(arg); } catch (ex) { logError(ex); } }; } // Helper classes. class Observable { constructor(source) { this._source = Observable.toSource(source); } static toSource(obj) { switch (true) { case typeof obj.forEach === "function": return obj.forEach.bind(obj); case typeof obj === "function": return obj; default: return (iterator) => iterator(obj); } } static join(...args) { args = args.map(Observable.toSource); return new Observable(iterator => args.forEach(source => source(iterator))); } forEach(fn) { try { this._source(fn); } catch (ex) { logError(ex); } return this; } map(fn) { var mappedSource = (iterator) => this._source(val => iterator(fn(val))); return new Observable(mappedSource); } flat() { var flattenedSource = (iterator) => this._source(val => { Observable.toSource(val)(iterator); }); return new Observable(flattenedSource); } flatMap(fn) { var flatMappedSource = (iterator) => this._source(val => { Observable.toSource(fn(val))(iterator); }); return new Observable(flatMappedSource); } collect(fn) { var collectedSource = (iterator) => this._source(val => { var result = fn(val); if (typeof result !== "undefined") iterator(result); }); return new Observable(collectedSource); } filter(fn) { var filteredSource = (iterator) => this._source(val => fn(val) && iterator(val)); return new Observable(filteredSource); } zip(fn) { var zippedSource = (iterator) => this._source(left => { Observable.toSource(fn(left))(right => { iterator(Array.isArray(left) ? [...left, right] : [left, right]); }); }); return new Observable(zippedSource); } } class NodeObserver { constructor(parent, selector) { this.disconnected = false; this._callbacks = []; this._nodes = new Set(); var addNode = (added) => { if (this._nodes.has(added)) return; this._callbacks.forEach(fn => fn(added)); this._nodes.add(added); }; var tryAddNode = (added) => { if (!added.matches) return; if (added.matches(selector)) return addNode(added); added.querySelectorAll(selector).forEach(addNode); }; // Attach to all existing nodes that match. parent.querySelectorAll(selector).forEach(addNode); this._observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(tryAddNode); if (mutation.removedNodes.length > 0) for (var node of this._nodes) if (isDetached(node)) this._nodes.delete(node); }); }); this._observer.observe(parent, { childList: true, subtree: true }); } disconnect() { if (this.disconnected) return; this._observer.disconnect(); this._callbacks = []; this._nodes = new Set(); this.disconnected = true; } forEach(fn) { if (this.disconnected) return; fn = createTrial(fn); this._nodes.forEach(fn); this._callbacks.push(fn); } toObservable() { return new Observable(this); } } // Inject style for revealed articles. var styleNode = document.createElement("style"); styleNode.innerHTML = ` .app-body > .app-holder article .status__wrapper.PASS_ext__revealed, .app-body > .app-holder .detailed-status__wrapper .status.PASS_ext__revealed, .app-body > .app-holder .detailed-status__wrapper .detailed-status.PASS_ext__revealed, .container.pawoo-wide .entry.PASS_ext__revealed { position: relative; } .PASS_ext__revealed > * { background-color: transparent !important; } .PASS_ext__revealed::before { background-color: rgba(255, 0, 0, 0.05); content: ''; display: block; width: 100%; height: 100%; position: absolute; left: 0; top: 0; pointer-events: none; } `; document.head.appendChild(styleNode); // Actual work of the script starts here. var applyModifications = (tuple) => { tuple[0].setAttribute("data-pass-ext-visited", ""); tuple[0].classList.add("PASS_ext__revealed"); tuple[1].click(); }; // Observe on the main application entry points. var app = new NodeObserver(document.documentElement, `.app-body > .app-holder`).toObservable(); var act = new NodeObserver(document.documentElement, `.container.pawoo-wide`).toObservable(); // Observe on the main app's articles list. var appArticles = app.flatMap(node => new NodeObserver(node, `.item-list[role="feed"] article .status__wrapper`)); // Observe on the main app's detailed view. var appView = app.flatMap(node => new NodeObserver(node, `.detailed-status__wrapper`)); var appViewStatus = appView.flatMap(node => new NodeObserver(node, `.status`)); var appViewDetail = appView.flatMap(node => new NodeObserver(node, `.detailed-status`)); // Observe on the activity stream entries and detailed views. var actEntries = act.flatMap(node => new NodeObserver(node, `.h-feed .entry`)); var actDetail = act.flatMap(node => new NodeObserver(node, `.h-entry .entry`)); // Apply the modifications. Observable.join(appArticles, appViewStatus, appViewDetail, actEntries, actDetail) .zip(node => new NodeObserver(node, `button.media-spoiler`)) .filter(tuple => !tuple[0].hasAttribute("data-pass-ext-visited")) .forEach(applyModifications); })();