Created
June 13, 2023 19:24
-
-
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.
This file contains 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
// 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