Blocks sponsored ads, tracking pixels and featured content on Reddit
// ==UserScript==
// @name Reddit Ad Blocker
// @namespace reddit-ad-blocker
// @version 2.1
// @description Blocks sponsored ads, tracking pixels and featured content on Reddit
// @author jwalley
// @match https://www.reddit.com/*
// @match https://old.reddit.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const LOG_PREFIX = '[Reddit Ad Blocker]';
const DEBUG = false;
function log(...args) {
if (DEBUG) console.log(LOG_PREFIX, ...args);
}
// ========================================
// 1. NETWORK INTERCEPTION - Block ad tracking URLs
// ========================================
const TRACKING_PATTERNS = [
'alb.reddit.com/i.gif', // Ad impression tracking pixels
'alb.reddit.com/skatepark', // Ad tracking beacon
'/shreddit/assets/pix/ads/', // Ad pixel assets
'/shreddit/assets/ad/', // Ad image assets
'id.rlcdn.com', // LiveRamp identity tracking
];
function isBlockedUrl(url) {
if (typeof url !== 'string') return false;
return TRACKING_PATTERNS.some(p => url.includes(p));
}
// fetch intercept
const originalFetch = window.fetch;
window.fetch = function (input, init) {
const url = typeof input === 'string' ? input : input?.url || '';
if (isBlockedUrl(url)) {
log('fetch blocked:', url.substring(0, 80));
return Promise.resolve(new Response('', { status: 204 }));
}
return originalFetch.apply(this, arguments);
};
// XMLHttpRequest intercept
const origXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
if (isBlockedUrl(url)) {
log('XHR blocked:', url.substring(0, 80));
this._blocked = true;
}
return origXHROpen.call(this, method, url, ...rest);
};
const origXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (...args) {
if (this._blocked) {
Object.defineProperty(this, 'readyState', { value: 4 });
Object.defineProperty(this, 'status', { value: 204 });
this.dispatchEvent(new Event('load'));
return;
}
return origXHRSend.apply(this, args);
};
// sendBeacon intercept
const origBeacon = navigator.sendBeacon?.bind(navigator);
if (origBeacon) {
navigator.sendBeacon = function (url, data) {
if (isBlockedUrl(url)) {
log('Beacon blocked:', url.substring(0, 80));
return true;
}
return origBeacon(url, data);
};
}
// ========================================
// 2. CSS STYLES - Directly hide ads
// ========================================
const css = document.createElement('style');
css.id = 'reddit-ad-blocker-css';
css.textContent = `
/* === MAIN AD POSTS === */
/* shreddit-ad-post: Reddit's official ad component */
shreddit-ad-post {
display: none !important;
}
/* HR separator following an ad post */
shreddit-ad-post + hr {
display: none !important;
}
/* === AD LINK COMPONENTS === */
shreddit-dynamic-ad-link {
pointer-events: none !important;
}
shreddit-dynamic-ad-link[aria-label^="Advertisement"] {
display: none !important;
}
/* === SIDEBAR AD PANELS === */
aside#right-rail-entity-panel-root,
[aria-label="İşletmede Öne Çıkanlar Paneli"],
[aria-label="Featured Businesses Panel"],
[aria-label*="İşletmede Öne Çıkanlar"],
[aria-label*="Featured Businesses"] {
display: none !important;
}
/* === AD SLOTS === */
shreddit-ad-slot,
[data-ad-slot],
.advert,
.promotedlink,
.ad-container {
display: none !important;
}
/* === PREMIUM UPSELL === */
[data-testid="premium-upsell"],
[data-testid="premium-banner"],
.premium-upsell {
display: none !important;
}
/* === OLD REDDIT SUPPORT === */
.promoted,
#siteTable_promoted,
.link.promoted {
display: none !important;
}
`;
if (document.documentElement) {
document.documentElement.appendChild(css);
} else {
document.addEventListener('DOMContentLoaded', () => {
document.head.appendChild(css);
});
}
// ========================================
// 3. MUTATION OBSERVER - Catch dynamic ads
// ========================================
let pendingFrame = null;
let hiddenCount = 0;
function hideElement(el) {
if (!el || el.style.display === 'none') return false;
el.style.setProperty('display', 'none', 'important');
// Also hide the next sibling HR
const next = el.nextElementSibling;
if (next && next.tagName === 'HR') {
next.style.setProperty('display', 'none', 'important');
}
hiddenCount++;
log(`Ad hidden #${hiddenCount}:`, el.tagName, el.getAttribute('data-code-comment-1') || '');
return true;
}
function scanForAds() {
let found = 0;
// Main target: shreddit-ad-post elements
document.querySelectorAll('shreddit-ad-post').forEach(el => {
if (hideElement(el)) found++;
});
// Ad slots
document.querySelectorAll('shreddit-ad-slot, [data-ad-slot]').forEach(el => {
if (hideElement(el)) found++;
});
// Sidebar ad panel
const sidebar = document.querySelector('#right-rail-entity-panel-root');
if (sidebar && sidebar.style.display !== 'none') {
sidebar.style.setProperty('display', 'none', 'important');
found++;
}
// "Advertisement:" ile başlayan aria-label'lar
document.querySelectorAll('[aria-label^="Advertisement:"]').forEach(el => {
const container = el.closest('shreddit-ad-post') || el.closest('article') || el.parentElement;
if (container && container.style.display !== 'none') {
hideElement(container);
found++;
}
});
// Old Reddit support
document.querySelectorAll('.link.promoted, .promoted').forEach(el => {
if (hideElement(el)) found++;
});
if (found > 0) log(`Scan: ${found} new ads hidden (total: ${hiddenCount})`);
}
function scheduleScan() {
if (pendingFrame) cancelAnimationFrame(pendingFrame);
pendingFrame = requestAnimationFrame(() => {
scanForAds();
pendingFrame = null;
});
}
function initObserver() {
const observer = new MutationObserver(mutations => {
let shouldScan = false;
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
const tag = node.tagName;
if (tag === 'SHREDDIT-AD-POST' ||
tag === 'SHREDDIT-DYNAMIC-AD-LINK' ||
tag === 'SHREDDIT-AD-SLOT' ||
tag === 'ARTICLE' ||
tag === 'SHREDDIT-POST' ||
node.querySelector?.('shreddit-ad-post, shreddit-ad-slot, shreddit-dynamic-ad-link')) {
shouldScan = true;
break;
}
}
if (shouldScan) break;
}
if (shouldScan) scheduleScan();
});
observer.observe(document.body || document.documentElement, {
childList: true,
subtree: true,
});
log('MutationObserver started');
}
// ========================================
// 4. INITIALIZATION
// ========================================
if (document.body) {
initObserver();
scanForAds();
} else {
document.addEventListener('DOMContentLoaded', () => {
initObserver();
scanForAds();
});
}
// Scroll scanning (infinite scroll)
let scrollTimer = null;
window.addEventListener('scroll', () => {
if (scrollTimer) return;
scrollTimer = setTimeout(() => {
scanForAds();
scrollTimer = null;
}, 500);
}, { passive: true });
// Watch SPA URL changes
let lastUrl = location.href;
const urlObs = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
log('URL changed:', lastUrl);
setTimeout(scanForAds, 1000);
setTimeout(scanForAds, 3000);
}
});
document.addEventListener('DOMContentLoaded', () => {
const title = document.querySelector('title');
if (title) urlObs.observe(title, { childList: true, subtree: true, characterData: true });
});
log('Reddit Ad Blocker v2.1 initialized');
})();