Skip to content

Instantly share code, notes, and snippets.

@zoubingwu
Last active July 13, 2023 14:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zoubingwu/c4bf1e1f26ad5d3190786bc4a000da62 to your computer and use it in GitHub Desktop.
Save zoubingwu/c4bf1e1f26ad5d3190786bc4a000da62 to your computer and use it in GitHub Desktop.
Twitter insta block
// ==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://twitter.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 regex = /^https:\/\/twitter\.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) {
if (!regex.test(window.location.href)) {
return;
}
const conversationItems = document.querySelectorAll('[data-testid="cellInnerDiv"]')
// For each conversation item, add a "Block User" button
conversationItems.forEach((item, index) => {
if (index === 0) {
return;
}
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'
return;
}
const link = item.querySelector('[data-testid="User-Name"] a[role="link"]')
if (!link) {
return;
}
const screenName = link.getAttribute('href').substring(1);
if (item.querySelector(`.insta-block-button[data-name="${screenName}"]`)) {
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('div[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://twitter.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;
}
}
})();
@Fatpandac
Copy link

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
  }
}

@zoubingwu
Copy link
Author

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

image

@tizee
Copy link

tizee commented Jul 11, 2023

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

Screen Shot

This would make the script more robust.

@zoubingwu
Copy link
Author

zoubingwu commented Jul 11, 2023

has selector was removed, it just get all the querySelectorAll('[data-testid="cellInnerDiv"]') then remove that item in the click event

@zoubingwu
Copy link
Author

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.

tizee/495e3411d6311c12ea3c06c4bf3c0bcf#file-main-js

Screen Shot

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 :(

@tizee
Copy link

tizee commented Jul 11, 2023

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