Skip to content

Instantly share code, notes, and snippets.

@nielsmh
Last active April 21, 2016 14:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nielsmh/8ae6ff1c4c571dcd9a57b046e6f0e697 to your computer and use it in GitHub Desktop.
Save nielsmh/8ae6ff1c4c571dcd9a57b046e6f0e697 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name SA Internal Ads Only
// @namespace nyo.dk
// @description Get rid of Something Awful Forums user ads that link outside the forum
// @include /forums.somethingawful.com/
// @version 1
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_log
// ==/UserScript==
var maxCacheAge = 60*60*24*1000; // one day
function GetDomainFromUrl(url) {
var a = document.createElement("a");
a.href = url;
return a.hostname;
}
function GetResponseDocument(rsp) {
if (rsp.responseXML)
return rsp.responseXML;
return (new DOMParser()).parseFromString(rsp.responseText, "text/html");
}
function IsAdGood(href) {
if (href.match(/^https?:\/\/forums.somethingawful.com/)) {
return true;
} else {
return false;
}
}
function CheckExtraLinks(extraLinks) {
// ad quality checker modules
var checkers = [
{
name: "bitly",
handles: function(domain) {
return !!domain.match(/^(bit\.ly|j\.mp)/);
},
check: function(url, continuation) {
GM_xmlhttpRequest({
method: "GET",
url: url + "+",
onload: function(rsp) {
var rspDoc = GetResponseDocument(rsp);
if (rspDoc) {
var realUrl = rspDoc.getElementById("bitmark_long_url");
if (realUrl) {
continuation(IsAdGood(realUrl.href), realUrl.href);
return;
}
}
continuation(false, null);
},
onerror: function(rsp) {
continuation(false, null);
},
});
}
}
];
// urls to check quality of
var urls = [];
// iteration index for continuations
var urlsIndex = 0;
// when all urls are quality-checked
function Finish() {
// extra ads to keep
var filteredAds = [];
// check each original ad for having good/bad url
extraLinks.forEach(function(ad) {
if (urls.indexOf(ad.href) >= 0) {
if (urls[ad.href]) {
ad.originalHref = ad.href;
ad.href = urls[ad.href];
}
// add the good ones to the list
filteredAds.push(ad);
}
});
// and append those to the cache
if (filteredAds.length > 0) {
GM_log("Finished checking external ad links, adding " + filteredAds.length + " more to cache");
var adcache = JSON.parse(GM_getValue("adcache", "[]"));
adcache = adcache.concat(filteredAds);
GM_setValue("adcache", JSON.stringify(adcache));
}
}
// url quality check step function
function CheckNextExtraLink() {
// check end of iteration
if (urls.length <= urlsIndex) {
return Finish();
}
var url = urls[urlsIndex];
var checker = checkers.find(function(c) { return c.handles(GetDomainFromUrl(url)); });
// continuation for quality check module
function WhenChecked(checkResult, realUrl) {
if (checkResult) {
// good url: keep it, and store the wrapped url
urls[urls[urlsIndex]] = realUrl;
urlsIndex++;
} else {
// bad url: remove it from the array
urls.splice(urlsIndex, 1);
}
CheckNextExtraLink();
}
GM_log("Check external ad link: " + url)
if (checker) {
// has a checker module for domain, call it
checker.check(url, WhenChecked);
} else {
// no checker module, assume bad
WhenChecked(false);
}
}
// get unique urls from list of ads
extraLinks.forEach(function (ad) {
if (urls.indexOf(ad.href) < 0) {
urls.push(ad.href);
}
});
// begin check
CheckNextExtraLink();
}
function CacheAdListFromDocument(adlistDoc) {
// storage for found good ads
var ads = [];
// storage for bad ads that need an extra check
var doublecheckLinks = [];
// get adlist container
var adlist = adlistDoc.getElementById("adlist");
if (!adlist) {
return null;
}
// iterate through it
var curAd = adlist.firstElementChild;
while (curAd) {
var theLink = curAd.firstElementChild;
var theImage = theLink.firstElementChild;
if (IsAdGood(theLink.href)) {
ads.push({href: theLink.href, src: theImage.src, hrefDomain: theLink.domain});
} else {
doublecheckLinks.push({href: theLink.href, src: theImage.src, hrefDomain: theLink.hostname});
}
curAd = curAd.nextElementSibling;
}
// update cache
GM_setValue("adcache", JSON.stringify(ads));
GM_setValue("adcache_updated", Date.now());
GM_log("Loaded "+ads.length+" ads and stored in cache");
// kick off check of bad-unknown ads
CheckExtraLinks(doublecheckLinks);
return ads;
}
function GetAdList(continuation) {
// check if the cache exists and is up-to-date
var cacheUpdated = GM_getValue("adcache_updated");
var now = Date.now();
var cache;
if (cacheUpdated && (now - cacheUpdated) < maxCacheAge) {
// cache good, load and use it
cache = JSON.parse(GM_getValue("adcache"));
return continuation(cache);
}
// otherwise kick off a cache update
GM_log("Reloading SA ad list");
var xhr = GM_xmlhttpRequest({
method: "GET",
url: window.location.protocol + "//forums.somethingawful.com/adlist.php",
onload: function (rsp) {
var adlistDoc = GetResponseDocument(rsp);
if (adlistDoc) {
continuation(CacheAdListFromDocument(adlistDoc));
} else {
continuation(null);
}
},
onerror: function (rsp) {
GM_log("Failed loading ad list");
continuation(null);
},
});
}
function FadeOutAdlistBads() {
// arguably this should also check the ad cache for ads with wrapped links that are still good
GM_log("Fading out on adlist");
var adlist = document.getElementById("adlist");
var ad = adlist.firstElementChild;
while (ad) {
var theLink = ad.firstElementChild;
if (!IsAdGood(theLink.href)) {
ad.style.opacity = 0.2;
}
ad = ad.nextElementSibling;
}
// also, since we have the full ad list anyway, update the cache
CacheAdListFromDocument(document);
}
function ReplaceAd() {
// find ad container
var containerDiv = document.getElementById("ad_banner_user");
if (!containerDiv) {
// not found, check if this is the adlist page instead
if (document.getElementById("adlist")) {
FadeOutAdlistBads();
}
return;
}
// check if the ad needs replacing
var theLink = containerDiv.firstElementChild;
var theImage = theLink.firstElementChild;
if (!IsAdGood(theLink.href)) {
// get list of possible replacements
GetAdList(function(availableAds) {
if (!availableAds || availableAds.length == 0) {
// no replacement, don't replace
return;
}
// pick a replacement
var i = Math.floor(Math.random() * availableAds.length);
var newAd = availableAds[i];
// replace link and image
theLink.href = newAd.href;
theImage.src = newAd.src;
// and add our signature
containerDiv.appendChild(document.createTextNode(" | Ad replaced by userscript"));
// if the ad had a wrapped/shortened link, also add the original one
if (newAd.originalHref) {
var orgLink = document.createElement("a");
orgLink.href = newAd.originalHref;
orgLink.appendChild(document.createTextNode("Original ad link"));
containerDiv.appendChild(document.createTextNode(" | "));
containerDiv.appendChild(orgLink);
}
});
}
}
ReplaceAd();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment