Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trumad/d577986502736da446a30a51c6d422bd to your computer and use it in GitHub Desktop.
Save trumad/d577986502736da446a30a51c6d422bd to your computer and use it in GitHub Desktop.
This script autoblocks accounts which post any sponsored tweets, hopefully before you even see them. Use the autoscroll checkbox to let it scroll replies on a popular tweet, to hoover up accounts who sponsor.
// Boost by Thanael - thannymack.com
// Created as a boost for Arc browser
// Could easily be ported to a tampermonkey script
// This script autoblocks accounts which post any sponsored tweets, hopefully before you even see them
// Use the autoscroll checkbox to let it scroll replies on a popular tweet, to hoover up accounts who sponsor.
if (!localStorage.userWantsPromotersBlocked){
localStorage.userWantsPromotersBlocked = true;
}
if (!localStorage.autoScroll){
localStorage.autoScroll = false;
}
let userWantsPromotersBlocked = localStorage.userWantsPromotersBlocked;
let autoScroll = localStorage.autoScroll;
function go() {
observeDOM(doDomStuff);
//
if (autoScroll === 'true'){
doScroll();
refreshAfterAwhile();
}
}
go();
// Helper function to create new DOM elements, by thanael - thannymack.com
function addEl() {
function isElement(element) { // Check if something is a DOM element
// This function from https://stackoverflow.com/a/36894871
return element instanceof Element || element instanceof HTMLDocument;
}
// Defaults, if the user specifies nothing:
let el = document.body; // add our new element to the document.body by default
let tag = "div"; // add a DIV element by default
let attr = {}; // no attributes by default
for (let i = 0, j = arguments.length; i < j; i++) { // for each argument the user supplied
// Idea from https://stackoverflow.com/a/51019068
if (isElement(arguments[i])) { // if it's a DOM element
el = arguments[i]; // assign it to the el variable
}
else if (typeof arguments[i] === "object") { // if it's a normal object
attr = arguments[i]; // assign it to the attributes variable
}
else if (typeof arguments[i] === "string") { // or if it's a string
tag = arguments[i]; // assign it to the tag name variable
}
else {
return "You must specify a tag name (string) or a DOM element, or an object full of attribute names & values";
}
}
const potentialHeadTags = ["style", "title", "link", "meta", "script", "base"]; // hardcoded list of potential tags that belong in <head>
if (potentialHeadTags.includes(tag.toLowerCase()) && el === document.body) {
// If the user has specified a tag usually put in the head,
// and haven't supplied any other element:
el = document.head; // we'll be appending our new element in the document.head
}
var insertBefore = false; // the user wants to insert the element before the element they specified in "el". Off by default.
var append = true;
var newEl = document.createElement(tag); // create the new element
var attrKeys = Object.keys(attr); // generate an array of all attribute names the user supplied
for (let i = 0, j = attrKeys.length; i < j; i++) { // for each attribute
if (attrKeys[i] === "insertBefore") { // Checks for the presence of the "insertBefore" directive
insertBefore = Boolean(attr[attrKeys[i]]); // convert to a boolean and assign
}
else if (attrKeys[i] === "append") { // Checks for the presence of the "append" directive
append = Boolean(attr[attrKeys[i]]); // convert to a boolean and assign
}
else if (attrKeys[i].toLowerCase().startsWith("on") && typeof (attr[attrKeys[i]]) === "function") { // if the user is trying to add an event handler
let handler = attrKeys[i].match(/on(.*)/i)[1]; // Regex out the actual event handler
newEl.addEventListener(handler, attr[attrKeys[i]]);
}
else if (attrKeys[i] in el && !attrKeys[i].toLowerCase().startsWith("on")) {
// if the user wants to specify a dot notation property (textContent etc)
// "in" syntax from https://dmitripavlutin.com/check-if-object-has-property-javascript/
// added the check for !startsWith("on") to handle edge cases where the user wants to supply a string containing JS
// otherwise javascript will do el.onmouseover = "alert()" which achieves nothing.
newEl[attrKeys[i]] = attr[attrKeys[i]]; // set the related element property to be whatever they asked for
}
else { // otherwise, they've just specified a regular attribute
newEl.setAttribute(attrKeys[i], attr[attrKeys[i]]); // set it and forget
}
}
if (!append) { return newEl } // The user wants to append the element later. Return it to them as-is
if (insertBefore) { // If the user assigned this as true, this is where we insert the element before the element they specified
el.parentNode.insertBefore(newEl, el);
}
else { // Otherwise, we just append the element to the page
el.appendChild(newEl)
}
return newEl;
}
// Usage:
// The function expects any of 3 types of arguments:
// * a tag name for your new element (string) - default: "div"
// * a DOM element you want to add your new element to - default: document.body
// * an object containing a list of attributes - default: {}. Example: {textContent:"Hello World", class: "dark", onclick: functionName}
// Don't try to supply two strings, or two DOM elements etc.
// The function will return the newly created element.
// Examples:
// Elements are added to the document.body by default:
// addEl({textContent:"A new div on the document body"});
// You can specify which element to add to:
// addEl("li", document.querySelector("ul"), {innerText:"A new list item",});
// You can specify an element and insert your new element in front of it by setting insertBefore to true (or anything that coerces to true!)
// addEl("li", document.querySelector("li"), {textContent:"A bumped first list item", insertBefore:true});
// You can create an element but not add it to the page, using the "append" attribute boolean:
// addEl("div",{textContent:"loading...", class:"main", append:false});
// You can set values
// addEl("input",{value:"Joe Bloggs"});
// Set event handler function names (Where handleEvent is the name of a function you've written)
// addEl("button", {textContent:"mouseoverme!", class:"btn", onmouseover:handleEvent});
// Set inline styles on the fly:
// addEl({textContent:"Warning!", style:"color: red;"});
// You can specify innerHTML if you realllly want to.
// addEl("span", {innerHTML:"<button>another button</button>"});
// You can also specify your own event handler javascript inside a string
// addEl("button",{textContent:"clickme!", class:"btn",onclick:"alert()"});
// It detects when a tag should be added to the head rather than the body:
// addEl("style", {textContent:"div {color: red;}"});
// addEl("script", {textContent:"alert()"});
function observeDOM(callback, disconnect) { // a function which observes the DOM and calls the callback function every time there's a new mutation
var mutationObserver = new MutationObserver(function (mutations) { //https://davidwalsh.name/mutationobserver-api
mutations.forEach(function (mutation) {
if (typeof (mutation.target.className) != "string") { return }
callback(mutation, mutationObserver);
});
});
// Keep an eye on the DOM for changes
mutationObserver.observe(document.body, { //https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
attributes: true,
// characterData: true,
childList: true,
subtree: true,
// attributeOldValue: true,
// characterDataOldValue: true,
//attributeFilter: ["class"] // We're really only interested in stuff that has a className
});
}
function sleep(ms) { // usage: await sleep(4000)
return new Promise(resolve => setTimeout(resolve, ms));
}
function addToggles(twitterLogo) {
const container = twitterLogo.closest('div');
if (container.querySelector('#blockPromoters')) { return }
const blockPromotersTitle = "Automatically blocks accounts that have sponsored tweets";
const blockPromotersCheckbox = addEl('input', container, { type: 'checkbox', id: 'blockPromoters', title: blockPromotersTitle, onclick: (e) => {userWantsPromotersBlocked = e.target.checked; localStorage.userWantsPromotersBlocked = e.target.checked} });
console.log({userWantsPromotersBlocked})
console.log({autoScroll});
const autoscrollTitle = "Scrolls the page looking for sponsored tweets. Refreshes after 25 seconds to get more.";
if (userWantsPromotersBlocked === 'true'){blockPromotersCheckbox.setAttribute('checked','true')}
addEl('label', container, { htmlFor: 'blockPromoters', textContent: 'block promoters', title: blockPromotersTitle });
const autoScrollCheckbox = addEl('input', container, { type: 'checkbox', title: autoscrollTitle, id: 'autoScroll',onclick: (e) => {localStorage.autoScroll = e.target.checked; location.reload()} });
addEl('label', container, { htmlFor: 'autoScroll', textContent: 'autoscroll', title: autoscrollTitle });
if (autoScroll === 'true'){autoScrollCheckbox.checked = 'checked'}
}
async function refreshAfterAwhile() {
await sleep(25000);
location.reload();
}
function checkSvgsForDAttributeString(baseElement, string) {
const svgArray = baseElement.querySelectorAll("svg");
for (let element of svgArray) {
const dAttribute = element?.querySelector('g')?.querySelector('path')?.getAttribute('d');
if (dAttribute && dAttribute.includes(string)) {
return element;
}
}
}
async function blockAccount(tweet) {
const threeDotsSvg = checkSvgsForDAttributeString(tweet, 'M3 12c0-1.1.9-2 2-2s2');
const threeDots = threeDotsSvg.closest("[aria-label=More]");
if (threeDots.getAttribute("aria-expanded") === "false") {
threeDots.click();
await sleep(500);
const blockSvg = checkSvgsForDAttributeString(document.querySelector("div[role=menu]"), 'M12 3.75c-4.55 0-8.25 3.69-8.25 8.25');
const blockButton = blockSvg.closest("[role=menuitem]");
blockButton.click();
await sleep(500);
document.querySelector('div[data-testid=confirmationSheetDialog]').querySelector('div[role=button]').click();
}
}
function doDomStuff(mutation) {
if (mutation.target.tagName && mutation.target.tagName === "A") {
if (mutation.target.href === "https://twitter.com/home") {
addToggles(mutation.target);
}
}
if (mutation.target.tagName && mutation.target.tagName === "DIV") {
const isPromotedTweet = checkSvgsForDAttributeString(mutation.target, 'M19.498 3h-15c-1.381');
if (isPromotedTweet && userWantsPromotersBlocked === 'true') {
const tweet = mutation.target.closest('[data-testid=cellInnerDiv]');
blockAccount(tweet);
}
}
}
function doScroll() {
var fps = 100;
var speedFactor = 0.001;
var minDelta = 0.5;
var autoScrollSpeed = 10;
var autoScrollTimer, restartTimer;
var isScrolling = false;
var prevPos = 0, currentPos = 0;
var currentTime, prevTime, timeDiff;
window.addEventListener("scroll", function (e) {
// window.pageYOffset is the fallback value for IE
currentPos = window.scrollY || window.pageYOffset;
});
window.addEventListener("wheel", handleManualScroll);
window.addEventListener("touchmove", handleManualScroll);
function handleManualScroll() {
// window.pageYOffset is the fallback value for IE
currentPos = window.scrollY || window.pageYOffset;
clearInterval(autoScrollTimer);
if (restartTimer) {
clearTimeout(restartTimer);
}
restartTimer = setTimeout(() => {
prevTime = null;
setAutoScroll();
}, 1000);
}
function setAutoScroll(newValue) {
if (newValue) {
autoScrollSpeed = speedFactor * newValue;
}
if (autoScrollTimer) {
clearInterval(autoScrollTimer);
}
autoScrollTimer = setInterval(function () {
currentTime = Date.now();
if (prevTime) {
if (!isScrolling) {
timeDiff = currentTime - prevTime;
currentPos += autoScrollSpeed * timeDiff;
if (Math.abs(currentPos - prevPos) >= minDelta) {
isScrolling = true;
window.scrollTo(0, currentPos);
isScrolling = false;
prevPos = currentPos;
prevTime = currentTime;
}
}
} else {
prevTime = currentTime;
}
}, 1000 / fps);
}
setAutoScroll(800);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment