Created
January 4, 2023 08:02
-
-
Save luavixen/f4e4f87e6d96bff5a4c549a8fea32d2a to your computer and use it in GitHub Desktop.
Tumblr userscript to automatically mass block users, visit your blog's settings to open the UI.
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 FoxBlock | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description Tumblr userscript to automatically mass block users. | |
// @author [email protected] | |
// @match https://*.tumblr.com/* | |
// @grant unsafeWindow | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
if (!window.location.href.includes('tumblr.com/settings/blog/')) return; | |
const state = window?.['___INITIAL_STATE___']; | |
async function perform(currentBlog, targetBlog) { | |
const referrer = `https://www.tumblr.com/settings/blog/${currentBlog}`; | |
const authorization = 'Bearer ' + state.apiFetchStore.API_TOKEN; | |
let responseBackup; | |
let payloadBackup; | |
try { | |
responseBackup = await fetch(`https://www.tumblr.com/api/v2/blog/${currentBlog}/backup`, { | |
referrer, method: 'GET', mode: 'cors', credentials: 'include', | |
headers: { | |
'Authorization': authorization, | |
'Accept': 'application/json; format=camelcase', | |
'Sec-Fetch-Dest': 'empty', | |
'Sec-Fetch-Mode': 'cors', | |
'Sec-Fetch-Site': 'same-origin', | |
}, | |
}); | |
payloadBackup = await responseBackup.json(); | |
} catch (cause) { | |
throw new Error('Request for CSRF token failed', { cause }); | |
} | |
const authorizationCSRF = responseBackup.headers.get('x-csrf'); | |
if (!authorizationCSRF) { | |
throw new Error('Response for CSRF token is invalid/errored'); | |
} | |
let responseBlock; | |
let payloadBlock; | |
try { | |
responseBlock = await fetch(`https://www.tumblr.com/api/v2/blog/${currentBlog}/blocks`, { | |
referrer, method: 'POST', mode: 'cors', credentials: 'include', | |
headers: { | |
'Authorization': authorization, | |
'X-CSRF': authorizationCSRF, | |
'X-Ad-Blocker-Enabled': '0', | |
'X-Version': 'redpop/3/0//redpop/', | |
'Accept': 'application/json; format=camelcase', | |
'Content-Type': 'application/json; charset=utf8', | |
'Sec-Fetch-Dest': 'empty', | |
'Sec-Fetch-Mode': 'cors', | |
'Sec-Fetch-Site': 'same-origin', | |
}, | |
body: JSON.stringify({ 'blocked_tumblelog': targetBlog, 'force': false }), | |
}); | |
payloadBlock = await responseBlock.json(); | |
} catch (cause) { | |
throw new Error('Request for block creation failed', { cause }); | |
} | |
const meta = payloadBlock?.meta; | |
if ( | |
typeof meta !== 'object' | |
|| typeof meta.msg !== 'string' | |
|| typeof meta.status !== 'number' | |
) { | |
throw new TypeError('Response from block creation is invalid'); | |
} | |
return { msg: meta.msg, status: meta.status, ok: (meta.status >= 200 && meta.status < 300) }; | |
} | |
const formatBlog = (text) => String(text).trim().toLowerCase(); | |
const formatBlogList = (text) => Array.from(new Set(String(text).split(/[\s\n]+/).map(formatBlog).filter((text) => !!text))); | |
const html = `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"></head><body class="m-0 bg-transparent"><div id="foxblock" class="container-fluid m-4 p-3 bg-light text-body border rounded shadow" style="width:500px"><form id="foxblock-form"><div id="foxblock-content" class="vstack gap-3"><div><label for="foxblock-current" class="form-label">Current blog</label> <input id="foxblock-current" class="form-control" required placeholder="your-blog"></div><div><label for="foxblock-targets" class="form-label">Target blogs</label> <textarea id="foxblock-targets" class="form-control" rows="5" required placeholder="blog1 blog2 blog3 " autocomplete="off" autocorrect="off" spellcheck="false"></textarea></div><div><label for="foxblock-interval" class="form-label">Request interval</label> <select id="foxblock-interval" class="form-select" required><option value="0">3 seconds</option><option value="1">2 seconds</option><option value="2">1 second</option><option value="3">0.5 seconds</option><option value="4">0.25 seconds</option></select></div><div><button id="foxblock-button" type="submit" class="btn btn-primary w-100"><span id="foxblock-button-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span> <span>Block All</span></button></div><div><div id="foxblock-progress" class="progress" role="progressbar"><div id="foxblock-progress-bar" class="progress-bar" style="width:0%"></div></div></div><div id="foxblock-alerts" class="d-none"></div></div></form></div></body></html>`; | |
const frame = document.createElement('iframe'); | |
frame.width = '100%'; | |
frame.height = '100%'; | |
frame.setAttribute('frameborder', '0'); | |
frame.setAttribute('style', 'position: fixed; top: 0; left: 0; bottom: 0; right: 0; z-index: 999999999;'); | |
frame.src = 'about:blank'; | |
document.body.appendChild(frame); | |
const frameDocument = frame.contentWindow.document; | |
frameDocument.open(); | |
frameDocument.write(html); | |
frameDocument.close(); | |
const $form = frameDocument.getElementById('foxblock-form'); | |
const $current = frameDocument.getElementById('foxblock-current'); | |
const $targets = frameDocument.getElementById('foxblock-targets'); | |
const $interval = frameDocument.getElementById('foxblock-interval'); | |
const $button = frameDocument.getElementById('foxblock-button'); | |
const $buttonSpinner = frameDocument.getElementById('foxblock-button-spinner'); | |
const $progress = frameDocument.getElementById('foxblock-progress-bar'); | |
const $alerts = frameDocument.getElementById('foxblock-alerts'); | |
$current.value = ''; | |
$targets.value = ''; | |
$interval.value = '2'; | |
const intervalValues = [3000, 2000, 1000, 500, 250]; | |
function intervalWait() { | |
const ms = Number(intervalValues[parseInt($interval.value)]) || 1000; | |
return new Promise((resolve) => setInterval(resolve, ms)); | |
} | |
function alert(message) { | |
const $alert = frameDocument.createElement('div'); | |
$alert.className = 'alert alert-danger alert-dismissible fade show'; | |
const $alertMessage = frameDocument.createElement('span'); | |
$alertMessage.innerText = message; | |
const $alertButton = frameDocument.createElement('button'); | |
$alertButton.className = 'btn-close'; | |
$alertButton.type = 'button'; | |
$alert.appendChild($alertMessage); | |
$alert.appendChild($alertButton); | |
function close() { | |
$alert.remove(); | |
if ($alerts.getElementsByClassName('alert').length < 1) { | |
$alerts.classList.add('d-none'); | |
} | |
} | |
$alertButton.addEventListener('click', close); | |
$alertButton.addEventListener('keydown', (ev) => { | |
if (ev.key === 'Enter') close(); | |
}); | |
$alerts.appendChild($alert); | |
$alerts.classList.remove('d-none'); | |
} | |
let locked = false; | |
async function executeStep(blogCurrent, blogTarget) { | |
try { | |
const meta = await perform(blogCurrent, blogTarget); | |
if (!meta.ok) { | |
alert(`${blogTarget}: ${meta.msg || 'Unknown error (remote)'}`); | |
} | |
} catch (err) { | |
console.error(err); | |
alert(`${blogTarget}: ${err.message || 'Unknown error (internal)'}`); | |
} | |
await intervalWait(); | |
} | |
async function execute() { | |
if (locked) return; | |
locked = true; | |
$button.setAttribute('disabled', ''); | |
$buttonSpinner.classList.remove('d-none'); | |
try { | |
const blogCurrent = formatBlog($current.value); | |
const blogTargets = formatBlogList($targets.value); | |
if (!blogCurrent) { | |
alert('No current blog specified'); | |
return; | |
} | |
if (blogTargets.length < 1) { | |
alert('No target blogs specified'); | |
return; | |
} | |
$progress.style.width = '0%'; | |
for (let i = 0, length = blogTargets.length; i < length; i++) { | |
await executeStep(blogCurrent, blogTargets[i]); | |
$progress.style.width = Math.floor(((i + 1) / length) * 100) + '%'; | |
} | |
} finally { | |
$button.removeAttribute('disabled'); | |
$buttonSpinner.classList.add('d-none'); | |
locked = false; | |
} | |
} | |
$form.addEventListener('submit', (ev) => { | |
ev.preventDefault(); | |
execute().catch((err) => console.error(err)); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment