Twitter Show Sensitive Content

No more extra click to show a stupid tweets!

当前为 2022-02-16 提交的版本,查看 最新版本

//* eslint-env browser, es6, greasemonkey */
// ==UserScript==
// @name         Twitter Show Sensitive Content
// @namespace    kTu*Kzukf&5p#85%xas!fBH4#GT@FQ7@
// @version      0.3.1
// @description  No more extra click to show a 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 warning button
	// 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)
						Object.defineProperty(this, 'response', { writable: true })
						Object.defineProperty(this, 'responseText', { writable: true })
						this.response = this.responseText = response
					}
				})
			return open_prototype.apply(this, arguments)
		}
	}

	intercept_response(
		/i\/api\/graphql\/\S+\/[UserMedia|UserByScreenName]+/gi,
		function (response) {
			// console.log('[API] FOUND RESPONSE!', response);

			const new_response = response.replace(/(sensitive_media)/gim, '')
			return new_response
		}
	)
	// TO HERE

	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 excedeed, 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 tecnically 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()
			},
			{ once: true }
		)
	}
	runOnURLChange()
})();