Twitter Show Sensitive Content

No more extra click to show stupid tweets!

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

//* 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();
  */
})();