-
-
Save zoubingwu/c4bf1e1f26ad5d3190786bc4a000da62 to your computer and use it in GitHub Desktop.
| // ==UserScript== | |
| // @name Twitter Insta Block | |
| // @namespace your-namespace-here | |
| // @version 3 | |
| // @description Adds a "Block User" button on Twitter timeline conversations and blocks the user with one click | |
| // @author zoubingwu | |
| // @match https://x.com/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| let authorization = ''; | |
| // Save a reference to the original XMLHttpRequest constructor | |
| const OriginalXHR = window.XMLHttpRequest; | |
| // Define a new constructor for our modified XMLHttpRequest object | |
| function ModifiedXHR() { | |
| const xhr = new OriginalXHR(); | |
| // Override the setRequestHeader() method to intercept the Authorization header | |
| const originalSetRequestHeader = xhr.setRequestHeader; | |
| xhr.setRequestHeader = function (name, value) { | |
| if (name.toLowerCase() === 'authorization' && !authorization) { | |
| authorization = value; | |
| } | |
| originalSetRequestHeader.apply(this, arguments); | |
| } | |
| return xhr; | |
| } | |
| // Replace the original XMLHttpRequest constructor with our modified constructor | |
| window.XMLHttpRequest = ModifiedXHR; | |
| const homePageRegex = /^https:\/\/x\.com\/\w+/; | |
| const statusPageRegex = /^https:\/\/x\.com\/\w+\/status\/(\d+)/; | |
| const style = document.createElement("style"); | |
| style.innerHTML = ` | |
| .insta-block-button { | |
| margin-right: 10px; | |
| height: 36px; | |
| width: 36px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition-duration: 0.2s; | |
| } | |
| .insta-block-button:hover { | |
| background-color: rgba(29, 155, 240, 0.1) | |
| } | |
| .insta-block-button > svg { | |
| height: 16px; | |
| color: ${reverseColor(document.body.style.backgroundColor)}; | |
| fill: currentColor; | |
| } | |
| ` | |
| document.body.appendChild(style); | |
| const tampered = new WeakSet(); | |
| const observer = new MutationObserver(function (mutationList, observer) { | |
| const conversationItems = document.querySelectorAll('[data-testid="cellInnerDiv"]') | |
| // For each conversation item, add a "Block User" button | |
| conversationItems.forEach((item, index) => { | |
| if (tampered.has(item)) { | |
| return; | |
| } | |
| const tweet = item.querySelector('[data-testid="tweet"]'); | |
| // already blocked | |
| if (!tweet && ['已屏蔽的账号', 'account you blocked'].some(i => item.innerText.includes(i))) { | |
| item.style.display = 'none' | |
| console.log('already blocked'); | |
| return; | |
| } | |
| const link = item.querySelector('[data-testid="User-Name"] a[role="link"]') | |
| if (!link) { | |
| console.log('no link found'); | |
| return; | |
| } | |
| const screenName = link.getAttribute('href').substring(1); | |
| if (item.querySelector(`.insta-block-button[data-name="${screenName}"]`)) { | |
| console.log('already have block button'); | |
| return; | |
| } | |
| // Create a "Block User" button | |
| const blockButton = document.createElement('div'); | |
| blockButton.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true"><g><path d="M12 3.75c-4.55 0-8.25 3.69-8.25 8.25 0 1.92.66 3.68 1.75 5.08L17.09 5.5C15.68 4.4 13.92 3.75 12 3.75zm6.5 3.17L6.92 18.5c1.4 1.1 3.16 1.75 5.08 1.75 4.56 0 8.25-3.69 8.25-8.25 0-1.92-.65-3.68-1.75-5.08zM1.75 12C1.75 6.34 6.34 1.75 12 1.75S22.25 6.34 22.25 12 17.66 22.25 12 22.25 1.75 17.66 1.75 12z"></path></g></svg>` | |
| blockButton.dataset.name = screenName; | |
| blockButton.classList.add('insta-block-button') | |
| // Add a click event listener to the button to block the user | |
| blockButton.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| item.remove(); | |
| blockUser(screenName) | |
| }); | |
| // Add the "Block User" button to the conversation item | |
| const menu = item.querySelector('button[data-testid="caret"]'); | |
| if (menu) { | |
| menu.parentNode.prepend(blockButton); | |
| tampered.add(item); | |
| } | |
| }); | |
| }) | |
| observer.observe(document.documentElement, { childList: true, subtree: true }); | |
| function getCookie(name) { | |
| const cookieString = document.cookie; | |
| const cookies = cookieString.split(';'); | |
| const cookieMap = cookies.reduce((map, cookie) => { | |
| const [key, value] = cookie.trim().split('='); | |
| map[key] = value; | |
| return map; | |
| }, {}); | |
| return cookieMap[name]; | |
| } | |
| function getHeader() { | |
| const authHeaders = JSON.parse(localStorage.getItem('authHeaders')); | |
| const headers = { | |
| 'Authorization': authHeaders ? Object.values(authHeaders).at(0).authorization : authorization, | |
| 'x-csrf-token': getCookie('ct0'), | |
| 'x-twitter-auth-type': 'OAuth2Session', | |
| 'x-twitter-active-user': 'yes', | |
| 'x-twitter-client-language': 'en', | |
| } | |
| return headers; | |
| } | |
| // Define a function to block a user by screen name | |
| function blockUser(screenName) { | |
| fetch(`https://x.com/i/api/1.1/blocks/create.json?screen_name=${screenName}`, { | |
| method: 'POST', | |
| credentials: 'include', | |
| headers: getHeader(), | |
| }) | |
| .then(response => { | |
| if (response.ok) { | |
| console.log(`User @${screenName} blocked successfully`); | |
| } else { | |
| console.error(`Failed to block user @${screenName}`); | |
| } | |
| }) | |
| .catch(error => { | |
| console.error(error); | |
| }); | |
| } | |
| function reverseColor(color) { | |
| // Check if the input is a hex color code | |
| if (color.startsWith('#')) { | |
| // Convert hex color code to RGB color values | |
| const r = parseInt(color.substring(1, 3), 16); | |
| const g = parseInt(color.substring(3, 5), 16); | |
| const b = parseInt(color.substring(5, 7), 16); | |
| // Compute the reverse color values by subtracting each component from 255 | |
| const reverseR = 255 - r; | |
| const reverseG = 255 - g; | |
| const reverseB = 255 - b; | |
| // Convert reverse RGB color values to hex color code | |
| const reverseHex = `#${reverseR.toString(16).padStart(2, '0')}${reverseG.toString(16).padStart(2, '0')}${reverseB.toString(16).padStart(2, '0')}`; | |
| return reverseHex; | |
| } else if (color.startsWith('rgb')) { | |
| // Parse the RGB color values from the input string | |
| const match = color.match(/\d+/g); | |
| const r = parseInt(match[0]); | |
| const g = parseInt(match[1]); | |
| const b = parseInt(match[2]); | |
| // Compute the reverse color values by subtracting each component from 255 | |
| const reverseR = 255 - r; | |
| const reverseG = 255 - g; | |
| const reverseB = 255 - b; | |
| // Construct the reverse RGB color string | |
| const reverseRGB = `rgb(${reverseR}, ${reverseG}, ${reverseB})`; | |
| return reverseRGB; | |
| } else { | |
| // Invalid input | |
| return null; | |
| } | |
| } | |
| })(); |
Add the following code to L96 to display a white icon when the website is in dark mode
@media (prefers-color-scheme: dark) { .insta-block-button { color: white } }
Looks like twitter not following the system color scheme setting, instead it uses its own background color setting, so I added a function to simply reverse that color for the icon
Firefox's querySelector does not support the pseduo-class :has() well enough like Chrome does.
BTW, the twitter auth token cannot be found in localStorage when using Firefox so I have to find it manually.
Here is my forked snippet with Firefox support.
https://gist.github.com/tizee/495e3411d6311c12ea3c06c4bf3c0bcf#file-main-js
This would make the script more robust.
has selector was removed, it just get all the querySelectorAll('[data-testid="cellInnerDiv"]') then remove that item in the click event
Firefox's querySelector does not support the pseduo-class
:has()well enough like Chrome does.BTW, the twitter auth token cannot be found in
localStoragewhen using Firefox so I have to find it manually.Here is my forked snippet with Firefox support.
tizee/495e3411d6311c12ea3c06c4bf3c0bcf#file-main-js
![]()
This would make the script more robust.
tried with firefox, seems twitter no longer stores auth token in local storage, so I have to monkey patch the XHR :(
tried with firefox, seems twitter no longer stores auth token in local storage, so I have to monkey patch the XHR :(
Thanks for solving the token issue. This hack is very creative.


Add the following code to L96 to display a white icon when the website is in dark mode