您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
No more extra click to show stupid tweets!
//* eslint-env browser, es6, greasemonkey */ // ==UserScript== // @name Twitter Show Sensitive Content // @namespace kTu*Kzukf&5p#85%xas!fBH4#GT@FQ7@ // @version 0.3.6 // @description No more extra click to show stupid tweets! // @author _SUDO // @match *://*.twitter.com/* // @icon https://www.google.com/s2/favicons?domain=twitter.com // @license GPL // @grant window.onurlchange // @run-at document-start // ==/UserScript== (function () { 'use strict'; //* Bypass NSFW warnings // Thanks to TURTLE: https://stackoverflow.com/a/43144531 //! IF IS NOT WORKING OR GENERATES SOME KIND OF PROBLEM, COMMENT FROM HERE: const open_prototype = XMLHttpRequest.prototype.open; const intercept_response = function (urlpattern, callback) { XMLHttpRequest.prototype.open = function () { arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function (event) { if (this.readyState === 4) { var response = callback( event.target.responseText, event.target.responseURL ); Object.defineProperty(this, 'response', { writable: true }); Object.defineProperty(this, 'responseText', { writable: true }); this.response = this.responseText = response; } }); return open_prototype.apply(this, arguments); }; }; // Creates and caches a random string to replace later the possibly_sensitive object key const ranString = (() => Array(5).fill().map(() => 'abcdefghijklmnopqrstuvwxyz'.charAt(Math.random() * 62)).join(''))(); intercept_response( /\/User|TweetDetail|Adaptive/gi, // 179 steps, 0.01ms function (response, responseURL) { // console.log('[API] FOUND RESPONSE!', responseURL, response); const new_response = response .replace(/sensitive_media/gim, '') .replace(/possibly_sensitive/gim, ranString) .replace(/offensive_profile_content/gim, ''); return new_response; } ); // TO HERE //! DOM DEPENDENT, USE IF THE ABOVE METHOD DO NOT WORK /* const maxCheckDeepness = 30; let observer = null; function findParent(elem) { let tries = maxCheckDeepness; let currentNode = elem; let parent = currentNode.parentElement; while (parent.childElementCount === 1) { if (tries <= 0) break; tries--; currentNode = parent; parent = parent.parentElement; } return parent; } function findChild(elem) { let tries = maxCheckDeepness; let currentNode = elem; let child = currentNode.children; while (child.length === 1) { if (tries <= 0) break; tries--; currentNode = child; child = child[0].children; } return child; } function unHideTweet(tweetElement) { const hidden = tweetElement; console.log('[M] Hidden container found!', hidden); // Now filter until we end up without singles divs and two elements let tweet = findChild(hidden); // second element console.log('[M] CHILDS:', tweet); if (tweet.length === 1) { console.log('[M] Only one child found!', tweet[0]); tweet = tweet[0]; } else { let running = true; while (running) { console.log( '[M] Multiple childs found, filtering one more time...', tweet ); if (tweet.length === 2 && tweet[0].childElementCount === 0) tweet = findChild(tweet[1]); else { tweet = tweet[1]; running = false; } } } try { // This should click the button instead of the actual container // if the container is clicked, the page will be redirected tweet.children[0].click(); } catch (err) { // No page interaction console.error('[M] NO PAGE INTERACTION!', err); } } function watcher(disconnect = false) { if (disconnect && observer) { observer.disconnect(); return; } // Twitter uses articles for every tweet. // To use the observer we need to find all tweets parent element const target = findParent(document.querySelector('article')); const sensitiveContentElement = `div[role="presentation"] > div`; console.log('Target:', target); // Show all elements loaded before the observer registration const staticTweets = document.querySelectorAll(sensitiveContentElement); if (staticTweets) staticTweets.forEach((e) => unHideTweet(e)); observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // Well now we can filter elements if (mutation.type === 'childList' && mutation.addedNodes.length) { // console.log('[M]', mutation, mutation.type, mutation.type.attributes) // console.log('[M]', mutation.addedNodes[0]) const hidden = mutation.addedNodes[0].querySelector( sensitiveContentElement ); if (hidden) { unHideTweet(hidden); } } }); }); observer.observe(target, { childList: true, subtree: false, characterData: false }); } function runOnURLChange() { if (window.onurlchange === null) { window.addEventListener('urlchange', (info) => { init(); }); } else { console.error('window.onurlchange not supported'); } } let executed = false; async function init() { let tries = 30; while (!document.querySelector('article')) { if (tries <= 0) { console.error('Max tries exceeded, perhaps the element have changed?'); // Maybe the user let the page in the button to show the profile // Add an click event listener to re-execute when technically clicking the button to show the profile if (!executed) { executed = true; console.log( 'Re-checking tweets container in the next click event...' ); document.body.addEventListener('click', init, { once: true }); } return; } tries--; await new Promise((r) => setTimeout(r, 500)); } watcher(); } if (document.readyState === 'complete') init() else { document.addEventListener('readystatechange', (evt) => { if (document.readyState === 'complete') init() } ) } runOnURLChange(); */ })();