Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save TwoXTwentyOne/8ceffec11bdb9cfe231ee1d41af5554a to your computer and use it in GitHub Desktop.

Select an option

Save TwoXTwentyOne/8ceffec11bdb9cfe231ee1d41af5554a to your computer and use it in GitHub Desktop.
TamperMonkey - Unfollow Unverified Account
// ==UserScript==
// @name X (Twitter) - v1.20 + Verified-No-Followback + Start/Stop Toggle + Dark Mode (All Text White) + Auto-Restart Toggle
// @namespace http://tampermonkey.net/
// @version 1.20
// @description Contextual counters, S=Start, A=Abort, P=Pause, E=Export, ignore list, plus an option to unfollow verified if they don’t follow back, with Start/Stop toggles and Dark Mode (all text white), and optional auto-restart after 60 seconds on completion
// @match https://x.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ------------------- DARK MODE GLOBALS & STYLE -------------------
let darkModeEnabled = localStorage.getItem('my_dark_mode') === "true";
function initDarkModeStyles() {
const style = document.createElement("style");
style.textContent = `
.dark-mode {
background-color: #333 !important;
color: #fff !important;
border: 1px solid #555 !important;
}
.dark-mode * {
color: #fff !important;
}
.dark-mode button {
background-color: #555 !important;
color: #fff !important;
}
.dark-mode textarea {
background-color: #555 !important;
color: #fff !important;
border: 1px solid #777 !important;
}
.dark-mode a {
color: #66aaff !important;
}
`;
document.head.appendChild(style);
}
initDarkModeStyles();
// ------------------- STORAGE KEYS -------------------
const panelId = '__sedgwickz__unfollow_id';
const storageKeyIgnoreList = '__my_ignore_list';
const storageKeyUnfollowLimit = '__my_unfollow_limit';
const storageKeyRemoveLimit = '__my_remove_limit';
const storageKeyVerifiedNoFollowback = '__unfollow_verified_no_followback';
const storageKeyAutoRestart = '__my_auto_restart_enabled'; // NEW
// ------------------- GLOBALS -------------------
let ignoreList = loadIgnoreList();
let isPaused = false;
let abortNow = false;
let unfollowedCount = 0;
let removedFollowersCount = 0;
let userUnfollowLimit = loadLimit(storageKeyUnfollowLimit, 50000);
let userRemoveLimit = loadLimit(storageKeyRemoveLimit, 50000);
let unfollowVerifiedNoFollowback = loadBooleanSetting(storageKeyVerifiedNoFollowback, false);
let autoRestartEnabled = localStorage.getItem(storageKeyAutoRestart) !== "false"; // default true
const seenHandles = new Set();
const allUsersArr = [];
const recentProcessed = [];
let recentProcessedDiv = null;
let countDiv = null;
let ignoreTextArea = null;
let isRunning = false;
let unfollowButton = null;
let removeFollowersButton = null;
// --------------- MAIN ENTRY ---------------
function start() {
if (isFollowingPage() || isFollowersPage()) {
createPanel();
} else {
removePanel();
}
}
function removePanel() {
const panel = document.getElementById(panelId);
if (panel) panel.remove();
}
// Refresh the page every 25 min
setTimeout(() => {
location.reload();
}, 1500000);
// SPA transitions
if (window?.navigation?.addEventListener) {
window.navigation.addEventListener("navigate", () => {
setTimeout(start, 200);
});
}
if (window.onurlchange === null) {
window.addEventListener('urlchange', () => {
setTimeout(start, 200);
});
}
// --------------- KEYBOARD SHORTCUTS ---------------
document.addEventListener('keydown', (e) => {
const k = e.key.toLowerCase();
if (k === 'p') {
isPaused = !isPaused;
notify(isPaused ? "Paused (P key)" : "Resumed (P key)");
updatePauseButtonText(isPaused);
}
else if (k === 'a') {
abortNow = true;
notify("Abort requested (A key). Loops will halt!");
}
else if (k === 'e') {
if (isFollowingPage() || isFollowersPage()) {
notify("Keyboard Export triggered...");
exportChronologicalList();
}
}
else if (k === 's') {
if (!isRunning && (isFollowingPage() || isFollowersPage())) {
notify("Starting main routine (S key)...");
startMainRoutine();
}
}
});
function updatePauseButtonText(isPaused) {
const panel = document.getElementById(panelId);
if (!panel) return;
const pauseBtn = panel.querySelector('button[data-role="pauseBtn"]');
if (!pauseBtn) return;
pauseBtn.innerText = isPaused ? "Resume" : "Pause";
}
start(); // On script load
// --------------- CREATE UI PANEL ---------------
function createPanel() {
removePanel();
abortNow = false;
isRunning = false;
const container = document.createElement('div');
container.id = panelId;
applyStyles(container, {
position: 'fixed',
zIndex: 9999999,
padding: '10px',
top: '0',
right: '0',
background: '#e0e0e0',
border: '1px solid #ccc',
borderRadius: '5px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'flex-start',
gap: '6px',
width: '280px'
});
if (darkModeEnabled) {
container.classList.add("dark-mode");
}
document.body.appendChild(container);
// Buttons
if (isFollowingPage()) {
unfollowButton = createButton("Remove Unverified Accounts - Start", '#28a745');
container.appendChild(unfollowButton);
unfollowButton.addEventListener('click', () => {
if (!isRunning) {
startMainRoutine();
unfollowButton.innerText = "Stop Unfollowing";
} else {
abortNow = true;
notify("Stop requested from button!");
unfollowButton.innerText = "Stopping...";
}
});
}
if (isFollowersPage()) {
removeFollowersButton = createButton("Remove Unverified Followers", '#dc3545');
container.appendChild(removeFollowersButton);
removeFollowersButton.addEventListener('click', () => {
if (!isRunning) {
startMainRoutine();
removeFollowersButton.innerText = "Stop Removing";
} else {
abortNow = true;
notify("Stop requested from button!");
removeFollowersButton.innerText = "Stopping...";
}
});
}
// Export
if (isFollowingPage() || isFollowersPage()) {
const exportButton = createButton("Export Chronological (Bottom->Top)", '#007bff');
container.appendChild(exportButton);
exportButton.addEventListener('click', async () => {
exportButton.innerText = "Gathering users from bottom...";
await exportChronologicalList();
exportButton.innerText = "Export Chronological (Bottom->Top)";
});
}
// Pause/Resume
const pauseButton = createButton("Pause", '#ffc107');
pauseButton.setAttribute('data-role', 'pauseBtn');
container.appendChild(pauseButton);
pauseButton.addEventListener('click', () => {
isPaused = !isPaused;
pauseButton.innerText = isPaused ? "Resume" : "Pause";
});
// NEW: Auto-restart toggle checkbox
const restartDiv = document.createElement('div');
restartDiv.style.display = 'flex';
restartDiv.style.alignItems = 'center';
restartDiv.style.marginTop = '6px';
const restartCheckbox = document.createElement('input');
restartCheckbox.type = 'checkbox';
restartCheckbox.checked = autoRestartEnabled;
restartCheckbox.id = 'restart_checkbox';
restartDiv.appendChild(restartCheckbox);
const restartLabel = document.createElement('label');
restartLabel.htmlFor = 'restart_checkbox';
restartLabel.innerText = "Auto-restart after finish (60s)";
restartLabel.style.marginLeft = '8px';
restartLabel.style.color = darkModeEnabled ? '#ddd' : '#222';
restartDiv.appendChild(restartLabel);
container.appendChild(restartDiv);
restartCheckbox.addEventListener('change', () => {
autoRestartEnabled = restartCheckbox.checked;
localStorage.setItem(storageKeyAutoRestart, autoRestartEnabled ? "true" : "false");
notify(`Auto-restart ${autoRestartEnabled ? "enabled" : "disabled"}`);
});
// Dark Mode Toggle Button
const darkModeToggle = createButton(darkModeEnabled ? "Light Mode" : "Dark Mode", '#6c757d');
container.appendChild(darkModeToggle);
darkModeToggle.addEventListener("click", () => {
darkModeEnabled = !darkModeEnabled;
localStorage.setItem('my_dark_mode', darkModeEnabled ? "true" : "false");
container.classList.toggle("dark-mode", darkModeEnabled);
darkModeToggle.innerText = darkModeEnabled ? "Light Mode" : "Dark Mode";
});
// Count Display
countDiv = document.createElement('div');
updateCountDisplay(countDiv);
container.appendChild(countDiv);
// Limits & Checkboxes (unfollow page)
if (isFollowingPage()) {
const limitLabel = document.createElement('div');
limitLabel.innerText = "Unfollow Limit (0=Unlimited):";
applyStyles(limitLabel, { fontWeight: 'bold', marginTop: '5px', color: '#444' });
container.appendChild(limitLabel);
const inputUnfollowLimit = document.createElement('input');
inputUnfollowLimit.type = 'number';
inputUnfollowLimit.min = '0';
inputUnfollowLimit.value = (userUnfollowLimit === Number.MAX_SAFE_INTEGER) ? '0' : String(userUnfollowLimit);
inputUnfollowLimit.style.width = '120px';
container.appendChild(inputUnfollowLimit);
const saveLimitBtn = createButton("Save Unfollow Limit", '#6c757d');
container.appendChild(saveLimitBtn);
saveLimitBtn.addEventListener('click', () => {
const newLimit = parseInt(inputUnfollowLimit.value, 10) || 0;
userUnfollowLimit = (newLimit === 0) ? Number.MAX_SAFE_INTEGER : newLimit;
localStorage.setItem(storageKeyUnfollowLimit, String(newLimit));
notify("Unfollow Limit saved!");
updateCountDisplay(countDiv);
});
const verifyDiv = document.createElement('div');
verifyDiv.style.marginTop = '6px';
verifyDiv.style.display = 'flex';
verifyDiv.style.alignItems = 'center';
const verifyCheckbox = document.createElement('input');
verifyCheckbox.type = 'checkbox';
verifyCheckbox.checked = unfollowVerifiedNoFollowback;
verifyCheckbox.id = '__verify_checkbox_id';
verifyDiv.appendChild(verifyCheckbox);
const verifyLabel = document.createElement('label');
verifyLabel.htmlFor = '__verify_checkbox_id';
verifyLabel.innerText = "Unfollow Verified Who Don’t Follow Me";
verifyLabel.style.marginLeft = '6px';
verifyLabel.style.color = '#222';
verifyDiv.appendChild(verifyLabel);
container.appendChild(verifyDiv);
verifyCheckbox.addEventListener('change', () => {
unfollowVerifiedNoFollowback = verifyCheckbox.checked;
localStorage.setItem(storageKeyVerifiedNoFollowback, String(unfollowVerifiedNoFollowback));
notify(
"Setting updated: " +
(unfollowVerifiedNoFollowback ? "Will" : "Won't") +
" remove verified who don't follow back."
);
});
}
// Remove limit (followers page)
if (isFollowersPage()) {
const limitLabel = document.createElement('div');
limitLabel.innerText = "Remove Limit (0=Unlimited):";
applyStyles(limitLabel, { fontWeight: 'bold', marginTop: '5px', color: '#444' });
container.appendChild(limitLabel);
const inputRemoveLimit = document.createElement('input');
inputRemoveLimit.type = 'number';
inputRemoveLimit.min = '0';
inputRemoveLimit.value = (userRemoveLimit === Number.MAX_SAFE_INTEGER) ? '0' : String(userRemoveLimit);
inputRemoveLimit.style.width = '120px';
container.appendChild(inputRemoveLimit);
const saveLimitBtn = createButton("Save Remove Limit", '#6c757d');
container.appendChild(saveLimitBtn);
saveLimitBtn.addEventListener('click', () => {
const newLimit = parseInt(inputRemoveLimit.value, 10) || 0;
userRemoveLimit = (newLimit === 0) ? Number.MAX_SAFE_INTEGER : newLimit;
localStorage.setItem(storageKeyRemoveLimit, String(newLimit));
notify("Remove Limit saved!");
updateCountDisplay(countDiv);
});
}
// Ignore List
const ignoreLabel = document.createElement('div');
ignoreLabel.innerText = "Ignore List (User won't be removed):";
applyStyles(ignoreLabel, { fontWeight: 'bold', marginTop: '10px', color: '#444' });
container.appendChild(ignoreLabel);
ignoreTextArea = document.createElement('textarea');
ignoreTextArea.rows = 4;
ignoreTextArea.cols = 22;
ignoreTextArea.value = ignoreList.join("\n");
container.appendChild(ignoreTextArea);
const saveIgnoreButton = createButton("Save Ignore List", '#6c757d');
container.appendChild(saveIgnoreButton);
saveIgnoreButton.addEventListener('click', () => {
const lines = ignoreTextArea.value.split("\n").map(s => s.trim()).filter(Boolean);
ignoreList = lines.map(h => h.toLowerCase());
saveIgnoreList(ignoreList);
notify("Ignore List saved!");
});
// Recently Processed
const recentLabel = document.createElement('div');
recentLabel.innerText = "Recently Processed (Last 25):";
applyStyles(recentLabel, { fontWeight: 'bold', marginTop: '10px', color: '#444' });
container.appendChild(recentLabel);
recentProcessedDiv = document.createElement('div');
applyStyles(recentProcessedDiv, {
maxHeight: '120px',
overflowY: 'auto',
border: '1px solid #ccc',
padding: '3px',
width: '100%'
});
container.appendChild(recentProcessedDiv);
updateRecentProcessedUI();
// Help / Shortcuts
const helpDiv = document.createElement('div');
helpDiv.innerHTML = `
<div style="font-weight:bold; margin-top:10px; color:#444;">Keyboard Shortcuts:</div>
<ul style="margin:0; padding-left:18px; font-size:12px; color:#333;">
<li><strong>S</strong>: Start Process</li>
<li><strong>A</strong>: Abort Immediately</li>
<li><strong>P</strong>: Pause/Resume</li>
<li><strong>E</strong>: Export Chronological</li>
</ul>
`;
container.appendChild(helpDiv);
const descDiv = document.createElement('div');
descDiv.innerHTML = `
<div style='font-weight: bold; color: #555;'>Feedback &amp; Support</div>
<div><a href='https://x.com/anon42' target='_blank' style='color: darkblue;'>@anon42</a></div>`;
applyStyles(descDiv, { textAlign: 'center' });
container.appendChild(descDiv);
applyHoverEffects(container);
}
// ~~~~~~~~~~~~~~~~~~~~~ MAIN ROUTINE (with conditional restart) ~~~~~~~~~~~~~~~~~~~~~
async function startMainRoutine() {
if (isRunning) return;
isRunning = true;
abortNow = false;
if (isFollowingPage()) {
notify("Starting unfollow routine...");
document.documentElement.scrollTo(0, 0);
await sleep(2000);
while (!abortNow && unfollowedCount < userUnfollowLimit) {
await unFollow();
}
if (abortNow) {
notify("Aborted unfollow routine!");
} else {
notify("Unfollow process completed!");
if (autoRestartEnabled) {
notify("Auto-restarting in 60 seconds...");
setTimeout(() => {
if (!abortNow) {
startMainRoutine();
}
}, 60000);
}
}
if (unfollowButton) {
unfollowButton.innerText = "Remove Unverified Accounts - Start";
}
}
else if (isFollowersPage()) {
notify("Starting remove-follower routine...");
document.documentElement.scrollTo(0, 0);
await sleep(2000);
while (!abortNow && removedFollowersCount < userRemoveLimit) {
await removeUnverifiedFollowers();
}
if (abortNow) {
notify("Aborted remove-follower routine!");
} else {
notify("Remove-follower process completed!");
if (autoRestartEnabled) {
notify("Auto-restarting in 60 seconds...");
setTimeout(() => {
if (!abortNow) {
startMainRoutine();
}
}, 60000);
}
}
if (removeFollowersButton) {
removeFollowersButton.innerText = "Remove Unverified Followers";
}
}
isRunning = false;
}
// ~~~~~~~~~~~~~~~~~~~~~ RECORD PROCESSED ~~~~~~~~~~~~~~~~~~~~~
function recordProcessedAction(handle, actionType) {
const entry = `${actionType} @${handle}`;
recentProcessed.unshift(entry);
if (recentProcessed.length > 25) recentProcessed.pop();
updateRecentProcessedUI();
}
function updateRecentProcessedUI() {
if (!recentProcessedDiv) return;
const lines = recentProcessed
.map(item => `<div style="font-size: 12px; color: #333;">${item}</div>`)
.join("");
recentProcessedDiv.innerHTML = lines || "<div style='font-size:12px; color:#999;'>No recent actions</div>";
}
// ~~~~~~~~~~~~~~~~~~~~~ UNFOLLOW LOGIC ~~~~~~~~~~~~~~~~~~~~~
async function unFollow() {
const container = document.querySelector('[aria-label*="Following"]');
if (!container) {
console.log("No main container found for 'Following'");
return;
}
const buttons = container.querySelectorAll("button[role='button']");
for (const item of buttons) {
if (unfollowedCount >= userUnfollowLimit || abortNow) break;
while (isPaused) {
await sleep(500);
if (abortNow) break;
}
if (abortNow) break;
if (item.innerText === 'Following') {
const accountElement = item.closest('[data-testid="UserCell"]');
if (!accountElement) continue;
const userHandle = getHandleFromUserCell(accountElement);
if (!userHandle) continue;
if (ignoreList.includes(userHandle.toLowerCase())) continue;
const verifiedIcon = accountElement.querySelector('svg[aria-label="Verified account"]');
const isVerified = !!verifiedIcon;
if (isVerified) {
if (!unfollowVerifiedNoFollowback) continue;
if (doesFollowMe(accountElement)) continue;
}
accountElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
highlightElement(accountElement);
await sleep(randomDelay(2000, 5000));
if (abortNow) break;
item.click();
await sleep(1000);
if (abortNow) break;
const confirmButton = getConfirmButton();
if (confirmButton) confirmButton.click();
unfollowedCount++;
updateCountDisplay(countDiv);
recordProcessedAction(userHandle, "Unfollowed");
}
}
document.documentElement.scrollTo(0, 999999999);
await sleep(2000);
}
function doesFollowMe(accountElement) {
const label = [...accountElement.querySelectorAll('span, div')]
.find(el => el.innerText && el.innerText.includes("Follows you"));
return !!label;
}
function getConfirmButton() {
return [...document.querySelectorAll("button[role='button']")]
.find(item => item.innerText === 'Unfollow');
}
// ~~~~~~~~~~~~~~~~~~~~~ REMOVE FOLLOWERS ~~~~~~~~~~~~~~~~~~~~~
async function removeUnverifiedFollowers() {
const container = document.querySelector('[aria-label*="Followers"]');
if (!container) {
console.log("No main container found for 'Followers'");
return;
}
const allUserCells = container.querySelectorAll('[data-testid="UserCell"]');
for (const cell of allUserCells) {
if (removedFollowersCount >= userRemoveLimit || abortNow) break;
while (isPaused) {
await sleep(500);
if (abortNow) break;
}
if (abortNow) break;
const userHandle = getHandleFromUserCell(cell);
if (!userHandle) continue;
if (ignoreList.includes(userHandle.toLowerCase())) continue;
const isVerified = !!cell.querySelector('svg[aria-label="Verified account"]');
if (!isVerified) {
cell.scrollIntoView({ behavior: 'smooth', block: 'center' });
highlightElement(cell);
await sleep(randomDelay(1500, 3000));
if (abortNow) break;
const menuButton = findThreeDotMenu(cell);
if (!menuButton) continue;
menuButton.click();
await sleep(1200);
if (abortNow) break;
const removeBtn = [...document.querySelectorAll('span, div')]
.find(el => el.innerText === 'Remove this follower');
if (!removeBtn) {
closeAnyMenu();
continue;
}
removeBtn.click();
await sleep(1000);
if (abortNow) break;
const confirmRemoveButton = [...document.querySelectorAll('span, div')]
.find(el => el.innerText === 'Remove');
if (confirmRemoveButton) confirmRemoveButton.click();
removedFollowersCount++;
updateCountDisplay(countDiv);
recordProcessedAction(userHandle, "Removed");
}
}
document.documentElement.scrollTo(0, 999999999);
await sleep(2000);
}
function findThreeDotMenu(cell) {
let menuButton = cell.querySelector('[aria-label="More"]');
if (!menuButton) menuButton = cell.querySelector('[data-testid="UserCellOverflowButton"]');
if (!menuButton) {
menuButton = [...cell.querySelectorAll('span, div, button')]
.find(el => el.innerText === '…' || el.innerText === '...');
}
return menuButton;
}
function closeAnyMenu() {
const overlay = document.querySelector('[data-testid="sheetDialog"]');
if (overlay) overlay.click();
}
function highlightElement(el) {
el.style.transition = "background-color 0.5s ease";
el.style.backgroundColor = "yellow";
setTimeout(() => el.style.backgroundColor = "", 2000);
}
// ~~~~~~~~~~~~~~~~~~~~~ EXPORT LOGIC ~~~~~~~~~~~~~~~~~~~~~
async function exportChronologicalList() {
const MAX_SCROLL_ITERATIONS = 200;
const MAX_STABLE_SCROLLS = 5;
let previousCount = 0;
let stableScrolls = 0;
for (let i = 0; i < MAX_SCROLL_ITERATIONS; i++) {
while (isPaused) {
await sleep(500);
if (abortNow) return;
}
if (abortNow) return;
gatherVisibleUsers();
window.scrollTo(0, document.body.scrollHeight);
await sleep(2000);
const currentCount = seenHandles.size;
if (currentCount === previousCount) {
stableScrolls++;
} else {
stableScrolls = 0;
}
previousCount = currentCount;
if (stableScrolls >= MAX_STABLE_SCROLLS) break;
}
gatherVisibleUsers();
const reversed = [...allUsersArr].reverse();
const csv = generateCSV(reversed);
downloadCSV(csv);
notify(`Exported ${reversed.length} users (oldest at the top)`);
}
function gatherVisibleUsers() {
let container = isFollowersPage()
? document.querySelector('[aria-label*="Followers"]')
: document.querySelector('[aria-label*="Following"]');
if (!container) {
console.log("No main container found for export");
return;
}
const cells = container.querySelectorAll('[data-testid="UserCell"]');
for (const cell of cells) {
const handle = getHandleFromUserCell(cell);
if (!handle) continue;
if (!seenHandles.has(handle)) {
seenHandles.add(handle);
const displayName = getDisplayNameFromCell(cell) || "";
const verified = !!cell.querySelector('svg[aria-label="Verified account"]');
allUsersArr.push({ handle, displayName, verified });
}
}
}
function getDisplayNameFromCell(cell) {
const nameEl = cell.querySelector('[data-testid="User-Name"] span');
if (nameEl && nameEl.innerText) return nameEl.innerText.trim();
const fallbackEl = cell.querySelector('span');
return fallbackEl ? fallbackEl.innerText.trim() : null;
}
function generateCSV(dataArray) {
const header = ["Handle", "DisplayName", "Verified"];
const rows = dataArray.map(user => [
user.handle,
user.displayName.replace(/,/g, ""),
user.verified ? "Yes" : "No"
]);
return [header.join(","), ...rows.map(r => r.join(","))].join("\n");
}
function downloadCSV(csv) {
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "x_users_chronological.csv";
a.click();
URL.revokeObjectURL(url);
}
// ~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~
function getHandleFromUserCell(cell) {
const link = cell.querySelector('a[href*="/"]');
if (!link) return null;
const urlPath = link.getAttribute('href') || '';
return urlPath.split('/').pop().replace('@', '').toLowerCase();
}
function isFollowingPage() {
return location.pathname.endsWith('/following');
}
function isFollowersPage() {
return location.pathname.endsWith('/followers');
}
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
function randomDelay(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function createButton(text, bgColor) {
const button = document.createElement('button');
button.innerText = text;
applyStyles(button, {
backgroundColor: bgColor,
color: '#fff',
border: 'none',
borderRadius: '5px',
padding: '8px 14px',
cursor: 'pointer',
fontWeight: 'bold',
marginRight: '5px'
});
return button;
}
function applyStyles(element, styles) {
Object.assign(element.style, styles);
}
function applyHoverEffects(container) {
const style = document.createElement('style');
style.textContent = `
#${panelId} button:hover { opacity: 0.8; }
#${panelId} button:active { transform: scale(0.95); }
`;
document.head.appendChild(style);
}
function updateCountDisplay(div) {
if (!div) return;
if (isFollowingPage()) {
const remaining = (userUnfollowLimit === Number.MAX_SAFE_INTEGER)
? 'Unlimited'
: Math.max(0, userUnfollowLimit - unfollowedCount);
div.innerHTML = `<div style="color: red">Unfollowed: ${unfollowedCount} | Remaining: ${remaining}</div>`;
}
else if (isFollowersPage()) {
const remaining = (userRemoveLimit === Number.MAX_SAFE_INTEGER)
? 'Unlimited'
: Math.max(0, userRemoveLimit - removedFollowersCount);
div.innerHTML = `<div style="color: purple">Removed: ${removedFollowersCount} | Remaining: ${remaining}</div>`;
} else {
div.innerHTML = "";
}
}
function notify(message) {
if (Notification.permission === "granted") {
new Notification(message);
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then(permission => {
if (permission === "granted") new Notification(message);
});
}
console.log(message);
}
function loadIgnoreList() {
try {
const data = localStorage.getItem(storageKeyIgnoreList);
if (!data) return [];
return JSON.parse(data);
} catch (e) {
return [];
}
}
function saveIgnoreList(list) {
localStorage.setItem(storageKeyIgnoreList, JSON.stringify(list));
}
function loadLimit(key, defaultValue) {
const data = localStorage.getItem(key);
if (!data) return defaultValue;
let val = parseInt(data, 10);
if (isNaN(val)) return defaultValue;
if (val === 0) return Number.MAX_SAFE_INTEGER;
return val;
}
function loadBooleanSetting(key, defaultVal) {
const data = localStorage.getItem(key);
if (!data) return defaultVal;
return data === "true";
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment