-
-
Save nielsmh/8ae6ff1c4c571dcd9a57b046e6f0e697 to your computer and use it in GitHub Desktop.
This file contains 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 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