Twitter One-Click Block Button

Adds a block button to every tweet in order to quickly block people.

当前为 2022-07-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Twitter One-Click Block Button
  3. // @namespace https://gist.github.com/toxicwind
  4. // @version 1.1
  5. // @description Adds a block button to every tweet in order to quickly block people.
  6. // @author toxicwind
  7. // @license MIT
  8. // @match https://twitter.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  12. // @grant none
  13. // @run-at document-end
  14. // ==/UserScript==
  15.  
  16. const css = `
  17. a.block-button {
  18. display: flex;
  19. align-items: center;
  20. justify-content: center;
  21. }
  22.  
  23. a.block-button svg {
  24. width: 1em;
  25. height: 1em;
  26. fill: currentcolor;
  27. }
  28.  
  29. a.block-button {
  30. transition: color 0.5s;
  31. }
  32.  
  33. a.block-button:hover {
  34. color: red;
  35. }
  36.  
  37. @keyframes wide {
  38. 0% {
  39. width: 0%
  40. }
  41. 100% {
  42. width: 60%;
  43. }
  44. }`;
  45.  
  46. const style = document.createElement("style");
  47. style.textContent = css;
  48. document.head.appendChild(style);
  49.  
  50. (async () => {
  51. let fetchToken = async () => {
  52. let mainUrl = null;
  53. for (let script of document.body.querySelectorAll("script[src]"))
  54. if (/\/main\.[^\/]*\.js$/.test(script.src)) mainUrl = script.src;
  55. if (!mainUrl) return null;
  56.  
  57. let response = await fetch(mainUrl);
  58. let mainSource = await response.text();
  59. let result = /\"AAAAAAA[^"]+\"/.exec(mainSource);
  60. if (!result || result.length != 1) return null;
  61.  
  62. return JSON.parse(result[0]);
  63. };
  64.  
  65. let authToken = await fetchToken();
  66.  
  67. let getCookie = (cname) => {
  68. const name = `${cname}=`;
  69. const decodedCookie = decodeURIComponent(document.cookie);
  70. const ca = decodedCookie.split(";");
  71. for (let i = 0; i < ca.length; i++) {
  72. let c = ca[i];
  73. while (c.charAt(0) == " ") {
  74. c = c.substring(1);
  75. }
  76. if (c.indexOf(name) == 0) {
  77. return c.substring(name.length, c.length);
  78. }
  79. }
  80. return "";
  81. };
  82.  
  83. let blockUser = async (userName) => {
  84. return await fetch("https://api.twitter.com/1.1/blocks/create.json", {
  85. credentials: "include",
  86. referrer: "https://api.twitter.com/1.1/blocks/create.json",
  87. body: `screen_name=${userName}`,
  88. method: "POST",
  89. mode: "cors",
  90. headers: {
  91. "x-twitter-auth-type": "OAuth2Session",
  92. "x-twitter-client-language": "en",
  93. "x-twitter-active-user": "yes",
  94. "x-csrf-token": getCookie("ct0"),
  95. authorization: `Bearer ${authToken}`,
  96. "Content-Type": "application/x-www-form-urlencoded",
  97. },
  98. });
  99. };
  100.  
  101. let buttonClick = async (e) => {
  102. let userName = e.currentTarget.dataset.blockUser;
  103. let blockResult = await blockUser(userName);
  104. if (blockResult.status === 200) {
  105. $("article")
  106. .find("a[data-block-user=" + userName + "]")
  107. .parents("article[data-testid=tweet]")
  108. .slideUp(200);
  109. //alert("User blocked successfully.");
  110. } else {
  111. alert(
  112. "The block operation failed, see the network tab for detailes of the failure."
  113. );
  114. }
  115. };
  116.  
  117. const icon = `<span>&nbsp;·&nbsp;</span><svg xmlns="http://www.w3.org/2000/svg" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m17.069 6.546 2.684-2.359c.143-.125.32-.187.497-.187.418 0 .75.34.75.75 0 .207-.086.414-.254.562l-16.5 14.501c-.142.126-.319.187-.496.187-.415 0-.75-.334-.75-.75 0-.207.086-.414.253-.562l2.438-2.143c-1.414-1.132-2.627-2.552-3.547-4.028-.096-.159-.144-.338-.144-.517s.049-.358.145-.517c2.111-3.39 5.775-6.483 9.853-6.483 1.815 0 3.536.593 5.071 1.546zm2.318 1.83c.967.943 1.804 2.013 2.475 3.117.092.156.138.332.138.507s-.046.351-.138.507c-2.068 3.403-5.721 6.493-9.864 6.493-1.298 0-2.553-.313-3.73-.849l2.624-2.307c.352.102.724.156 1.108.156 2.208 0 4-1.792 4-4 0-.206-.016-.408-.046-.606zm-4.932.467c-.678-.528-1.53-.843-2.455-.843-2.208 0-4 1.792-4 4 0 .741.202 1.435.553 2.03l1.16-1.019c-.137-.31-.213-.651-.213-1.011 0-1.38 1.12-2.5 2.5-2.5.474 0 .918.132 1.296.362z" fill-rule="nonzero"/>`;
  118.  
  119. let observer;
  120.  
  121. const interval = setInterval(init, 500);
  122.  
  123. const DEBUG = true;
  124.  
  125. function debug() {
  126. if (DEBUG) {
  127. console.log.apply(null, arguments);
  128. }
  129. }
  130.  
  131. function init() {
  132. const el = document.querySelectorAll(
  133. 'div[data-testid="primaryColumn"] section article'
  134. );
  135.  
  136. if (el && el.length > 0) {
  137. clearInterval(interval);
  138. debug("articles found");
  139. //add links to already exisiting articles
  140. observer = new MutationObserver(newTweets);
  141. observer.observe(document.body, {
  142. childList: true,
  143. attributes: false,
  144. subtree: true,
  145. characterData: false,
  146. });
  147.  
  148. el.forEach((article) => {
  149. createLink(article);
  150. });
  151. }
  152. }
  153.  
  154. function newTweets(mutationList, observer) {
  155. mutationList.forEach((mut) => {
  156. if (mut.addedNodes.length > 0) {
  157. mut.addedNodes.forEach((node) => {
  158. if (node.innerHTML && node.innerHTML.indexOf("<article ") > -1) {
  159. createLink(node.querySelector("article"));
  160. }
  161. });
  162. }
  163. });
  164. }
  165.  
  166. function createLink(t) {
  167. if (!t.querySelector("a[block-button]")) {
  168. const profileName = t
  169. .querySelector("div[data-testid=User-Names]")
  170. .querySelector("a")
  171. .pathname.substr(1);
  172.  
  173. const time = t.querySelector('a[href*="/status/"] time[datetime]');
  174. if (!time) return;
  175. const statuslink = time.parentNode;
  176.  
  177. const block_link = document.createElement("a");
  178. block_link.setAttribute("class", "block-button");
  179. block_link.setAttribute("data-block-user", profileName);
  180. block_link.innerHTML = icon;
  181. statuslink.insertAdjacentElement("afterend", block_link);
  182.  
  183. block_link.title = "Block User: " + profileName;
  184. block_link.onclick = buttonClick;
  185. }
  186. }
  187. })();