Image Board Enhancer (Rule34, Gelbooru, Konachan, and more)

Auto Resize images and video on multiple image boards.

As of 2020-06-27. See the latest version.

// ==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.');
})();