Guest User

Untitled

a guest
Oct 28th, 2023
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.30 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Hide or Highlight Verified and Seen
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Hides & controls visibility of verified and seen account tweets and replies.
  6. // @author You
  7. // @match https://twitter.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. "use strict";
  13.  
  14.  
  15. const excludeKeywords =
  16. "@people,@you,@whitelist".split(
  17. ","
  18. );
  19.  
  20. // Constants
  21. const seenTweetsKey = "seen_tweets";
  22. const maxStorageSize = 4 * 1024 * 1024; // 4MB
  23. const oneMonthInMilliseconds = 30 * 24 * 60 * 60 * 1000;
  24.  
  25. // Define control panel CSS
  26. const generalStyle = `
  27. #user-script-control-panel {
  28. position: fixed;
  29. left: 20px;
  30. bottom: 90px;
  31. width: 41px; /* Set the control panel width to 23px */
  32. overflow: hidden; /* Hidden overflow */
  33. padding: 2px;
  34. background-color: #2D2D2D;
  35. color: #FFF;
  36. border-radius: 5px;
  37. box-shadow: 0 2px 5px 0 rgba(0,0,0,0.25);
  38. z-index: 99999999;
  39. }
  40.  
  41. #user-script-control-panel:hover {
  42. width: auto; /* Restore the width to 200px when hovered */
  43. overflow: auto; /* Show the overflow when hovered */
  44. }
  45.  
  46. #user-script-control-panel label {
  47. display: block;
  48. white-space: nowrap; /* Prevent text from wrapping */
  49. }
  50.  
  51. #user-script-control-panel label {
  52. display: block; /* Make the labels (and hence checkboxes) display in a column */
  53. }
  54.  
  55. .hidden-verified {
  56. background-color: rgba(255, 0, 0, 0.15) !important;
  57. }
  58. .hidden-seen {
  59. background-color: rgba(0, 207, 255, 0.15) !important;
  60. }
  61. .hidden-verified.hidden-seen {
  62. background: linear-gradient(90deg, rgba(0, 207, 255, 0.15) 0%, rgba(255, 0, 0, 0.15) 100%) !important;
  63. }
  64.  
  65.  
  66. a[rel~="noreferrer"] .css-1dbjc4n.r-161ttwi.r-u8s1d {
  67. display: none;
  68. }
  69. .link-text {
  70. display: block;
  71. color: white;
  72. background-color: #040404b5;
  73. font-family: "Segoe UI Emoji";
  74. position: relative;
  75. font-size: 13px;
  76. width: 100%;
  77. padding: 10px;
  78. padding-top: 6px;
  79. padding-bottom:8px;
  80. }
  81. .link-text .domain{
  82. color: grey;
  83. }
  84.  
  85. `;
  86.  
  87. const controlPanelHTML = `
  88. <div id="user-script-control-panel">
  89. <label>
  90. <input type="checkbox" id="collapseVerified"><svg viewBox="0 0 22 22" aria-label="Verified account" role="img" class="r-1cvl2hr r-4qtqp9 r-yyyyoo r-1xvli5t r-9cviqr r-f9ja8p r-og9te1 r-bnwqim r-1plcrui r-lrvibr" data-testid="icon-verified"><g><path d="M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.902-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.144 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.567s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.907.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z"></path></g></svg>
  91. Collapse verified
  92. </label>
  93. <label>
  94. <input type="checkbox" id="collapseSeen">👁️
  95. Collapse seen
  96. </label>
  97. </div>
  98. `;
  99.  
  100. document.body.insertAdjacentHTML("beforeend", controlPanelHTML);
  101. document.querySelector("#collapseVerified").checked =
  102. localStorage.getItem("collapseVerified") === "true";
  103. document.querySelector("#collapseSeen").checked =
  104. localStorage.getItem("collapseSeen") === "true";
  105.  
  106. const controlPanel = document.querySelector("#user-script-control-panel");
  107.  
  108. const style = document.createElement("style");
  109. style.innerHTML = generalStyle;
  110. document.head.appendChild(style);
  111.  
  112. controlPanel.addEventListener("change", function (event) {
  113. localStorage.setItem(event.target.id, event.target.checked);
  114. updateStyles();
  115. processTweets();
  116. });
  117.  
  118. // A throttling function that calls a function at most once every specific interval
  119. const throttle = (func, wait) => {
  120. var timeout;
  121. return function executedFunction() {
  122. var context = this;
  123. var args = arguments;
  124.  
  125. var later = function () {
  126. timeout = null;
  127. func.apply(context, args);
  128. };
  129.  
  130. clearTimeout(timeout);
  131. timeout = setTimeout(later, wait);
  132. };
  133. };
  134.  
  135.  
  136.  
  137. let verifiedStyle = document.createElement("style");
  138. let seenStyle = document.createElement("style");
  139. document.head.appendChild(verifiedStyle);
  140. document.head.appendChild(seenStyle);
  141.  
  142. function updateStyles() {
  143. verifiedStyle.innerHTML =
  144. localStorage.getItem("collapseVerified") === "true"
  145. ? `
  146. .hidden-verified {
  147. max-height: 30px !important;
  148. overflow: hidden;
  149. transition: max-height 0.3s ease;
  150. }
  151. .hidden-verified.hovered {
  152. max-height: unset !important;
  153. overflow: visible;
  154. }`
  155. : "";
  156. seenStyle.innerHTML =
  157. localStorage.getItem("collapseSeen") === "true"
  158. ? `
  159. .hidden-seen {
  160. max-height: 30px !important;
  161. overflow: hidden;
  162. transition: max-height 0.3s ease;
  163. }
  164. .hidden-seen.hovered {
  165. max-height: unset !important;
  166. overflow: visible;
  167. }`
  168. : "";
  169. }
  170.  
  171. updateStyles();
  172.  
  173. const getSeenTweets = () => {
  174. const seenTweets = localStorage.getItem(seenTweetsKey);
  175. return seenTweets ? JSON.parse(seenTweets) : {};
  176. };
  177.  
  178. const saveSeenTweets = (seenTweets) => {
  179. const seenTweetsString = JSON.stringify(seenTweets);
  180. if (new Blob([seenTweetsString]).size > maxStorageSize) {
  181. const prunedTweets = pruneTweets(seenTweets);
  182. localStorage.setItem(seenTweetsKey, JSON.stringify(prunedTweets));
  183. } else {
  184. localStorage.setItem(seenTweetsKey, seenTweetsString);
  185. }
  186. };
  187.  
  188. // Initialize hovered tweets session storage
  189. const sessionHoverKey = "session_hovered";
  190. sessionStorage.setItem(sessionHoverKey, JSON.stringify({}));
  191.  
  192. /* Function to handle mouseenter event, adds the 'hovered' class and store hover state in session */
  193. function handleMouseEnter(e) {
  194. const tweetLink = e.target.querySelector('a[href*="/status/"]');
  195. if (tweetLink) {
  196. const tweetId = tweetLink.getAttribute("href").split("/").pop();
  197. let hoveredTweets =
  198. JSON.parse(sessionStorage.getItem(sessionHoverKey)) || {};
  199. if (!hoveredTweets[tweetId]) {
  200. hoveredTweets[tweetId] = true;
  201. sessionStorage.setItem(sessionHoverKey, JSON.stringify(hoveredTweets));
  202. }
  203. }
  204.  
  205. var classes = e.target.className.split(" ");
  206.  
  207. // Check if 'hovered' class already exists
  208. var hoveredClassExists = classes.includes("hovered");
  209.  
  210. // If 'hovered' class is not applied, then add it
  211. if (!hoveredClassExists) {
  212. e.target.className += " hovered";
  213. }
  214. }
  215.  
  216. function processTweets() {
  217. const articles = document.querySelectorAll("article");
  218. const seenTweets = getSeenTweets();
  219.  
  220. let hoveredTweets =
  221. JSON.parse(sessionStorage.getItem(sessionHoverKey)) || {};
  222.  
  223. observer.disconnect();
  224.  
  225. articles.forEach((article) => {
  226. const tweetLink = article.querySelector('a[href*="/status/"]');
  227. if (tweetLink) {
  228. const tweetId = tweetLink.getAttribute("href").split("/").pop();
  229. if (hoveredTweets[tweetId] && !article.classList.contains("hovered")) {
  230. article.className += " hovered";
  231. }
  232. }
  233.  
  234. if (!article.classList.contains("hidden-verified")) {
  235. const svg = article.querySelector('svg[aria-label="Verified account"]');
  236. const articleText = article.textContent.toLowerCase();
  237. const shouldExclude = excludeKeywords.some((keyword) =>
  238. articleText.includes(keyword.toLowerCase())
  239. );
  240. if (svg && !shouldExclude) {
  241. article.classList.add("hidden-verified");
  242. article.addEventListener("mouseenter", handleMouseEnter);
  243. }
  244. }
  245.  
  246. if (!article.classList.contains("hidden-seen")) {
  247. if (tweetLink) {
  248. const tweetId = tweetLink.getAttribute("href").split("/").pop();
  249. if (seenTweets[tweetId]) {
  250. article.classList.add("hidden-seen");
  251. article.addEventListener("mouseenter", handleMouseEnter); // Attach mouseenter event
  252. }
  253. }
  254. }
  255.  
  256.  
  257. const ariaLabelModifiedClass = "aria-label-modified";
  258.  
  259. const mediaLink = article.querySelector('div[data-testid="card.layoutLarge.media"] a[rel~="noreferrer"].r-13qz1uu');
  260. if (mediaLink && !mediaLink.classList.contains(ariaLabelModifiedClass)) {
  261. const ariaLabel = mediaLink.getAttribute("aria-label");
  262. if (ariaLabel) {
  263. const labelSplit = ariaLabel.split(/ (.+)/);
  264. const domain = labelSplit[0];
  265. const text = labelSplit.length > 1 ? labelSplit[1] : "";
  266.  
  267. const linkTextElement = document.createElement('div');
  268. linkTextElement.className = 'link-text';
  269.  
  270. const domainElement = document.createElement('div');
  271. domainElement.className = 'domain';
  272. domainElement.textContent = domain;
  273. linkTextElement.appendChild(domainElement);
  274.  
  275. const titleElement = document.createElement('div');
  276. titleElement.className = 'title';
  277. titleElement.textContent = text;
  278. linkTextElement.appendChild(titleElement);
  279.  
  280. mediaLink.appendChild(linkTextElement);
  281.  
  282. // Add a class to indicate that this mediaLink has been modified
  283. mediaLink.classList.add(ariaLabelModifiedClass);
  284. }
  285. }
  286.  
  287.  
  288. });
  289.  
  290. observer.observe(document, {
  291. childList: true,
  292. subtree: true,
  293. });
  294. }
  295.  
  296. // Listen scroll event. Throttled function to avoid too many calls
  297. window.addEventListener(
  298. "scroll",
  299. throttle(() => {
  300. const articles = document.querySelectorAll("article");
  301. const seenTweets = getSeenTweets();
  302. articles.forEach((article) => {
  303. const rect = article.getBoundingClientRect();
  304. if (rect.bottom < 0) {
  305. const tweetLink = article.querySelector('a[href*="/status/"]');
  306. if (tweetLink) {
  307. const tweetId = tweetLink.getAttribute("href").split("/").pop();
  308. // Check if the tweet is verified and if verified tweets are collapsed
  309. const isVerified = article.classList.contains("hidden-verified");
  310. const collapseVerified =
  311. localStorage.getItem("collapseVerified") === "true";
  312. // Check if the tweet is not seen and either it's not verified or it's verified but not collapsed or it's hovered
  313. if (
  314. !seenTweets[tweetId] &&
  315. (!isVerified ||
  316. (isVerified && !collapseVerified) ||
  317. article.classList.contains("hovered"))
  318. ) {
  319. seenTweets[tweetId] = Date.now();
  320. saveSeenTweets(seenTweets);
  321. if (rect.top > 0) {
  322. article.classList.add("hidden-slim");
  323. } else {
  324. article.classList.add("hovered");
  325. }
  326. article.classList.add("hidden-seen");
  327. article.addEventListener("mouseenter", handleMouseEnter); // Attach mouseenter event, provided it doesn't already exist
  328. }
  329. }
  330. }
  331. processTweets();
  332. });
  333. }, 100)
  334. ); // throttle function will ensure that the function is called at most once every 100 milliseconds
  335.  
  336. const observer = new MutationObserver(processTweets);
  337. observer.observe(document, {
  338. childList: true,
  339. subtree: true,
  340. });
  341.  
  342. processTweets();
  343. })();
  344.  
Advertisement
Add Comment
Please, Sign In to add comment