Last active
October 14, 2024 20:06
-
-
Save zoubingwu/c4bf1e1f26ad5d3190786bc4a000da62 to your computer and use it in GitHub Desktop.
Twitter insta block
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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; | |
} | |
} | |
})(); |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
tried with firefox, seems twitter no longer stores auth token in local storage, so I have to monkey patch the XHR :(