Discord multimedia dimmer

Dim images and videos on Discord.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Discord multimedia dimmer
// @namespace    http://tampermonkey.net/
// @version      2023-12-18
// @description  Dim images and videos on Discord.
// @author       Andrew15-5
// @match        https://discord.com/channels/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=discord.com
// @grant        none
// @license      AGPL-3.0
// ==/UserScript==

(function () {
  "use strict";
  const brightness_threshold = 100;

  async function get_image_data(element) {
    // Avoid insecure error on getImageData() by fetching the media again.
    // Firefox caches fetches, so refetching doesn't really refetch from the server.
    const url = element.poster === undefined ? element.src : element.poster;
    const response = await fetch(url);
    const blob = await response.blob();
    return await new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => {
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0, img.width, img.height);
        const image_data = ctx.getImageData(0, 0, img.width, img.height).data;
        resolve(image_data);
      };
      img.onerror = reject;
      img.src = URL.createObjectURL(blob);
    });
  }

  async function get_average_brightness(element) {
    const data = await get_image_data(element);
    const pixel_count = element.width * element.height;
    let red, green, blue, average;
    let color_sum = 0;

    for (let x = 0, len = data.length; x < len; x += 4) {
      red = data[x];
      green = data[x + 1];
      blue = data[x + 2];
      average = Math.floor((red + green + blue) / 3);
      color_sum += average;
    }

    const brightness = Math.floor(color_sum / pixel_count);
    return brightness;
  }

  function dim_element(element) {
    element.style.filter = "brightness(50%)";
  }

  function dim_if_too_bright(elements) {
    elements.forEach(async (element) => {
      const average_brightness = await get_average_brightness(element);
      if (average_brightness > brightness_threshold) dim_element(element);
    });
  }

  let observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
      if (mutation.type !== "childList") return;
      mutation.addedNodes.forEach(function (node) {
        if (node.nodeName === "IMG" || node.nodeName === "VIDEO") {
          dim_if_too_bright([node]);
          return;
        }
        const images = Array.from(node.querySelectorAll("img")).filter(
          (element) =>
            Array.from(element.getAttribute("alt")).length > 2 &&
            element.style.filter === "",
        );
        const videos = Array.from(node.querySelectorAll("video"));
        dim_if_too_bright(images);
        dim_if_too_bright(videos);
      });
    });
  });

  observer.observe(document.body, { childList: true, subtree: true });
})();