RedGIFs iframe Sound Helper

Provide facilities to automatically enable RedGIFs audio in embedded iframes, and for communication regarding audio controls between the the hosting site and the iframe.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name     RedGIFs iframe Sound Helper
// @description    Provide facilities to automatically enable RedGIFs audio in embedded iframes, and for communication regarding audio controls between the the hosting site and the iframe.
// @license public domain
// @version  1.4.1
// @grant    none
// @include  https://www.redgifs.com/ifr/*
// @namespace https://greasyfork.org/users/1258441
// ==/UserScript==

const SOUND_DEFAULT = false; // false = off, true = on
const LINK_DEFAULT = true; // false = link removed, true = link remains

const urlParams = new URLSearchParams(window.location.search);

function waitForElm(selector) {
  // Lovingly stolen from Yong Wang and sashaolm on StackOverflow
  return new Promise(resolve => {
    if (document.querySelector(selector)) {
      return resolve(document.querySelector(selector));
    }
    const observer = new MutationObserver(mutations => {
      if (document.querySelector(selector)) {
        observer.disconnect();
        resolve(document.querySelector(selector));
      }
    });

    // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  });
}

function toBoolean(argument) {
  if (argument === "true" || argument === "yes" || argument === "on" || argument === "1") {
    return true;
  }
  if (argument === "false" || argument === "no" || argument === "off" || argument === "0") {
    return false;
  }
  return undefined; // I could probably do this implicitly...
}

// Quick and dirty function to click an arbitrary element—not sure if elm.click() would work better?
var click = function(elm) {
  var clickEvent = new MouseEvent("click", {
    "bubbles": true,
    "cancelable": false,
    "view": window
  });
  elm.dispatchEvent(clickEvent);
};

// Facilities for enabling or disabling click-to-open link on the video
var saved_link = null;

function disableHyperLink(hyperlink) {
  if (hyperlink.tagName !== "A") {
    return;
  }
  if (saved_link === null) {
    saved_link = hyperlink.href;
  }
  hyperlink.href = "javascript: void(0)";
  hyperlink.target = "_self";
}

function enableHyperLink(hyperlink) {
  if (hyperlink.tagName !== "A" || saved_link === null) {
    return;
  }
  hyperlink.href = saved_link;
  hyperlink.target = "_blank";
}

// Facilities for turning looping on and off

var video_end_listener = (event) => window.parent.postMessage("gfy_ended", "*")

function disableLoop(video) {
  if (video.tagName !== "VIDEO") {
    return;
  }
  video.removeAttribute("loop");
  video.addEventListener("ended", function(event) {window.dispatchEvent(event);});
  video.addEventListener("ended", video_end_listener);
}

function enableLoop(video) {
  if (video.tagName !== "VIDEO") {
    return;
  }
  video.setAttribute("loop", "");
  video.removeEventListener("ended", video_end_listener);
}

// Setup configuration
const query_sound = toBoolean(urlParams.get("sound"));
const query_link = toBoolean(urlParams.get("link"));
const query_loop = toBoolean(urlParams.get("loop"));
const hash = window.location.hash;

var autoenable_sound = undefined;
var autodisable_link = undefined;
var autodisable_loop = undefined;

// Figure out whether to enable sound
if (query_sound === true) {
  autoenable_sound = true;
}
else if (query_sound === false) { // do nothing if it's undefined
  autoenable_sound = false;
}

if (window.location.hash === "#sound") {
  autoenable_sound = true;
}
else if (window.location.hash === "#nosound") {
  autoenable_sound = false;
}

if (autoenable_sound === undefined) {
  autoenable_sound = SOUND_DEFAULT;
}

// Figure out whether to disable the link
if (query_link === true) {
  autodisable_link = false;
}
else if (query_link === false) {
  autodisable_link = true;
}

if (autodisable_link === undefined) {
  autodisable_link = !LINK_DEFAULT;
}

// Figure out whether to disable looping
autodisable_loop = !query_loop; // loop should be ON by default

// Execute the above settings
if (autoenable_sound) {
  waitForElm(".soundOff").then(click);
}

if (autodisable_link) {
  waitForElm(".videoLink").then(disableHyperLink)
}

if (autodisable_loop) {
  waitForElm("video").then(disableLoop);
}

window.parent.postMessage("gfy_enhanced_api", "*");

// For communication with the parent window
window.onmessage = function(message) {
  const soundButton = document.querySelector(".soundOff") || document.querySelector(".soundOn");
  const loaded = soundButton !== null;

  switch (message.data) {
    case "soundOn":
      /* If the page is not loaded yet and sound will be off when it loads, wait for it to load then click .soundOff
       * If the page is not loaded yet and sound will be on when it loads, do nothing
       * If the page is loaded, look for .soundOff and click if present
       *
       * I worked it out, this is the only way to make it work.
       */
      if (loaded) {
        let button = document.querySelector(".soundOff");
        if (button !== null)
          click(button);
      }
      else if (!autoenable_sound)
        waitForElm(".soundOff").then(click);
      break;

    case "soundOff":
      /* If the page is not loaded yet and sound will be off when it loads, do nothing
       * If the page is not loaded yet and sound will be on when it loads, wait for it to load then click .soundOn
       * If the page is loaded, look for .soundOn and click if present
       */
      if (loaded) {
        let button = document.querySelector(".soundOn");
        if (button !== null)
          click(button);
      }
      else if (autoenable_sound)
        waitForElm(".soundOff").then(click);
      break;

    case "soundToggle":
      click(soundButton);
      break;

    case "linkOff":
      waitForElm(".videoLink").then(disableHyperLink);
      break;

    case "linkOn":
      waitForElm(".videoLink").then(enableHyperLink);
      break;

    case "loopOff":
      waitForElm("video").then(disableLoop);
      break;

    case "loopOn":
      waitForElm("video").then(enableLoop);
      break;

    case "pause":
      waitForElm("video").then((vid) => vid.pause());
      break;

    case "play":
      waitForElm("video").then((vid) => vid.play());
      break;

    default:
      console.error("Unknown command " + message.data);
  }
}