Skip to content

Instantly share code, notes, and snippets.

@ALERTua
Last active December 2, 2025 09:38
Show Gist options
  • Select an option

  • Save ALERTua/e555998e9c47d99d2bf470f5c9afb3fc to your computer and use it in GitHub Desktop.

Select an option

Save ALERTua/e555998e9c47d99d2bf470f5c9afb3fc to your computer and use it in GitHub Desktop.
Block users on DOU.ua forums and hide their comments
// ==UserScript==
// @name DOU.ua Forum User Blocker
// @namespace http://tampermonkey.net/
// @version 1.0.20
// @description Block users on DOU.ua forums and hide their comments
// @author You
// @homepage https://gist.github.com/ALERTua/e555998e9c47d99d2bf470f5c9afb3fc
// @downloadURL https://gist.githubusercontent.com/ALERTua/e555998e9c47d99d2bf470f5c9afb3fc/raw
// @updateURL https://gist.githubusercontent.com/ALERTua/e555998e9c47d99d2bf470f5c9afb3fc/raw
// @match *.dou.ua/forums/topic/*
// @match *.dou.ua/lenta/*/*
// @match *.dou.ua/blogs/*
// @match *.dou.ua/articles/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Configuration
const STORAGE_KEY = 'douForumBlockedUsers';
const HIDDEN_TEXT = 'Hidden';
const DEBUG_MODE = false; // Set to false to disable logging
const PERF_MODE = false; // Set to false to disable logging
// Modern CSS styles
const styles = `
.dou-user-blocker-btn {
background: linear-gradient(135deg, #bcbcbc 0%, #a8a8a8 100%);
color: #333;
border: none;
border-radius: 6px;
padding: 4px 8px;
font-size: 0.75em;
cursor: pointer;
transition: all 0.3s ease;
margin-left: 8px;
font-weight: 500;
}
.dou-user-blocker-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.dou-user-blocker-btn.blocked {
background: linear-gradient(135deg, #a0a0a0 0%, #8c8c8c 100%);
}
.dou-user-blocker-btn.blocked:hover {
background: linear-gradient(135deg, #8c8c8c 0%, #a0a0a0 100%);
}
.dou-user-blocker-management {
position: fixed;
bottom: 20px;
right: 20px;
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
border: none;
border-radius: 12px;
padding: 12px 16px;
cursor: pointer;
z-index: 10000;
transition: all 0.3s ease;
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
font-weight: 600;
font-size: 0.9em;
}
.dou-user-blocker-management:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
.dou-user-blocker-management::before {
content: "🚫";
margin-right: 8px;
}
.dou-user-blocker-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 16px;
padding: 24px;
z-index: 10001;
width: 90%;
max-width: 400px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
border: 1px solid rgba(255,255,255,0.2);
}
.dou-user-blocker-panel.dark-theme {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
}
.dou-user-blocker-title {
margin: 0 0 16px 0;
font-size: 1.4em;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-title {
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dou-user-blocker-list {
list-style: none;
padding: 0;
margin: 0;
}
.dou-user-blocker-item {
background: rgba(102, 126, 234, 0.1);
margin-bottom: 12px;
padding: 16px;
border-radius: 8px;
border-left: 4px solid #667eea;
transition: all 0.3s ease;
}
.dou-user-blocker-item:hover {
background: rgba(102, 126, 234, 0.15);
transform: translateX(4px);
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-item {
background: rgba(116, 185, 255, 0.1);
border-left-color: #74b9ff;
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-item:hover {
background: rgba(116, 185, 255, 0.15);
}
.dou-user-blocker-nickname {
font-weight: 600;
font-size: 1.1em;
margin-bottom: 4px;
color: #2c3e50;
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-nickname {
color: #ecf0f1;
}
.dou-user-blocker-profile {
font-size: 0.85em;
color: #7f8c8d;
margin-bottom: 8px;
word-break: break-all;
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-profile {
color: #bdc3c7;
}
.dou-user-blocker-unblock {
background: linear-gradient(135deg, #c0c0c0 0%, #a0a0a0 100%);
color: #333;
border: none;
border-radius: 6px;
padding: 6px 12px;
font-size: 0.85em;
cursor: pointer;
transition: all 0.3s ease;
float: right;
}
.dou-user-blocker-unblock:hover {
background: linear-gradient(135deg, #a0a0a0 0%, #c0c0c0 100%);
transform: translateY(-1px);
}
.dou-user-blocker-empty {
text-align: center;
color: #7f8c8d;
font-style: italic;
padding: 20px;
}
.dou-user-blocker-panel.dark-theme .dou-user-blocker-empty {
color: #bdc3c7;
}
.dou-user-blocker-close {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 0.9em;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 16px;
width: 100%;
}
.dou-user-blocker-close:hover {
background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%);
}
.dou-user-blocker-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
backdrop-filter: blur(4px);
}
.dou-user-blocker-spoiler {
border-collapse: collapse;
list-style: none;
font: .625em Roboto,Arial,sans-serif;
font-size: 13px;
margin: 0;
padding: 0;
border: 0;
vertical-align: top;
cursor: pointer;
outline: 0;
border-bottom: 1px dashed #606983;
text-decoration: none;
margin-right: 4px;
color: #9e9e9e;
border-color: #9e9e9e;
}
.dou-user-blocker-spoiler:hover {
border-color: #7f8c8d;
color: #7f8c8d;
}
.dou-user-blocker-hidden {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
color: #ffffff;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-style: italic;
border: 2px dashed rgba(255, 255, 255, 0.3);
}
.dou-user-blocker-hidden:hover {
background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%);
transform: scale(1.02);
}
@media (prefers-color-scheme: dark) {
.dou-user-blocker-panel {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
}
.dou-user-blocker-title {
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dou-user-blocker-nickname {
color: #ecf0f1;
}
.dou-user-blocker-profile {
color: #bdc3c7;
}
}
`;
// Logging utility
function log(message, data = null) {
if (DEBUG_MODE) {
const timestamp = new Date().toISOString();
if (data) {
console.log(`[DOU-Blocker:${timestamp}] ${message}`, data);
} else {
console.log(`[DOU-Blocker:${timestamp}] ${message}`);
}
}
}
function logError(message, error = null) {
if (DEBUG_MODE) {
const timestamp = new Date().toISOString();
console.error(`[DOU-Blocker:${timestamp}] ERROR: ${message}`, error);
}
}
function logPerformance(label, startTime) {
if (DEBUG_MODE && PERF_MODE) {
const duration = performance.now() - startTime;
console.log(`[DOU-Blocker:PERF] ${label} took ${duration.toFixed(2)}ms`);
}
}
// Initialize blocked users list from browser storage
let blockedUsers = [];
try {
const stored = localStorage.getItem(STORAGE_KEY);
blockedUsers = stored ? JSON.parse(stored) : [];
log(`Loaded ${blockedUsers.length} blocked users from storage`, blockedUsers);
} catch (error) {
logError('Failed to load blocked users from storage', error);
blockedUsers = [];
}
// Add styles to page
function addStyles() {
const startTime = performance.now();
try {
if (document.getElementById('dou-user-blocker-styles')) {
log('Styles already exist, skipping');
return;
}
const styleElement = document.createElement('style');
styleElement.id = 'dou-user-blocker-styles';
styleElement.textContent = styles;
if (!document.head) {
logError('Document head not available for style injection');
return;
}
document.head.appendChild(styleElement);
logPerformance('addStyles', startTime);
log('Styles added successfully');
} catch (error) {
logError('Failed to add styles', error);
logPerformance('addStyles (failed)', startTime);
}
}
// Save blocked users to browser storage
function saveBlockedUsers() {
const startTime = performance.now();
try {
const beforeCount = blockedUsers.length;
const uniqueUsers = blockedUsers.filter((user, index, self) =>
index === self.findIndex(u => u.profileUrl === user.profileUrl)
);
const afterCount = uniqueUsers.length;
const duplicatesRemoved = beforeCount - afterCount;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(uniqueUsers));
blockedUsers = uniqueUsers;
log(`Saved ${afterCount} blocked users to storage (${duplicatesRemoved} duplicates removed)`);
logPerformance('saveBlockedUsers', startTime);
} catch (storageError) {
logError('Failed to save to localStorage (quota exceeded?)', storageError);
logPerformance('saveBlockedUsers (failed)', startTime);
}
} catch (error) {
logError('Error in saveBlockedUsers', error);
logPerformance('saveBlockedUsers (error)', startTime);
}
}
// Extract user info from profile URL
function getUserInfo(profileUrl) {
if (!profileUrl) return null;
const cleanUrl = profileUrl.split('?')[0].trim();
const match = cleanUrl.match(/dou\.ua\/users\/([^\/]+)\//);
if (match) {
return {
username: match[1],
profileUrl: cleanUrl
};
}
return null;
}
// Restore all comments for a specific user (remove spoilers)
function restoreUserComments(profileUrl) {
const startTime = performance.now();
let restoredCount = 0;
try {
log(`Restoring comments for user: ${profileUrl}`);
// Find all comments that have spoilers and belong to this user
const spoiledComments = document.querySelectorAll('.dou-user-blocker-has-spoiler');
log(`Found ${spoiledComments.length} spoiled comments to check`);
spoiledComments.forEach(commentElement => {
try {
// Check if this comment belongs to the user we're restoring
const userLinks = commentElement.closest('[class*="comment"], tr, div')?.querySelectorAll('a[href*="/users/"]');
if (userLinks) {
for (const link of userLinks) {
const userInfo = getUserInfo(link.href);
if (userInfo && userInfo.profileUrl === profileUrl) {
// This is a comment by the user we're unblocking - remove spoiler
const contentDiv = commentElement.querySelector('.dou-user-blocker-content');
const spoilerBtn = commentElement.querySelector('.dou-user-blocker-spoiler');
if (contentDiv && spoilerBtn) {
commentElement.innerHTML = contentDiv.innerHTML;
commentElement.classList.remove('dou-user-blocker-has-spoiler');
restoredCount++;
log(`Removed spoiler from comment by ${userInfo.username}`);
}
break; // Found the user, no need to check more links
}
}
}
} catch (commentError) {
logError('Error processing spoiled comment', commentError);
}
});
log(`restoreUserComments: restored ${restoredCount} comments for ${profileUrl}`);
logPerformance('restoreUserComments', startTime);
} catch (error) {
logError('Error in restoreUserComments', error);
logPerformance('restoreUserComments (error)', startTime);
}
return restoredCount;
}
// Update all block buttons for a specific user
function updateUserButtons(profileUrl, isBlocked) {
const startTime = performance.now();
let updatedCount = 0;
try {
// Find all block buttons for this user
document.querySelectorAll('.dou-user-blocker-btn').forEach(button => {
try {
// Find the associated user info by looking at nearby user links
const postAuthorDiv = button.closest('.b-post-author');
if (postAuthorDiv) {
const userLink = postAuthorDiv.querySelector('a[href*="/users/"].avatar');
if (userLink) {
const userInfo = getUserInfo(userLink.href);
if (userInfo && userInfo.profileUrl === profileUrl) {
// Update this button
button.textContent = isBlocked ? 'Unblock' : 'Block';
button.className = `dou-user-blocker-btn ${isBlocked ? 'blocked' : ''}`;
button.title = isBlocked ? 'Unblock user' : 'Block user';
updatedCount++;
}
}
}
} catch (buttonError) {
logError('Error updating individual button', buttonError);
}
});
log(`updateUserButtons: updated ${updatedCount} buttons for ${profileUrl} (blocked: ${isBlocked})`);
logPerformance('updateUserButtons', startTime);
} catch (error) {
logError('Error in updateUserButtons', error);
logPerformance('updateUserButtons (error)', startTime);
}
return updatedCount;
}
// Hide comments from blocked users
function hideBlockedComments() {
const startTime = performance.now();
let processedCount = 0;
let hiddenCount = 0;
let skippedAlreadyHidden = 0;
try {
// Find all comment containers directly
const commentContainers = document.querySelectorAll('.comment');
log(`Found ${commentContainers.length} comment containers to process`);
commentContainers.forEach((commentContainer, index) => {
try {
// Extract user info from the user link within this comment
const userLink = commentContainer.querySelector('a[href*="/users/"].avatar');
if (!userLink) {
log(`Comment ${index} has no user link, skipping`);
return;
}
const userInfo = getUserInfo(userLink.href);
if (!userInfo) {
log(`Comment ${index} has invalid user link: ${userLink.href}`);
return;
}
processedCount++;
const isBlocked = blockedUsers.some(user => user.profileUrl === userInfo.profileUrl);
// Hide the comment content - prioritize finding the text container with paragraph content
let commentText = commentContainer.querySelector('.comment_text');
if (!commentText) {
commentText = commentContainer.querySelector('.comment-text, .text, p');
}
if (!commentText) {
commentText = commentContainer.querySelector('[class*="comment"], [class*="text"] p');
}
// If we found a p element, use its parent instead to wrap the whole text block
if (commentText && commentText.tagName === 'P' && commentText.parentElement) {
commentText = commentText.parentElement;
}
log(`Processing comment ${index} by ${userInfo.username}${isBlocked ? ' (blocked)' : ' (not blocked)'}`);
log(`Found comment text element: ${commentText?.tagName} with classes: ${commentText?.className}`);
if (commentText) {
log(`Comment text has content: ${commentText.textContent?.substring(0, 50)}...`);
} else {
log('No comment text element found, checking container structure:');
log(`Container classes: ${commentContainer.className}`);
const paragraphs = commentContainer.querySelectorAll('p');
log(`Found ${paragraphs.length} paragraph elements in container`);
}
if (isBlocked) {
hiddenCount++;
if (commentText && !commentText.classList.contains('dou-user-blocker-has-spoiler')) {
// Wrap the content in a spoiler
const originalContent = commentText.innerHTML;
commentText.innerHTML = `
<span class="dou-user-blocker-spoiler">${HIDDEN_TEXT}</span>
<span class="dou-user-blocker-content" style="display: none;">${originalContent}</span>
`;
commentText.classList.add('dou-user-blocker-has-spoiler');
// Add click handler to reveal content temporarily
const spoilerBtn = commentText.querySelector('.dou-user-blocker-spoiler');
const contentDiv = commentText.querySelector('.dou-user-blocker-content');
let autoHideTimer = null;
spoilerBtn.addEventListener('click', function() {
if (contentDiv.style.display === 'none') {
// Reveal content
contentDiv.style.display = 'inline';
this.style.display = 'none'; // Hide the spoiler button
// Auto-hide after 5 seconds
autoHideTimer = setTimeout(() => {
if (contentDiv.style.display !== 'none') {
contentDiv.style.display = 'none';
this.style.display = 'inline'; // Show spoiler again
this.textContent = HIDDEN_TEXT;
autoHideTimer = null;
}
}, 5000);
}
});
log(`Added spoiler to comment by blocked user ${userInfo.username}`);
} else if (commentText && commentText.classList.contains('dou-user-blocker-has-spoiler')) {
skippedAlreadyHidden++;
}
} else {
// Make sure unblocked users' comments are shown
if (commentText && commentText.classList.contains('dou-user-blocker-has-spoiler')) {
const contentDiv = commentText.querySelector('.dou-user-blocker-content');
const spoilerBtn = commentText.querySelector('.dou-user-blocker-spoiler');
if (contentDiv && spoilerBtn) {
commentText.innerHTML = contentDiv.innerHTML;
commentText.classList.remove('dou-user-blocker-has-spoiler');
log(`Removed spoiler from comment by unblocked user ${userInfo.username}`);
}
}
}
} catch (commentError) {
logError(`Error processing comment ${index}`, commentError);
}
});
log(`hideBlockedComments: processed ${processedCount} comments, hidden ${hiddenCount}, skipped ${skippedAlreadyHidden} already hidden`);
logPerformance('hideBlockedComments', startTime);
} catch (error) {
logError('Error in hideBlockedComments', error);
logPerformance('hideBlockedComments (error)', startTime);
}
}
// Add block/unblock buttons to user profiles in comments
function addBlockButtons() {
const startTime = performance.now();
let processedCount = 0;
let addedCount = 0;
try {
// Process only .b-post-author elements within .comment containers
const postAuthorElements = document.querySelectorAll('.comment .b-post-author');
log(`Found ${postAuthorElements.length} post author elements in comments`);
postAuthorElements.forEach((postAuthorDiv, index) => {
try {
// Skip if button already exists in this post author div
if (postAuthorDiv.querySelector('.dou-user-blocker-btn')) {
return;
}
// Find the user avatar link
const avatarLink = postAuthorDiv.querySelector('a[href*="/users/"].avatar');
if (!avatarLink || avatarLink.closest('.sup-users')) {
return;
}
const userInfo = getUserInfo(avatarLink.href);
if (!userInfo) return;
processedCount++;
const blockBtn = document.createElement('button');
// Update button state dynamically based on current blocked status
function updateButtonState() {
const currentlyBlocked = blockedUsers.some(user => user.profileUrl === userInfo.profileUrl);
blockBtn.className = `dou-user-blocker-btn ${currentlyBlocked ? 'blocked' : ''}`;
blockBtn.textContent = currentlyBlocked ? 'Unblock' : 'Block';
blockBtn.title = currentlyBlocked ? 'Unblock user' : 'Block user';
}
updateButtonState();
blockBtn.addEventListener('click', function(e) {
const clickTime = performance.now();
try {
e.preventDefault();
e.stopPropagation();
const currentlyBlocked = blockedUsers.some(user => user.profileUrl === userInfo.profileUrl);
log(`Block button clicked for user: ${userInfo.username} (currently ${currentlyBlocked ? 'blocked' : 'unblocked'})`);
if (currentlyBlocked) {
// Remove from blocked list
blockedUsers = blockedUsers.filter(user => user.profileUrl !== userInfo.profileUrl);
log(`Unblocked user: ${userInfo.username}`);
// Restore all comments for this user
const restoredComments = restoreUserComments(userInfo.profileUrl);
log(`Restored ${restoredComments} comments for user: ${userInfo.username}`);
// Ensure any remaining hidden comments are properly handled
hideBlockedComments();
} else {
// Get full name from the avatar link text
const avatarLink = postAuthorDiv.querySelector('a[href*="/users/"].avatar');
const fullName = avatarLink ? avatarLink.textContent.trim() : userInfo.username;
// Add to blocked list
blockedUsers.push({
username: userInfo.username,
fullName: fullName,
profileUrl: userInfo.profileUrl,
blockedAt: new Date().toISOString()
});
log(`Blocked user: ${fullName} (${userInfo.username})`);
}
saveBlockedUsers();
hideBlockedComments();
updateUserButtons(userInfo.profileUrl, !currentlyBlocked);
updateManagementButton();
updateButtonState(); // Update this button's state
logPerformance('block button click handler', clickTime);
} catch (clickError) {
logError('Error in block button click handler', clickError);
logPerformance('block button click handler (error)', clickTime);
}
});
// Insert before the comment-link (timestamp)
const commentLink = postAuthorDiv.querySelector('.comment-link');
if (commentLink) {
commentLink.insertAdjacentHTML('beforebegin', ' ');
commentLink.parentNode.insertBefore(blockBtn, commentLink);
addedCount++;
//log(`Added button before comment-link for ${userInfo.username}`);
}
} catch (error) {
logError(`Error processing post author element ${index}`, error);
}
});
log(`addBlockButtons: processed ${processedCount} post authors, added ${addedCount} buttons`);
logPerformance('addBlockButtons', startTime);
} catch (error) {
logError('Error in addBlockButtons', error);
logPerformance('addBlockButtons (error)', startTime);
}
}
// Update management button count
function updateManagementButton() {
const startTime = performance.now();
try {
const existingBtn = document.getElementById('dou-user-blocker-management');
if (existingBtn) {
const oldText = existingBtn.textContent;
existingBtn.textContent = `Blocked: ${blockedUsers.length}`;
log(`Updated management button: "${oldText}" → "${existingBtn.textContent}"`);
} else {
log('Management button not found for update');
}
logPerformance('updateManagementButton', startTime);
} catch (error) {
logError('Error in updateManagementButton', error);
logPerformance('updateManagementButton (error)', startTime);
}
}
// Create management panel
function createManagementPanel() {
// Remove existing panel if open
const existingPanel = document.getElementById('dou-user-blocker-panel');
if (existingPanel) {
existingPanel.remove();
}
const overlay = document.createElement('div');
overlay.className = 'dou-user-blocker-overlay';
overlay.addEventListener('click', function() {
document.body.removeChild(overlay);
const panel = document.getElementById('dou-user-blocker-panel');
if (panel) panel.remove();
});
const panel = document.createElement('div');
panel.className = 'dou-user-blocker-panel';
panel.id = 'dou-user-blocker-panel';
// Detect dark theme preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
panel.classList.add('dark-theme');
}
const title = document.createElement('h3');
title.className = 'dou-user-blocker-title';
title.textContent = 'Blocked Users';
panel.appendChild(title);
if (blockedUsers.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.className = 'dou-user-blocker-empty';
emptyMsg.textContent = 'No blocked users';
panel.appendChild(emptyMsg);
} else {
const list = document.createElement('ul');
list.className = 'dou-user-blocker-list';
blockedUsers.forEach(user => {
const item = document.createElement('li');
item.className = 'dou-user-blocker-item';
const nickname = document.createElement('div');
nickname.className = 'dou-user-blocker-nickname';
nickname.textContent = user.fullName || user.username;
const profile = document.createElement('div');
profile.className = 'dou-user-blocker-profile';
profile.textContent = user.profileUrl;
const unblockBtn = document.createElement('button');
unblockBtn.className = 'dou-user-blocker-unblock';
unblockBtn.textContent = 'Unblock';
unblockBtn.addEventListener('click', function() {
const unblockTime = performance.now();
try {
log(`Management panel: unblocking user ${user.username}`);
// Remove from blocked list
blockedUsers = blockedUsers.filter(u => u.profileUrl !== user.profileUrl);
// Restore all comments for this user
const restoredComments = restoreUserComments(user.profileUrl);
log(`Management panel: restored ${restoredComments} comments for user: ${user.username}`);
// Update all buttons for this user
const updatedButtons = updateUserButtons(user.profileUrl, false);
log(`Management panel: updated ${updatedButtons} buttons for user: ${user.username}`);
saveBlockedUsers();
hideBlockedComments();
// Close panel and refresh
document.body.removeChild(overlay);
document.body.removeChild(panel);
createManagementPanel(); // Refresh the panel
logPerformance('management panel unblock handler', unblockTime);
} catch (unblockError) {
logError('Error in management panel unblock handler', unblockError);
logPerformance('management panel unblock handler (error)', unblockTime);
}
});
item.appendChild(nickname);
item.appendChild(profile);
item.appendChild(unblockBtn);
list.appendChild(item);
});
panel.appendChild(list);
}
const closeBtn = document.createElement('button');
closeBtn.className = 'dou-user-blocker-close';
closeBtn.textContent = 'Close';
closeBtn.addEventListener('click', function() {
document.body.removeChild(overlay);
document.body.removeChild(panel);
});
panel.appendChild(closeBtn);
document.body.appendChild(overlay);
document.body.appendChild(panel);
}
// Add management button
function addManagementButton() {
const existingBtn = document.getElementById('dou-user-blocker-management');
if (existingBtn) existingBtn.remove();
const managementBtn = document.createElement('button');
managementBtn.className = 'dou-user-blocker-management';
managementBtn.id = 'dou-user-blocker-management';
managementBtn.textContent = `Blocked: ${blockedUsers.length}`;
managementBtn.addEventListener('click', createManagementPanel);
document.body.appendChild(managementBtn);
}
// Initialize the script
function init() {
const startTime = performance.now();
log('Initializing DOU.ua User Blocker script');
try {
addStyles();
log('Styles initialization completed');
hideBlockedComments();
log('Initial comment hiding completed');
addBlockButtons();
log('Initial button addition completed');
addManagementButton();
log('Management button addition completed');
// Set up observer for dynamically loaded content with debouncing
let observerTimeout;
const observer = new MutationObserver(function(mutations) {
// Clear any pending observer callback
if (observerTimeout) {
clearTimeout(observerTimeout);
}
// Debounce the observer callback to prevent loops
observerTimeout = setTimeout(() => {
try {
const addedNodes = mutations.reduce((count, mutation) => {
return count + mutation.addedNodes.length;
}, 0);
if (addedNodes > 0) {
log(`Observer triggered: ${mutations.length} mutations, ${addedNodes} new nodes`);
// Check if any new nodes contain user links (comments/post authors) before processing
let hasUserContent = false;
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const userLinks = node.querySelectorAll ? node.querySelectorAll('a[href*="/users/"]') : [];
if (userLinks.length > 0 || node.matches && node.matches('a[href*="/users/"]')) {
hasUserContent = true;
}
}
});
});
// Only process if we detected new user content
if (hasUserContent) {
log('Detected new user content, processing comments and buttons');
hideBlockedComments();
addBlockButtons();
} else {
log('No new user content detected, skipping processing');
}
}
} catch (observerError) {
logError('Error in observer callback', observerError);
}
}, 100); // 100ms debounce
});
// Observe the entire document body comprehensively to catch all dynamic content
// This ensures expand-thread and other AJAX-loaded content is detected
observer.observe(document.body, {
childList: true,
subtree: true
});
log('Observing entire document body for all dynamic content changes');
log('Observer setup completed, watching entire document body for all dynamic content changes');
logPerformance('init', startTime);
log('DOU.ua User Blocker initialization completed successfully');
} catch (error) {
logError('Error during script initialization', error);
logPerformance('init (error)', startTime);
}
}
// Start the script when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment