Twitter Show Sensitive Content

No more extra click to show stupid tweets!

  1. //* eslint-env browser, es6, greasemonkey */
  2. // ==UserScript==
  3. // @name Twitter Show Sensitive Content
  4. // @namespace kTu*Kzukf&5p#85%xas!fBH4#GT@FQ7@
  5. // @version 0.3.6
  6. // @description No more extra click to show stupid tweets!
  7. // @author _SUDO
  8. // @match *://*.twitter.com/*
  9. // @icon https://www.google.com/s2/favicons?domain=twitter.com
  10. // @license GPL
  11. // @grant window.onurlchange
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. //* Bypass NSFW warnings
  19. // Thanks to TURTLE: https://stackoverflow.com/a/43144531
  20.  
  21. //! IF IS NOT WORKING OR GENERATES SOME KIND OF PROBLEM, COMMENT FROM HERE:
  22. const open_prototype = XMLHttpRequest.prototype.open;
  23. const intercept_response = function (urlpattern, callback) {
  24. XMLHttpRequest.prototype.open = function () {
  25. arguments['1'].match(urlpattern) &&
  26. this.addEventListener('readystatechange', function (event) {
  27. if (this.readyState === 4) {
  28. var response = callback(
  29. event.target.responseText,
  30. event.target.responseURL
  31. );
  32. Object.defineProperty(this, 'response', { writable: true });
  33. Object.defineProperty(this, 'responseText', { writable: true });
  34. this.response = this.responseText = response;
  35. }
  36. });
  37. return open_prototype.apply(this, arguments);
  38. };
  39. };
  40.  
  41. // Creates and caches a random string to replace later the possibly_sensitive object key
  42. const ranString = (() => Array(5).fill().map(() => 'abcdefghijklmnopqrstuvwxyz'.charAt(Math.random() * 62)).join(''))();
  43. intercept_response(
  44. /\/User|TweetDetail|Adaptive/gi, // 179 steps, 0.01ms
  45. function (response, responseURL) {
  46. // console.log('[API] FOUND RESPONSE!', responseURL, response);
  47.  
  48. const new_response = response
  49. .replace(/sensitive_media/gim, '')
  50. .replace(/possibly_sensitive/gim, ranString)
  51. .replace(/offensive_profile_content/gim, '');
  52. return new_response;
  53. }
  54. );
  55. // TO HERE
  56.  
  57. //! DOM DEPENDENT, USE IF THE ABOVE METHOD DO NOT WORK
  58. /*
  59. const maxCheckDeepness = 30;
  60. let observer = null;
  61.  
  62. function findParent(elem) {
  63. let tries = maxCheckDeepness;
  64. let currentNode = elem;
  65. let parent = currentNode.parentElement;
  66.  
  67. while (parent.childElementCount === 1) {
  68. if (tries <= 0) break;
  69. tries--;
  70. currentNode = parent;
  71. parent = parent.parentElement;
  72. }
  73.  
  74. return parent;
  75. }
  76.  
  77. function findChild(elem) {
  78. let tries = maxCheckDeepness;
  79. let currentNode = elem;
  80. let child = currentNode.children;
  81.  
  82. while (child.length === 1) {
  83. if (tries <= 0) break;
  84. tries--;
  85. currentNode = child;
  86. child = child[0].children;
  87. }
  88.  
  89. return child;
  90. }
  91.  
  92. function unHideTweet(tweetElement) {
  93. const hidden = tweetElement;
  94.  
  95. console.log('[M] Hidden container found!', hidden);
  96. // Now filter until we end up without singles divs and two elements
  97. let tweet = findChild(hidden); // second element
  98. console.log('[M] CHILDS:', tweet);
  99. if (tweet.length === 1) {
  100. console.log('[M] Only one child found!', tweet[0]);
  101. tweet = tweet[0];
  102. } else {
  103. let running = true;
  104. while (running) {
  105. console.log(
  106. '[M] Multiple childs found, filtering one more time...',
  107. tweet
  108. );
  109. if (tweet.length === 2 && tweet[0].childElementCount === 0)
  110. tweet = findChild(tweet[1]);
  111. else {
  112. tweet = tweet[1];
  113. running = false;
  114. }
  115. }
  116. }
  117.  
  118. try {
  119. // This should click the button instead of the actual container
  120. // if the container is clicked, the page will be redirected
  121. tweet.children[0].click();
  122. } catch (err) {
  123. // No page interaction
  124. console.error('[M] NO PAGE INTERACTION!', err);
  125. }
  126. }
  127.  
  128. function watcher(disconnect = false) {
  129. if (disconnect && observer) {
  130. observer.disconnect();
  131. return;
  132. }
  133.  
  134. // Twitter uses articles for every tweet.
  135. // To use the observer we need to find all tweets parent element
  136. const target = findParent(document.querySelector('article'));
  137. const sensitiveContentElement = `div[role="presentation"] > div`;
  138.  
  139. console.log('Target:', target);
  140.  
  141. // Show all elements loaded before the observer registration
  142. const staticTweets = document.querySelectorAll(sensitiveContentElement);
  143. if (staticTweets) staticTweets.forEach((e) => unHideTweet(e));
  144.  
  145. observer = new MutationObserver((mutations) => {
  146. mutations.forEach((mutation) => {
  147. // Well now we can filter elements
  148. if (mutation.type === 'childList' && mutation.addedNodes.length) {
  149. // console.log('[M]', mutation, mutation.type, mutation.type.attributes)
  150. // console.log('[M]', mutation.addedNodes[0])
  151.  
  152. const hidden = mutation.addedNodes[0].querySelector(
  153. sensitiveContentElement
  154. );
  155. if (hidden) {
  156. unHideTweet(hidden);
  157. }
  158. }
  159. });
  160. });
  161.  
  162. observer.observe(target, {
  163. childList: true,
  164. subtree: false,
  165. characterData: false
  166. });
  167. }
  168.  
  169. function runOnURLChange() {
  170. if (window.onurlchange === null) {
  171. window.addEventListener('urlchange', (info) => {
  172. init();
  173. });
  174. } else {
  175. console.error('window.onurlchange not supported');
  176. }
  177. }
  178.  
  179. let executed = false;
  180. async function init() {
  181. let tries = 30;
  182. while (!document.querySelector('article')) {
  183. if (tries <= 0) {
  184. console.error('Max tries exceeded, perhaps the element have changed?');
  185.  
  186. // Maybe the user let the page in the button to show the profile
  187. // Add an click event listener to re-execute when technically clicking the button to show the profile
  188. if (!executed) {
  189. executed = true;
  190. console.log(
  191. 'Re-checking tweets container in the next click event...'
  192. );
  193. document.body.addEventListener('click', init, { once: true });
  194. }
  195. return;
  196. }
  197. tries--;
  198. await new Promise((r) => setTimeout(r, 500));
  199. }
  200. watcher();
  201. }
  202.  
  203. if (document.readyState === 'complete') init()
  204. else {
  205. document.addEventListener('readystatechange', (evt) => {
  206. if (document.readyState === 'complete') init()
  207. }
  208. )
  209. }
  210. runOnURLChange();
  211. */
  212. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址