// ==UserScript==
// @name Image Board Enhancer (Rule34, Gelbooru, Konachan, and more)
// @namespace ImageBoardEnhancer
// @version 0.9
// @description Auto Resize images and video on multiple image boards.
// @author DanDanDan
// @match *://rule34.xxx/*
// @match *://chan.sankakucomplex.com/*
// @match *://idol.sankakucomplex.com/*
// @match *://gelbooru.com/*
// @match *://danbooru.donmai.us/*
// @match *://konachan.com/*
// @match *://yande.re/*
// @match *://safebooru.org/*
// @match *://rule34.paheal.net/*
// @match *://rule34hentai.net/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @grant GM.setValue
// @grant GM.getValue
// ==/UserScript==
(async () => {
'use strict';
var resizeImageToFit = await GM.getValue('resizeImageToFit', true);
var resizeVideoToFit = await GM.getValue('resizeVideoToFit', true);
var autoplayVideos = await GM.getValue('autoplayVideos', true);
var autoScrollToContent = await GM.getValue('autoScrollToContent', true);
var updateWithWindowResize = await GM.getValue('updateWithWindowResize', true);
var showFitButton = await GM.getValue('showFitButton', true);
var showScrollButton = await GM.getValue('showScrollButton', true);
var showR34XXXLikeAndFavoriteButtons = await GM.getValue('showR34XXXLikeAndFavoriteButtons', true);
var removeFluid = await GM.getValue('removeFluid', false);
var videoVolume = await GM.getValue('videoVolume', 0);
var widthMargin = await GM.getValue('widthMargin', 15);
var heightMargin = await GM.getValue('heightMargin', 15);
var resizeButton = await GM.getValue('resizeButton', 'BracketLeft');
var scrollButton = await GM.getValue('scrollButton', 'BracketRight');
// Create variables.
var currentWindowWidth = 0;
var currentWindowHeight = 0;
var currentWindowAspect = 0;
var contentTrueWidth = 0;
var contentTrueHeight = 0;
var contentTrueAspect = 0;
var resizeReady = false;
var debugMode = false;
var r34buttons = false;
var toolbarDOM = '.sidebar form';
var containerDOM = '#content';
var imageDOM = '#image';
var playerDOM;
var changeKeyboardShortcut = false;
// Per-site DOM selection.
if (document.location.hostname.toLowerCase() == 'rule34.xxx') { toolbarDOM = '.space'; r34buttons = showR34XXXLikeAndFavoriteButtons; playerDOM = '#gelcomVideoContainer'; }
if (document.location.hostname.toLowerCase() == 'chan.sankakucomplex.com') { toolbarDOM = '#search-form'; }
if (document.location.hostname.toLowerCase() == 'idol.sankakucomplex.com') { toolbarDOM = '#search-form'; }
if (document.location.hostname.toLowerCase() == 'gelbooru.com') { toolbarDOM = '#tag-list form'; containerDOM = '.contain-push'; }
if (document.location.hostname.toLowerCase() == 'danbooru.donmai.us') { toolbarDOM = '#search-box'; }
if (document.location.hostname.toLowerCase() == 'rule34.paheal.net') { toolbarDOM = '#Navigationleft'; containerDOM = 'article'; imageDOM = '#main_image'; }
if (document.location.hostname.toLowerCase() == 'rule34hentai.net') { toolbarDOM = '#Navigationleft'; containerDOM = 'article'; imageDOM = '#main_image'; playerDOM = '#fluid_video_wrapper_video-id'; }
// Remove the Gelcom Video player.
function removeFluidPlayer() {
if (debugMode) console.log('removeFluidPlayer');
$(playerDOM).replaceWith($(containerDOM + ' video'));
$(containerDOM + ' video').attr('id', 'image');
document.getElementById('image').outerHTML = document.getElementById('image').outerHTML; // This removes all event listeners, it seems jquery tries to maintain them.
$(containerDOM + ' video').removeAttr('style');
$(containerDOM + ' video').removeAttr('playsinline');
$(containerDOM + ' video').removeAttr('webkit-playsinline');
$(containerDOM + ' video').attr('controls', 'true');
$(containerDOM + ' video').attr('autoplay', autoplayVideos);
}
// Get window size and aspect ratio.
function getWindowProps() {
if (debugMode) console.log('getWindowProps');
currentWindowWidth = $(window).width() - widthMargin;
currentWindowHeight = $(window).height() - heightMargin;
if (currentWindowWidth !== 0 && currentWindowHeight !== 0)
currentWindowAspect = currentWindowWidth / currentWindowHeight;
}
// Get the real size of the video or image.
function getContentProps() {
if (debugMode) console.log('getContentProps');
if ($(containerDOM + ' video').length) {
contentTrueWidth = $(containerDOM + ' video')[0].videoWidth;
contentTrueHeight = $(containerDOM + ' video')[0].videoHeight;
}
else if ($(containerDOM + ' ' + imageDOM).length) {
var screenImage = $(containerDOM + ' ' + imageDOM);
var theImage = new Image();
theImage.src = screenImage.attr("src");
contentTrueWidth = theImage.width;
contentTrueHeight = theImage.height;
}
if (contentTrueWidth !== 0 && contentTrueHeight !== 0)
contentTrueAspect = contentTrueWidth / contentTrueHeight;
resizeReady = true;
}
// Resize the image (This resizes the video on some sites eg. sankakucomplex.com)
function resizeImage() {
if (debugMode) console.log('resizeImage');
$(containerDOM + ' ' + imageDOM).css('max-width', '');
if (currentWindowAspect > contentTrueAspect) {
$(containerDOM + ' ' + imageDOM)[0].width = currentWindowHeight * contentTrueAspect;
$(containerDOM + ' ' + imageDOM)[0].height = currentWindowHeight;
}
else {
$(containerDOM + ' ' + imageDOM)[0].width = currentWindowWidth;
$(containerDOM + ' ' + imageDOM)[0].height = currentWindowWidth / contentTrueAspect;
}
// Remove css from images.
$(containerDOM + ' ' + imageDOM).removeAttr('style');
}
// Resize Fluid video player.
function resizeFluidVideo() {
if (debugMode) console.log('resizeFluidVideo');
$(containerDOM + ' ' + playerDOM).css('max-width', '');
if (currentWindowAspect > contentTrueAspect) {
$(containerDOM + ' ' + playerDOM).css('width', currentWindowHeight * contentTrueAspect);
$(containerDOM + ' ' + playerDOM).css('height', currentWindowHeight);
}
else {
$(containerDOM + ' ' + playerDOM).css('width', currentWindowWidth);
$(containerDOM + ' ' + playerDOM).css('height', currentWindowWidth / contentTrueAspect);
}
}
// Resize default video.
function resizeVideo() {
if (debugMode) console.log('resizeVideo');
$(containerDOM + ' video').css('max-width', '');
if (currentWindowAspect > contentTrueAspect) {
$(containerDOM + ' video')[0].width = currentWindowHeight * contentTrueAspect;
$(containerDOM + ' video')[0].height = currentWindowHeight;
}
else {
$(containerDOM + ' video')[0].width = currentWindowWidth;
$(containerDOM + ' video').height = currentWindowWidth / contentTrueAspect;
}
}
// Scroll the window to the video or image.
function scrollToContent() {
if (debugMode) console.log('scrollToContent');
var contentID;
if ($(containerDOM + ' ' + imageDOM).length) contentID = containerDOM + ' ' + imageDOM;
else if ($(containerDOM + ' ' + playerDOM).length) contentID = containerDOM + ' ' + playerDOM;
else if ($(containerDOM + ' video').length) contentID = containerDOM + ' video';
$([document.documentElement, document.body]).animate({
scrollTop: $(contentID).offset().top
}, 0);
$([document.documentElement, document.body]).animate({
scrollLeft: $(contentID).offset().left
}, 0);
}
// Check if resize is ready and what type of content to resize.
function fitContent() {
if (debugMode) console.log('fitContent');
if (resizeReady) {
getWindowProps();
if ($(containerDOM + ' ' + imageDOM).length) {
resizeImage();
}
else if ($(containerDOM + ' ' + playerDOM).length) {
resizeFluidVideo();
}
else if ($(containerDOM + ' video').length) {
resizeVideo();
}
}
}
// Set the video auto play and volume settings.
function videoSettings() {
if (debugMode) console.log('videoSettings');
$(containerDOM + ' video').prop('autoplay', autoplayVideos);
$(containerDOM + ' video').prop('volume', videoVolume);
$(containerDOM + ' video').prop('loop', true);
if (autoplayVideos) $(containerDOM + ' video')[0].play(); else $(containerDOM + ' video')[0].pause();
}
// Remove the Gelcom player if present.
if (removeFluid && $(playerDOM).length) removeFluidPlayer();
// Get the image properties, resize, and scroll as the page is loading.
// If the image loads too quickly it wont fire the event.
if ($(containerDOM + ' video').length || $(containerDOM + ' ' + imageDOM).length) {
getContentProps();
if (resizeImageToFit) fitContent();
if (autoScrollToContent) scrollToContent();
}
// Add event listener to the image or video.
if ($(containerDOM + ' video').length) {
if (debugMode) console.log('Create video event listener');
videoSettings();
$(containerDOM + ' video').on('loadedmetadata', function () { //NOTE: replaced 'loadedmetadata' with 'canplay'
getContentProps();
if (resizeVideoToFit) fitContent();
if (autoScrollToContent) scrollToContent();
$(containerDOM + ' video').play();
});
}
else if ($(containerDOM + ' ' + imageDOM).length) {
if (debugMode) console.log('Create image event listener');
$(containerDOM + ' ' + imageDOM).on('load', function () {
getContentProps();
if (resizeImageToFit) fitContent();
if (autoScrollToContent) scrollToContent();
});
}
// Add the event listener to the window.
if (updateWithWindowResize) {
$(window).resize(function () {
fitContent();
});
}
// Setting Functions
function showSettings() {
$("#ibenhancerSettings").addClass('show');
}
function hideSettings() {
$("#ibenhancerSettings").removeClass('show');
}
function changeResizeButtonClicked() {
$('#resizeButton').html('?');
changeKeyboardShortcut = 'resizeButton';
}
function changeScrollButtonClicked() {
$('#scrollButton').html('?');
changeKeyboardShortcut = 'scrollButton';
}
function saveSettings() {
GM.setValue('resizeImageToFit', $('#resizeImageToFitCheckbox').is(':checked'));
GM.setValue('resizeVideoToFit', $('#resizeVideoToFitCheckbox').is(':checked'));
GM.setValue('autoplayVideos', $('#autoplayVideosCheckbox').is(':checked'));
GM.setValue('autoScrollToContent', $('#autoScrollToContentCheckbox').is(':checked'));
GM.setValue('updateWithWindowResize', $('#updateWithWindowResizeCheckbox').is(':checked'));
GM.setValue('showFitButton', $('#showFitButtonCheckbox').is(':checked'));
GM.setValue('showScrollButton', $('#showScrollButtonCheckbox').is(':checked'));
GM.setValue('showR34XXXLikeAndFavoriteButtons', $('#showR34XXXLikeAndFavoriteButtonsCheckbox').is(':checked'));
GM.setValue('removeFluid', $('#removeFluidCheckbox').is(':checked'));
GM.setValue('videoVolume', $('#videoVolumeInput').val());
GM.setValue('widthMargin', $('#widthMarginInput').val());
GM.setValue('heightMargin', $('#heightMarginInput').val());
GM.setValue('resizeButton', resizeButton);
GM.setValue('scrollButton', scrollButton);
hideSettings();
}
// Create the toolbar.
if ($(containerDOM + ' ' + imageDOM).length || $(containerDOM + ' video').length) {
if (debugMode) console.log('Create toolbar');
$(toolbarDOM).after("<div id='ibenhancer' style=''>Image Board Enhancer<br></div>");
if (showFitButton) {
$("#ibenhancer").append("<button id='fitContentButton' style='margin-top: 3px; background: #fff; border: 1px solid #dadada; width: 50px;'>Fit</button>");
$("#fitContentButton").click(function () { getContentProps(); fitContent(); });
}
if (showScrollButton) {
$("#ibenhancer").append("<button id='scrollContentButton' style='margin-top: 3px; background: #fff; border: 1px solid #dadada; width: 60px;'>Scroll</button>");
$("#scrollContentButton").click(scrollToContent);
}
// Create settings.
$("#ibenhancer").append("<br><button id='ibenhancerSettingsButton' style='margin-top: 3px; background: #fff; border: 1px solid #dadada; width: 120px;'>Settings</button>");
$("#ibenhancerSettingsButton").click(showSettings);
$("#ibenhancer").append(`
<div id="ibenhancerSettings">
<label><input id="resizeImageToFitCheckbox" type="checkbox" ` + (resizeImageToFit ? `checked` : ``) + `>resizeImageToFit</label>
<br>
<label><input id="resizeVideoToFitCheckbox" type="checkbox" ` + (resizeVideoToFit ? `checked` : ``) + `>resizeVideoToFit</label>
<br>
<label><input id="autoplayVideosCheckbox" type="checkbox" ` + (autoplayVideos ? `checked` : ``) + `>autoplayVideos</label>
<br>
<label><input id="autoScrollToContentCheckbox" type="checkbox" ` + (autoScrollToContent ? `checked` : ``) + `>autoScrollToContent</label>
<br>
<label><input id="updateWithWindowResizeCheckbox" type="checkbox" ` + (updateWithWindowResize ? `checked` : ``) + `>updateWithWindowResize</label>
<br>
<label><input id="showFitButtonCheckbox" type="checkbox" ` + (showFitButton ? `checked` : ``) + `>showFitButton</label>
<br>
<label><input id="showScrollButtonCheckbox" type="checkbox" ` + (showScrollButton ? `checked` : ``) + `>showScrollButton</label>
<br>
<label><input id="showR34XXXLikeAndFavoriteButtonsCheckbox" type="checkbox" ` + (showR34XXXLikeAndFavoriteButtons ? `checked` : ``) + `>showR34XXXLikeAndFavoriteButtons</label>
<br>
<label><input id="removeFluidCheckbox" type="checkbox" ` + (removeFluid ? `checked` : ``) + `>removeFluid</label>
<br>
<label>videoVolume<input id="videoVolumeInput" type="number" min="0" max="1" step="0.01" value="` + videoVolume + `" style="width:60px;">0 - 1</label>
<br>
<label>widthMargin<input id="widthMarginInput" type="number" min="0" max="100" step="1" value="` + widthMargin + `" style="width:60px;">0 - 100</label>
<br>
<label>heightMargin<input id="heightMarginInput" type="number" min="0" max="100" step="1" value="` + heightMargin + `" style="width:60px;">0 - 100</label>
<br>
<button id="resizeButton">` + resizeButton + `</button> resizeButton
<br>
<button id="scrollButton">` + scrollButton + `</button> scrollButton
<br>
<button id="ibenhancerSettingsSave">Save</button><button id="ibenhancerSettingsCancel">Cancel</button>
</div>
` );
$("#ibenhancerSettingsSave").click(saveSettings);
$("#ibenhancerSettingsCancel").click(hideSettings);
$("#resizeButton").click(changeResizeButtonClicked);
$("#scrollButton").click(changeScrollButtonClicked);
addGlobalStyle(`
#ibenhancerSettings {
position: fixed;
width: 300px;
height: 320px;
left: calc(50vw - 155px);
top: calc(50vh - 165px);
background-color: white;
border: 2px solid black;
border-radius: 3px;
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
text-align: left;
z-index: 999999;
display: none;
-webkit-box-sizing: unset;
-moz-box-sizing: unset;
box-sizing: unset;
}
#ibenhancerSettings * {
all: ini;
font: 15px Arial, sans-serif;
}
#ibenhancerSettings input {
margin: 5px;
width: auto;
}
#ibenhancerSettings.show {
display: block;
}
`);
// Add the like and favorite button to rule34.xxx
if (r34buttons) {
if (debugMode) console.log('r34buttons');
$("#ibenhancer").append('<br><img id="like-butt" class="custom-button" src="https://i.imgur.com/Kh1HzGr.png" alt="like"><img id="favorite-butt" class="custom-button" src="https://i.imgur.com/dTpBrIj.png" alt="favorite">');
$("#like-butt").click(function () {
$("#stats > ul > li:contains('(vote up)') > a:contains('up')").click();
});
$("#favorite-butt").click(function () {
$("#stats + div > ul > li > a:contains('Add to favorites')").click();
});
addGlobalStyle(`
img.custom-button {
cursor: pointer;
width: 35px;
padding: 3px;
margin: 0;
border-radius: 20px;
}
.custom-button:hover {
background-color: rgba(255,255,255,.5);
}
.custom-button:active {
background-color: rgba(255,255,255,1);
}
`);
}
}
function addGlobalStyle(css) {
if (debugMode) console.log('addGlobalStyle');
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}
// Keyboard shortcuts
document.addEventListener('keyup', (e) => {
if (debugMode) console.log(e.code)
if (!changeKeyboardShortcut) {
if (e.code === resizeButton) { getContentProps(); fitContent(); }
else if (e.code === scrollButton) scrollToContent();
}
else if (changeKeyboardShortcut == 'resizeButton') {
resizeButton = e.code;
$('#resizeButton').html(e.code);
changeKeyboardShortcut = false;
}
else if (changeKeyboardShortcut == 'scrollButton') {
scrollButton = e.code;
$('#scrollButton').html(e.code);
changeKeyboardShortcut = false;
}
});
if (debugMode) console.log('End of script.');
})();