Discord multimedia dimmer

Dim images and videos on Discord.

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