// ==UserScript==
// @name Something Awful Image Fixes
// @namespace SA
// @description Smarter image handling on the Something Awful forums.
// @include http://forums.somethingawful.com/*
// @version 1.3.4
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// @icon http://forums.somethingawful.com/favicon.ico
// ==/UserScript==
"use strict";
var Config = {
createIssue: 'https://github.com/psychoticmeow/Something-Awful-Image-Fixes/issues/new',
imgurClientId: 'b0bde100257fa60',
imgurMaxGifSize: 3 * 1024 * 1024,
loadingIndicator: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAPFBMVEU5Ojg+QD1FRkRPUE5aW1lkZmNucG16e3iBg4CIioeTlZKgop+srqu4urfAwr/GyMXR09Dd4Nzo6uf09/OAsyLcAAAB0klEQVRIx72V63KEIAyFhUAioKL2/d+14SbqgtPt7Gx+dfB8HHLZdBg+E+gX/Rbg13X5g0yAyH+tHOVQiZ5ezn6Sd0D6zcsOQN57ewfctm1jN1MOdQUU6zfspTAx4K7AzPqlm4QKFvoMUDB4KLBlYBYVECvr3UNdYWFiDMIE2GAAjXqSzs8cGVigNA6C3uQEyUBNdp5dqo6YmaAyGob1a7pKr/u+luRhDmGhlPZIEktJYd5DwMkhBIUbyFF96ziFnkkb5dVhkCYRU7OAtCW9O08IuIQ0emqSfFG3cx3fZV+B+PyVGoNBXYfNtodDom59QAPDd0O5KYdTnR/W/pNjD7kfeibawKFn4j/AW0/CL9dLYvNNAO0+C3TONcYVELGFaMt611hZGkPcpwOMi9Fx4NDysgSS3Db3biawfhQ26TGcaDq5KK3i/s4mxxKIcoqeylp71AqICIaKyJNDnnlhGGALGEc+UAxQulZy7tVa6nIpst7yRaMxYQEGQD21Q9pkMBiOIVvIh14T642oQLR4WN5gS8oZiGlT9ycdM069LsCADHRnOpQ0bdgKyCcLzfp82wFwI2uhXmpkUsYXQCBh93+ckOVTBU6HT5Ea90ak0fhI/ALpBRsilkfZxwAAAABJRU5ErkJggg=="
};
var Util = {
/**
* Initialise the page, strip out any assets that we will load.
*/
initialise: function(target) {
var assets = [];
// Remove content images:
var images = document.querySelectorAll('td.postbody img');
for (var index in images) {
var image = images[index];
if (typeof image !== 'object') continue;
var src = image.getAttribute('src');
// Exclude smilies:
if (!/somethingawful[.]com[/](images[/]smilies|forumsystem[/]emoticons)[/]/.test(src)) {
var placeholder = document.createElement('span');
// Replace the image with a placeholder:
placeholder.setAttribute('class', 'saif-pending');
image.parentNode.replaceChild(placeholder, image);
// Remove a parent link element:
if (
placeholder.parentNode instanceof HTMLAnchorElement
&& placeholder.parentNode.children.length === 1
&& !!placeholder.parentNode.parentNode
) {
placeholder.parentNode.parentNode.replaceChild(
placeholder,
placeholder.parentNode
);
}
// Create asset:
if (/i\.imgur\.com/.test(src)) {
assets.push(new ImgurAsset(placeholder, src));
}
else if (/staticflickr\.com\//.test(src)) {
assets.push(new FlickrAsset(placeholder, src));
}
else {
assets.push(new GenericAsset(placeholder, src));
}
}
}
// Reload other images:
var images = document.querySelectorAll('img');
for (var index in images) {
var image = images[index];
if (!image.parentNode) continue;
image.parentNode.replaceChild(image.cloneNode(true), image);
}
// Reload embedded iframes:
var iframes = document.querySelectorAll('td.postbody iframe');
for (var index in iframes) {
var iframe = iframes[index];
if (!iframe.parentNode) continue;
iframe.parentNode.replaceChild(iframe.cloneNode(true), iframe);
}
// Fix post table styles:
var posts = document.querySelectorAll('table.post');
for (var index in posts) {
var post = posts[index];
if (typeof post !== 'object') continue;
post.style.tableLayout = 'fixed';
}
Util.build(target, assets);
},
/**
* Begin loading assets from the start of the document
* until and including the windows viewport.
*/
build: function(target, assets) {
var offset = window.scrollY + window.innerHeight,
queue = [],
scroll;
// Keep the window scrolled to the target:
if (!!target) {
offset = Util.getElementOffset(target) + window.innerHeight;
scroll = setInterval(function() {
window.scrollTo(0, Util.getElementOffset(target));
}, 100);
}
// Initialise all elements up until the offset:
for (var index in assets) {
var asset = assets[index];
if (asset.getOffset() < offset) {
queue.push(asset.build());
}
}
// Wait for the queued assets to render:
Promise.all(queue).then(function() {
var queue = [];
// Scroll to the URL target:
if (!!target) {
clearInterval(scroll);
window.scrollTo(0, Util.getElementOffset(target));
}
// Load remaining assets:
for (var index in assets) {
var asset = assets[index];
if (!asset.rendered) {
asset.build();
}
}
});
},
/**
* Create a formatted error message.
*/
createError: function(message, data) {
var wrapper = document.createElement('div'),
title = document.createElement('p'),
footer = document.createElement('p'),
link = document.createElement('a'),
raw = document.createElement('pre');
wrapper.setAttribute('class', 'saif-error');
title.textContent = 'Sorry, an error occured while making things pretty:';
wrapper.appendChild(title);
// Make the data copy+paste safe:
data = btoa(JSON.stringify(data));
data = data.match(/.{1,76}/g).join("\n");
data = message + "\n\n" + data;
raw.textContent = data;
raw.style.whiteSpace = 'pre-wrap';
wrapper.appendChild(raw);
footer.appendChild(document.createTextNode('If this problem persists, please '));
data = "```\n" + data + "\n```";
data = "**What was the error?**\n" + data;
data = "**Where did this happen?**\n" + window.location + "\n\n" + data;
data = "**What did you experience?**\n\n\n" + data;
link.textContent = 'report this issue';
link.setAttribute('href', Config.createIssue + '?body=' + encodeURIComponent(data));
link.setAttribute('target', '_blank');
footer.appendChild(link);
footer.appendChild(document.createTextNode(' on our issue tracker.'));
wrapper.appendChild(footer);
return wrapper;
},
/**
* Create a simple message.
*/
createMessage: function(message) {
var wrapper = document.createElement('div'),
title = document.createElement('p');
wrapper.setAttribute('class', 'saif-error');
title.textContent = message;
wrapper.appendChild(title);
return wrapper;
},
/**
* Create a simple image element from a given source URL.
*/
createImage: function(source) {
return function(callback) {
var image = document.createElement('img');
image.addEventListener('load', function() {
callback(true, image);
});
image.addEventListener('error', function() {
callback(false, image);
});
// Set image source:
image.setAttribute('src', source);
return image;
};
},
/**
* Inject a stylesheet into the page head.
*/
createStyle: function(css) {
var head = document.querySelectorAll('head')[0],
style = document.createElement('style');
style.textContent = css;
head.appendChild(style);
},
/**
* Create a video element from a list of source URLs with media types.
*/
createVideo: function(sources) {
return function(callback) {
var video = document.createElement('video');
// Set attributes to ensure gif style playback:
video.setAttribute('preload', 'auto');
video.setAttribute('autoplay', 'autoplay');
video.setAttribute('muted', 'muted');
video.setAttribute('loop', 'loop');
video.setAttribute('webkit-playsinline', 'webkit-playsinline');
// Listen for success:
video.addEventListener('loadeddata', function() {
callback(true, video);
});
// Set the video sources:
for (var index in sources) {
var source = document.createElement('source');
source.setAttribute('src', sources[index]);
source.setAttribute('type', (
/[.]webm$/.test(sources[index])
? 'video/webm'
: 'video/mp4'
));
// Listen for failure:
if (index == sources.length - 1) {
source.addEventListener('error', function() {
callback(false, video, source);
});
}
video.appendChild(source);
}
};
},
/**
* Calculate the offset from the top of the page to the
* top of the given element.
*/
getElementOffset: function(element) {
var offset = 0;
while (element.offsetParent) {
offset += element.offsetTop;
element = element.offsetParent;
}
return offset;
}
};
function Deferred() {
try {
this.resolve = null;
this.reject = null;
this.promise = new Promise(function(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this));
Object.freeze(this);
}
catch (error) {
throw new Error('Promise/Deferred is not available');
}
}
var Asset = (function() {
function Asset(placeholder, source) {
if (!!placeholder) {
var self = this;
self.placeholder = placeholder;
self.source = source;
self.wrapper = document.createElement('span');
self.link = document.createElement('a');
self.wrapper.setAttribute('class', 'saif-wrapper');
self.link.setAttribute('target', '_blank');
self.wrapper.appendChild(self.link);
}
};
Asset.prototype.getOffset = function() {
var self = this;
if (!!self.placeholder.parentNode) {
return Util.getElementOffset(self.placeholder);
}
else if (!!self.wrapper.parentNode) {
return Util.getElementOffset(self.wrapper);
}
return 0;
};
Asset.prototype.build = function() {
var self = this;
if (!self.rendered) {
self.rendered = new Deferred();
self.render(self.source);
}
return self.rendered.promise;
};
Asset.prototype.visible = function() {
var deferred = new Deferred(),
chromeSucks = false,
self = this;
var scroll = function() {
if (chromeSucks) return;
var offset = self.getOffset(),
max = window.scrollY + (window.innerHeight * 2);
if (max > offset) {
chromeSucks = true;
window.removeEventListener('scroll', scroll);
deferred.resolve(self);
}
};
window.addEventListener('scroll', scroll);
return deferred.promise;
};
Asset.prototype.setLink = function(href) {
var self = this;
self.link.setAttribute('href', href);
};
Asset.prototype.setContent = function(content) {
var self = this;
self.link.appendChild(content);
if (!!self.placeholder.parentNode) {
self.placeholder.parentNode.replaceChild(self.wrapper, self.placeholder);
}
self.rendered.resolve(self);
};
return Asset;
})();
var FlickrAsset = (function() {
function FlickrAsset(placeholder, source) {
this.parent.constructor.call(this, placeholder, source);
};
FlickrAsset.prototype = new Asset();
FlickrAsset.prototype.parent = Asset.prototype;
FlickrAsset.prototype.constructor = FlickrAsset;
FlickrAsset.prototype.render = function(source) {
var bits = /^(.+?\.com\/.+?\/.+?_.+?)(_[sqtmn-zcbhko])?\.(.+?)$/.exec(source),
self = this;
// Create an image:
if (bits) {
var source = bits[1] + '_b.' + bits[3].toLowerCase();
Util.createImage(source)(function(success, element) {
if (success) {
self.setLink(source);
self.setContent(element);
}
else {
self.setContent(
Util.createError('Could not load image.', {
url: source
})
);
}
});
}
// The source was invalid:
else {
self.setContent(
Util.createError('Could not parse URL.', {
url: source
})
);
}
};
return FlickrAsset;
})();
var GenericAsset = (function() {
function GenericAsset(placeholder, source) {
this.parent.constructor.call(this, placeholder, source);
};
GenericAsset.prototype = new Asset();
GenericAsset.prototype.parent = Asset.prototype;
GenericAsset.prototype.constructor = GenericAsset;
GenericAsset.prototype.render = function(source) {
var self = this;
Util.createImage(source)(function(success, element) {
if (success) {
self.setLink(source);
self.setContent(element);
}
else {
self.setContent(
Util.createError('Could not load image.', {
url: source
})
);
}
});
};
return GenericAsset;
})();
var ImgurAsset = (function() {
function ImgurAsset(placeholder, source) {
var bits = /\/(.{5}|.{7})[sbtmlh]?\.(jpg|png|gif)/i.exec(source),
self = this;
self.parent.constructor.call(self, placeholder, source);
self.prefetched = new Deferred();
// Update the source:
if (bits) {
self.prefetch(bits[1], 'https://api.imgur.com/3/image/' + bits[1]);
}
// Could not parse the image source:
else {
self.prefetched.reject(
Util.createError('Could not parse URL.', {
url: self.source
})
);
}
};
ImgurAsset.prototype = new Asset();
ImgurAsset.prototype.parent = Asset.prototype;
ImgurAsset.prototype.constructor = ImgurAsset;
ImgurAsset.prototype.build = function() {
var self = this;
if (!self.rendered) {
self.rendered = new Deferred();
self.prefetched.promise.then(
function(data) {
self.render(data);
},
function(content) {
self.setContent(content);
}
);
}
return self.rendered.promise;
};
ImgurAsset.prototype.prefetch = function(id, url) {
var request = new XMLHttpRequest(),
data = GM_getValue('imgur-data-' + id),
self = this;
// Use stored data:
if (!!data) {
self.prefetched.resolve(JSON.parse(data));
}
// Load and then store data:
else {
request.addEventListener('load', function() {
try {
var data = JSON.parse(request.response);
if (!data || !data.success || !data.data) {
throw data;
}
}
catch (error) {
// Image not found:
if (!!error.status && error.status === 404) {
self.prefetched.reject(
Util.createMessage('The image you are requesting does not exist or is no longer available.')
);
}
// Generic error:
else {
self.prefetched.reject(
Util.createError('Invalid response from imgur.com.', {
url: url,
result: error
})
);
}
return;
}
// Store date for later:
GM_setValue('imgur-data-' + id, JSON.stringify(data.data));
self.prefetched.resolve(data.data);
});
request.addEventListener('error', function() {
self.prefetched.reject(
Util.createError('Could not access imgur.com.', {
url: url
})
);
});
request.open('GET', url, true);
request.setRequestHeader('Authorization', 'Client-ID ' + Config.imgurClientId);
request.send();
}
};
ImgurAsset.prototype.render = function(data) {
var self = this,
video,
link;
// Has a video page:
if (!!data.gifv) {
link = data.gifv;
}
// Link to the raw image:
else {
link = data.link;
}
// Show a video
if (data.animated && Config.imgurMaxGifSize < data.size) {
if (!!data.webm && !!data.mp4) {
video = Util.createVideo([data.webm, data.mp4]);
}
else if (!!data.webm) {
video = Util.createVideo([data.webm]);
}
else if (!!data.mp4) {
video = Util.createVideo([data.mp4]);
}
}
var image = function() {
return Util.createImage(data.link)(function(success, element) {
if (success) {
self.setLink(link);
self.setContent(element);
}
else {
self.setContent(
Util.createError('Could not load image.', {
url: link
})
);
}
});
};
if (!!video) video(function(success, element) {
if (success) {
self.setLink(link);
self.setContent(element);
}
else image();
});
else image();
};
return ImgurAsset;
})();
try {
Util.createStyle(`
@keyframes saif-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
span.saif-pending {
background: #3b3b3b;
border-radius: 32px;
display: inline-block;
height: 32px;
overflow: hidden;
width: 32px;
}
span.saif-pending:before {
animation-duration: 1s;
animation-name: saif-spinner;
animation-iteration-count: infinite;
animation-timing-function: steps(12, end);
background: url(` + Config.loadingIndicator + `) no-repeat center;
background-size: 24px 24px;
content: '';
display: block;
height: 32px;
width: 32px;
}
span.saif-wrapper {
display: inline-block;
position: relative;
margin: 5px 0;
max-width: 100%;
}
span.saif-wrapper img,
span.saif-wrapper video {
max-height: 1000px;
max-width: 100%;
opacity: 1;
vertical-align: bottom;
}
span.saif-wrapper div.saif-error {
background: #3b3b3b;
border-radius: 8px;
color: #ffffff;
padding: 1px 1em;
}
span.saif-wrapper div.saif-error pre {
color: #cccccc;
font-size: 0.9em;
line-height: 1.2;
}
span.saif-wrapper div.saif-error a {
color: #ffffff;
}
span.saif-wrapper a.saif-link {
background: hsla(0, 0%, 10%, 0.7);
color: #ffffff;
cursor: pointer;
display: none;
font-size: 0.75em;
left: 0;
line-height: 1;
padding: 5px;
position: absolute;
right: 0;
text-decoration: none;
top: 0;
z-index: 1;
}
span.saif-wrapper:hover a.saif-link {
display: block;
}
`);
// Prevent images from loading:
window.stop();
// Redirect the page:
if (document.querySelectorAll('meta[http-equiv=refresh]').length) {
var rule = document.querySelectorAll('meta[http-equiv=refresh]')[0].getAttribute('content');
if (/URL=(.+)$/.test(rule)) {
window.location = /URL=(.+)$/.exec(rule)[1];
}
}
// Jump to appropriate place on page:
else if (!!window.location.hash && document.querySelectorAll(window.location.hash).length) {
Util.initialise(document.querySelectorAll(window.location.hash)[0]);
}
// Load the page normally:
else {
Util.initialise();
}
}
catch (e) {
console.log("Exception: " + e.name + " Message: " + e.message);
}
Donate for the site OpenUserJS
Are you sure you want to go to an external site to donate a monetary value?
WARNING: Some countries laws may supersede the payment processors policy such as the GDPR and PayPal. While it is highly appreciated to donate, please check with your countries privacy and identity laws regarding privacy of information first. Use at your utmost discretion.