YouTube Defaulter

Set speed, quality, subtitles and volume as default globally or per channel

目前為 2025-02-24 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Defaulter
// @namespace    https://greasyfork.org/ru/users/901750-gooseob
// @version      1.12.3
// @description  Set speed, quality, subtitles and volume as default globally or per channel
// @author       GooseOb
// @license      MIT
// @grant        window.onurlchange
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// ==/UserScript==

(function(){// src/config/update.ts
var update = (cfg) => {
  const doUpdate = cfg._v !== 4;
  if (doUpdate) {
    switch (cfg._v) {
      case 2:
        cfg.flags.standardMusicSpeed = false;
        cfg._v = 3;
      case 3:
        cfg.global.quality = cfg.global.qualityMax;
        delete cfg.global.qualityMax;
        for (const key in cfg.channels) {
          const currCfg = cfg.channels[key];
          currCfg.quality = currCfg.qualityMax;
          delete currCfg.qualityMax;
        }
        cfg._v = 4;
    }
  }
  return doUpdate;
};
// src/config/value.ts
var cfgLocalStorage = localStorage.YTDefaulter;
var value = cfgLocalStorage ? JSON.parse(cfgLocalStorage) : {
  _v: 4,
  global: {},
  channels: {},
  flags: {
    shortsToRegular: false,
    newTab: false,
    copySubs: false,
    standardMusicSpeed: false,
    enhancedBitrate: false
  }
};

// src/config/save.ts
var saveLS = (newCfg) => {
  saveLSRaw(JSON.stringify(newCfg));
};
var saveLSRaw = (raw) => {
  localStorage.YTDefaulter = raw;
};
var save = (raw) => {
  const newCfg = JSON.parse(raw);
  if (typeof newCfg !== "object" || !newCfg._v) {
    throw new Error("Invalid data");
  }
  if (update(newCfg)) {
    saveLS(newCfg);
  } else {
    saveLSRaw(raw);
  }
  Object.assign(value, newCfg);
};
// src/config/prune.ts
var prune = () => {
  outer:
    for (const key in value.channels) {
      const channelCfg = value.channels[key];
      if (channelCfg.subtitles)
        continue;
      for (const cfgKey in channelCfg) {
        if (cfgKey !== "subtitles")
          continue outer;
      }
      delete value.channels[key];
    }
};
// src/utils/ref.ts
var ref = (val) => ({ val });

// src/config/current-channel.ts
var username = ref("");
var channel = () => value.channels[username.val] ||= {};
// src/text.ts
var translations = {
  "be-BY": {
    OPEN_SETTINGS: "Адкрыць дадатковыя налады",
    SUBTITLES: "Субтытры",
    SPEED: "Хуткасьць",
    CUSTOM_SPEED: "Свая Хуткасьць",
    CUSTOM_SPEED_HINT: 'Калі вызначана, будзе выкарыстоўвацца замест "Хуткасьць"',
    QUALITY: "Якасьць",
    VOLUME: "Гучнасьць, %",
    GLOBAL: "Глябальна",
    LOCAL: "Гэты Канал",
    SHORTS: "Адкрываць shorts як звычайныя",
    NEW_TAB: "Адкрываць відэа ў новай картцы",
    COPY_SUBS: "Капіяваць субтытры ў поўнаэкранным, Ctrl+C",
    STANDARD_MUSIC_SPEED: "Звычайная хуткасьць на каналах музыкаў",
    ENHANCED_BITRATE: "Палепшаны бітрэйт (для карыстальнікаў Premium)",
    HIDE_SHORTS: "Схаваць shorts на галоўнай",
    SAVE: "Захаваць",
    EXPORT: "Экспарт",
    IMPORT: "Імпарт"
  }
};
var text = {
  OPEN_SETTINGS: "Open additional settings",
  SUBTITLES: "Subtitles",
  SPEED: "Speed",
  CUSTOM_SPEED: "Custom Speed",
  CUSTOM_SPEED_HINT: 'If defined, will be used instead of "Speed"',
  QUALITY: "Quality",
  VOLUME: "Volume, %",
  GLOBAL: "Global",
  LOCAL: "This Channel",
  SHORTS: "Open shorts as regular videos",
  NEW_TAB: "Open videos in new tab",
  COPY_SUBS: "Copy subtitles by Ctrl+C in fullscreen mode",
  STANDARD_MUSIC_SPEED: "Normal speed on artist channels",
  ENHANCED_BITRATE: "Quality: Enhanced bitrate (for Premium users)",
  HIDE_SHORTS: "Hide shorts on the Home page",
  SAVE: "Save",
  DEFAULT: "-",
  EXPORT: "Export",
  IMPORT: "Import"
};

// src/utils/$.ts
var $ = (id) => document.getElementById(id);
// src/utils/debounce.ts
var debounce = (callback, delay) => {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = window.setTimeout(() => {
      callback(...args);
    }, delay);
  };
};
// src/utils/is-descendant-or-the-same.ts
var isDescendantOrTheSame = (child, parents) => {
  while (child !== null) {
    if (parents.includes(child))
      return true;
    child = child.parentNode;
  }
  return false;
};
// src/utils/restore-focus-after.ts
var restoreFocusAfter = (cb) => {
  const el = document.activeElement;
  cb();
  el.focus();
};
// src/utils/until.ts
var until = (getItem, check, msToWait = 1e4, msReqTimeout = 20) => new Promise((res, rej) => {
  const item = getItem();
  if (check(item))
    return res(item);
  const reqLimit = msToWait / msReqTimeout;
  let i = 0;
  const interval = setInterval(() => {
    const item2 = getItem();
    if (check(item2)) {
      clearInterval(interval);
      res(item2);
    } else if (++i > reqLimit) {
      clearInterval(interval);
      rej(new Error(`Timeout: item ${getItem.name} wasn't found`));
    }
  }, msReqTimeout);
});
var untilAppear = (getItem, msToWait) => until(getItem, Boolean, msToWait);
// src/utils/find-in-node-list.ts
var findInNodeList = (list, predicate) => {
  for (const item of list) {
    if (predicate(item))
      return item;
  }
  return null;
};
// src/utils/get-el-creator.ts
var getElCreator = (tag) => (props) => Object.assign(document.createElement(tag), props);
// src/utils/delay.ts
var delay = (ms) => new Promise((res) => setTimeout(res, ms));
// src/style.ts
var m = "#" + "YTDef-menu";
var d = " div";
var i = " input";
var s = " select";
var bg = "var(--yt-spec-menu-background)";
var underline = "border-bottom: 2px solid var(--yt-spec-text-primary);";
var style = getElCreator("style")({
  textContent: `
#${"YTDef-btn"} {position: relative; margin-left: 8px}
${m} {
display: flex;
visibility: hidden;
color: var(--yt-spec-text-primary);
font-size: 14px;
flex-direction: column;
position: fixed;
background: ${bg};
border-radius: 2rem;
padding: 1rem;
text-align: center;
box-shadow: 0px 4px 32px 0px var(--yt-spec-static-overlay-background-light);
z-index: 2202
}
.control-cont > button {margin: .2rem}
${m + d} {display: flex; margin-bottom: 1rem}
${m + d + d} {
flex-direction: column;
margin: 0 2rem
}
${m + d + d + d} {
flex-direction: row;
margin: 1rem 0
}
${m + s}, ${m + i} {
text-align: center;
background: ${bg};
border: none;
${underline}
color: inherit;
width: 5rem;
padding: 0;
margin-left: auto
}
${m} .${"YTDef-setting-hint"} {margin: 0; text-align: end}
${m + i} {outline: none}
${m + d + d + d}:focus-within > label, ${m} .check-cont:focus-within > label {${underline}}
${m} .check-cont {padding: 0 1rem}
${m + s} {appearance: none; outline: none}
${m} label {margin-right: 1.5rem; white-space: nowrap}
${m + i}::-webkit-outer-spin-button,
${m + i}::-webkit-inner-spin-button {-webkit-appearance: none; margin: 0}
${m + i}[type=number] {-moz-appearance: textfield}
${m + s}::-ms-expand {display: none}`
});

// src/listeners/click.ts
var onClick = (e) => {
  const { shortsToRegular, newTab } = value.flags;
  if (shortsToRegular || newTab) {
    let el = e.target;
    if (el.tagName !== "A") {
      el = el.closest("a");
    }
    if (el) {
      const isShorts = el.href.includes("/shorts/");
      if (shortsToRegular && isShorts) {
        el.href = el.href.replace("shorts/", "watch?v=");
      }
      const isNormal = el.href.includes("/watch?v=");
      if (newTab && (isShorts || isNormal)) {
        el.target = "_blank";
        e.stopPropagation();
      }
    }
  }
};
// src/element-getters.ts
var plr = () => $("movie_player");
var aboveTheFold = () => $("above-the-fold");
var actionsBar = () => $("actions")?.querySelector("ytd-menu-renderer");
var getPlrGetter = (plr2) => (selector) => () => plr2.querySelector(selector);
var plrGetters = (plr2) => {
  const get = getPlrGetter(plr2);
  return {
    ad: get(".ytp-ad-player-overlay"),
    video: get(".html5-main-video"),
    subtitlesBtn: get(".ytp-subtitles-button"),
    muteBtn: get(".ytp-mute-button"),
    menu: {
      element: get(".ytp-settings-menu"),
      btn: get(".ytp-settings-button")
    }
  };
};
var plrMenuItemsGetter = (menu) => () => menu.querySelectorAll('.ytp-menuitem[role="menuitem"]');
var menuSubItems = (item) => item.querySelectorAll(".ytp-menuitem-label");
var channelUsernameElementGetter = (aboveTheFold2) => () => aboveTheFold2.querySelector(".ytd-channel-name > a");
var artistChannelBadge = (aboveTheFold2) => aboveTheFold2.querySelector(".badge-style-type-verified-artist");
var videoPlr = () => document.querySelector(".html5-video-player");
var videoPlrCaptions = (plr2) => plr2.querySelectorAll(".captions-text > span");
var popupContainer = () => document.querySelector("ytd-popup-container");

// src/player/menu.ts
var set = (getEl) => {
  element ||= getEl.menu.element();
  btn ||= getEl.menu.btn();
};
var element = null;
var btn = null;
var clickBtn = () => {
  btn.click();
};
var isOpen = () => {
  return element.style.display !== "none";
};
var setOpen = (bool) => {
  if (bool !== isOpen())
    btn.click();
};
var openItem = (item) => {
  setOpen(true);
  item.click();
  return menuSubItems(element);
};
var settingItems = {
  speed: null,
  quality: null
};
var setSettingItems = (items) => {
  const findIcon = (d2) => findInNodeList(items, (el) => !!el.querySelector(`path[d="${d2}"]`));
  settingItems.speed = findIcon("M10,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z            M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z            M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z            M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z            M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z");
  settingItems.quality = findIcon("M15,17h6v1h-6V17z M11,17H3v1h8v2h1v-2v-1v-2h-1V17z M14,8h1V6V5V3h-1v2H3v1h11V8z            M18,5v1h3V5H18z M6,14h1v-2v-1V9H6v2H3v1 h3V14z M10,12h11v-1H10V12z");
};
var findInItem = (name, finder) => {
  const prevItems = new Set(menuSubItems(element));
  return findInNodeList(openItem(settingItems[name]), (item) => !prevItems.has(item) && finder(item));
};

// src/player/plr.ts
var setPlr = async (el) => {
  const getEl = plrGetters(el);
  await delay(1000);
  await until(getEl.ad, (ad) => !ad, 200000);
  video ||= getEl.video();
  subtitlesBtn ||= getEl.subtitlesBtn();
  muteBtn ||= getEl.muteBtn();
  set(getEl);
  restoreFocusAfter(clickBtn);
  await delay(50);
  restoreFocusAfter(clickBtn);
  setSettingItems(await until(plrMenuItemsGetter(element), (arr) => !!arr.length));
  if (!speedNormal)
    restoreFocusAfter(() => {
      speedNormal = findInItem("speed", (btn2) => !+btn2.textContent).textContent;
    });
};
var isSpeed = (value3) => video.playbackRate === value3;
var speedNormal = "";
var video = null;
var subtitlesBtn = null;
var muteBtn = null;
// src/logger.ts
var err = (...msgs) => {
  console.error("[YT-Defaulter]", ...msgs);
};
var outOfRange = (what) => {
  err(what, "value is out of range");
};

// src/player/value-setters.ts
var comparators = {
  quality: (target, current) => +target >= parseInt(current) && (value.flags.enhancedBitrate || !current.toLowerCase().includes("premium")),
  speed: (target, current) => target === current
};
var setYT = (settingName) => (value3) => {
  const isOpen2 = isOpen();
  const compare = comparators[settingName];
  const btn2 = findInItem(settingName, (btn3) => compare(value3, btn3.textContent));
  if (btn2) {
    btn2.click();
  }
  setOpen(isOpen2);
};
var valueSetters = {
  speed: (value3) => {
    setYT("speed")(isSpeed(+value3) ? speedNormal : value3);
  },
  customSpeed: (value3) => {
    try {
      video.playbackRate = isSpeed(+value3) ? 1 : +value3;
    } catch {
      outOfRange("Custom speed");
    }
  },
  subtitles: (value3) => {
    if (subtitlesBtn.ariaPressed !== value3.toString())
      subtitlesBtn.click();
  },
  volume: (value3) => {
    const num = +value3;
    const isMuted = muteBtn.dataset.titleNoTooltip !== "Mute";
    if (num === 0) {
      if (!isMuted)
        muteBtn.click();
    } else {
      if (isMuted)
        muteBtn.click();
      try {
        video.volume = num / 100;
      } catch {
        outOfRange("Volume");
      }
    }
  },
  quality: setYT("quality")
};
// src/player/apply-settings.ts
var applySettings = (settings) => {
  if (!Number.isNaN(+settings.customSpeed)) {
    valueSetters.customSpeed(settings.customSpeed);
  }
  delete settings.customSpeed;
  restoreFocusAfter(() => {
    for (const setting in settings) {
      valueSetters[setting](settings[setting]);
    }
    setOpen(false);
  });
};
// src/compute-settings.ts
var computeSettings = (doUseNormalSpeed) => {
  const channel2 = channel();
  const settings = {
    ...value.global,
    ...channel2
  };
  if (doUseNormalSpeed) {
    settings.speed = speedNormal;
    delete settings.customSpeed;
  } else if ("customSpeed" in channel2) {
    delete settings.speed;
  } else if ("speed" in channel2) {
    delete settings.customSpeed;
  } else if ("customSpeed" in settings) {
    delete settings.speed;
  }
  return settings;
};

// src/listeners/keyup.ts
var onKeyup = (e) => {
  if (e.code === "Enter") {
    onClick(e);
  } else if (e.ctrlKey && !e.shiftKey) {
    if (value.flags.copySubs && e.code === "KeyC") {
      const plr3 = videoPlr();
      if (plr3?.classList.contains("ytp-fullscreen")) {
        const text2 = Array.from(videoPlrCaptions(plr3), (line) => line.textContent).join(" ");
        navigator.clipboard.writeText(text2);
      }
    } else if (e.code === "Space") {
      e.stopPropagation();
      e.preventDefault();
      const settings = computeSettings(false);
      if (settings.speed) {
        restoreFocusAfter(() => {
          valueSetters.speed(settings.speed);
        });
      } else if (settings.customSpeed) {
        valueSetters.customSpeed(settings.customSpeed);
      }
    }
  }
};
// src/menu/controls.ts
var updateValuesIn = (controls, cfgPart) => {
  controls.speed.value = cfgPart.speed || text.DEFAULT;
  controls.customSpeed.value = cfgPart.customSpeed || "";
  controls.quality.value = cfgPart.quality || text.DEFAULT;
  controls.volume.value = cfgPart.volume || "";
  controls.subtitles.checked = cfgPart.subtitles || false;
};
var channelControls = () => ({
  speed: null,
  customSpeed: null,
  quality: null,
  volume: null,
  subtitles: null
});
var sections = {
  global: channelControls(),
  thisChannel: channelControls()
};
var flags = {
  shortsToRegular: {
    elem: null,
    id: "shorts",
    text: text.SHORTS
  },
  newTab: {
    elem: null,
    id: "new-tab",
    text: text.NEW_TAB
  },
  copySubs: {
    elem: null,
    id: "copy-subs",
    text: text.COPY_SUBS
  },
  standardMusicSpeed: {
    elem: null,
    id: "standard-music-speed",
    text: text.STANDARD_MUSIC_SPEED
  },
  enhancedBitrate: {
    elem: null,
    id: "enhanced-bitrate",
    text: text.ENHANCED_BITRATE
  },
  hideShorts: {
    elem: null,
    id: "hide-shorts",
    text: text.HIDE_SHORTS
  }
};
var updateThisChannel = () => {
  updateValuesIn(sections.thisChannel, channel());
};
var updateValues = (cfg) => {
  updateValuesIn(sections.global, cfg.global);
  updateThisChannel();
  for (const key in cfg.flags) {
    flags[key].elem.checked = cfg.flags[key];
  }
};
// src/utils/with.ts
var withHint = (hint, getItem) => [getItem(hint).item, hint.element];
var withOnClick = (elem, listener) => {
  elem.addEventListener("click", listener);
  return elem;
};
var withListeners = (elem, listeners) => {
  for (const key in listeners) {
    elem.addEventListener(key, listeners[key]);
  }
  return elem;
};
var controlWith = (withFn) => (obj, ...args) => {
  withFn(obj.elem, ...args);
  return obj;
};
var withControlListeners = controlWith(withListeners);

// src/utils/element-creators.ts
var div = getElCreator("div");
var input = getElCreator("input");
var checkbox = (props) => input({ type: "checkbox", ...props });
var option = getElCreator("option");
var _label = getElCreator("label");
var labelEl = (forId, props) => {
  const elem = _label(props);
  elem.setAttribute("for", forId);
  return elem;
};
var selectEl = getElCreator("select");
var btnClass = "yt-spec-button-shape-next";
var btnClassFocused = btnClass + "--focused";
var _button = getElCreator("button");
var button = (textContent, props) => withListeners(_button({
  textContent,
  className: `${btnClass} ${btnClass}--tonal ${btnClass}--mono ${btnClass}--size-m`,
  ...props
}), {
  focus() {
    this.classList.add(btnClassFocused);
  },
  blur() {
    this.classList.remove(btnClassFocused);
  }
});

// src/menu/get-controls-creators.ts
var getControlCreators = (getCreator) => ({
  numericInput: getCreator((props) => input({ type: "number", ...props }), (elem) => ({
    get: () => elem.value,
    set: (value3) => {
      elem.value = value3;
    },
    default: ""
  })),
  checkbox: getCreator(checkbox, (elem) => ({
    get: () => elem.checked.toString(),
    set: (value3) => {
      elem.checked = value3 === "true";
    },
    default: text.DEFAULT
  })),
  select: getCreator(({
    values,
    getText
  }) => {
    const elem = selectEl({ value: text.DEFAULT });
    elem.append(option({
      value: text.DEFAULT,
      textContent: text.DEFAULT
    }), ...values.map((value3) => option({
      value: value3,
      textContent: getText(value3)
    })));
    return elem;
  }, (elem) => ({
    get: () => elem.value,
    set: (value3) => {
      elem.value = value3;
    },
    default: "false"
  }))
});

// src/menu/validate-volume.ts
var validateVolume = (value3) => {
  const num = +value3;
  return num < 0 || num > 100 ? "out of range" : isNaN(num) ? "not a number" : null;
};

// src/hint.ts
class Hint {
  constructor(prefix) {
    this.element = div();
    this.element.className ||= "YTDef-setting-hint";
    this.prefix = prefix;
    this.hide();
  }
  hide() {
    this.element.style.display = "none";
  }
  show(msg) {
    this.element.style.display = "block";
    this.element.textContent = this.prefix + msg;
  }
  prefix;
  element;
}

// src/menu/close.ts
var close = () => {
  element2.style.visibility = "hidden";
  stopListening();
};
var listenForClose = () => {
  document.addEventListener("click", onClick2);
  document.addEventListener("keyup", onKeyUp);
};
var stopListening = () => {
  document.removeEventListener("click", onClick2);
  document.removeEventListener("keyup", onKeyUp);
};
var onClick2 = (e) => {
  const el = e.target;
  if (!isDescendantOrTheSame(el, [element2, btn2]))
    close();
};
var onKeyUp = (e) => {
  if (e.code === "Escape") {
    close();
    btn2.focus();
  }
};

// src/menu/value.ts
var set2 = (el, btnEl) => {
  element2 = el;
  btn2 = btnEl;
};
var element2 = null;
var btn2 = null;
var isOpen2 = false;
var menuWidth = 0;
var adjustWidth = () => {
  menuWidth = element2.getBoundingClientRect().width;
};
var firstFocusable = ref(null);
var toggle = debounce(() => {
  isOpen2 = !isOpen2;
  if (isOpen2) {
    fixPosition();
    element2.style.visibility = "visible";
    listenForClose();
    firstFocusable.val.focus();
  } else {
    close();
  }
}, 100);
var fixPosition = () => {
  const { y, height, width, left } = btn2.getBoundingClientRect();
  element2.style.top = y + height + 8 + "px";
  element2.style.left = left + width - menuWidth + "px";
};

// src/menu/section.ts
var section = (sectionId, title, sectionCfg) => {
  const control = getControlCreators((createElement, initVal) => (name, label, props) => {
    const item = div();
    const id = "YTDef-" + name + "-" + sectionId;
    const elem = Object.assign(createElement(props), props, {
      id,
      name
    });
    elem.addEventListener("change", () => {
      const value3 = val.get();
      if (value3 === val.default) {
        delete sectionCfg[name];
      } else {
        sectionCfg[name] = value3;
      }
    });
    const val = initVal(elem);
    const cfgValue = sectionCfg[name];
    if (cfgValue) {
      setTimeout(() => {
        val.set(cfgValue.toString());
      });
    }
    item.append(labelEl(id, { textContent: label }), elem);
    sections[sectionId][name] = elem;
    return { item, elem };
  });
  const speedSelect = control.select("speed", text.SPEED, {
    values: ["2", "1.75", "1.5", "1.25", speedNormal, "0.75", "0.5", "0.25"],
    getText: (val) => val
  });
  if (sectionId === "global")
    firstFocusable.val = speedSelect.elem;
  const sectionElement = div({ role: "group" });
  sectionElement.setAttribute("aria-labelledby", sectionId);
  sectionElement.append(getElCreator("span")({ textContent: title, id: sectionId }), speedSelect.item, ...withHint(new Hint(""), (hint) => withControlListeners(control.numericInput("customSpeed", text.CUSTOM_SPEED), {
    blur: () => {
      hint.hide();
    },
    focus: () => {
      hint.show(text.CUSTOM_SPEED_HINT);
    }
  })), control.select("quality", text.QUALITY, {
    values: [
      "144",
      "240",
      "360",
      "480",
      "720",
      "1080",
      "1440",
      "2160",
      "4320"
    ],
    getText: (val) => val + "p"
  }).item, ...withHint(new Hint("Warning: "), (hint) => withControlListeners(control.numericInput("volume", text.VOLUME, {
    min: "0",
    max: "100"
  }), {
    blur() {
      const warning = validateVolume(this.value);
      if (warning) {
        hint.show(warning);
      } else {
        hint.hide();
      }
    }
  })), control.checkbox("subtitles", text.SUBTITLES, checkbox()).item);
  return sectionElement;
};

// src/menu/settings-icon.ts
var settingsIcon = () => {
  const element3 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  for (const [prop, value3] of [
    ["viewBox", "0 0 24 24"],
    ["width", "24"],
    ["height", "24"],
    ["fill", "var(--yt-spec-text-primary)"]
  ]) {
    element3.setAttribute(prop, value3);
  }
  element3.append($("settings"));
  return element3;
};

// src/menu/init.ts
var init = () => {
  const sections2 = div({ className: "YTDef-" + "sections" });
  sections2.append(section("global", text.GLOBAL, value.global), section("thisChannel", text.LOCAL, channel()));
  const controlStatus = div();
  const updateControlStatus = (content) => {
    controlStatus.textContent = `[${new Date().toLocaleTimeString()}] ${content}`;
  };
  const controlDiv = div({ className: "control-cont" });
  controlDiv.append(withOnClick(button(text.SAVE), () => {
    prune();
    saveLS(value);
    updateControlStatus(text.SAVE);
  }), withOnClick(button(text.EXPORT), () => {
    navigator.clipboard.writeText(localStorage.YTDefaulter).then(() => {
      updateControlStatus(text.EXPORT);
    });
  }), withOnClick(button(text.IMPORT), () => {
    navigator.clipboard.readText().then((raw) => {
      save(raw);
      updateValues(value);
      return text.IMPORT;
    }).catch((e) => text.IMPORT + ": " + e.message).then(updateControlStatus);
  }));
  set2(div({
    id: "YTDef-menu"
  }), withOnClick(button("", {
    id: "YTDef-btn",
    ariaLabel: text.OPEN_SETTINGS,
    tabIndex: 0
  }), toggle));
  btn2.setAttribute("aria-controls", "YTDef-menu");
  btn2.classList.add(btnClass + "--icon-button");
  btn2.append(settingsIcon());
  element2.append(sections2, ...Object.entries(flags).map(([flagName, flag]) => {
    const cont = div({ className: "check-cont" });
    const id = "YTDef-" + flag.id;
    const elem = withOnClick(checkbox({
      id,
      checked: value.flags[flagName]
    }), function() {
      value.flags[flagName] = this.checked;
    });
    flag.elem = elem;
    cont.append(labelEl(id, { textContent: flag.text }), elem);
    return cont;
  }), controlDiv, controlStatus);
  element2.addEventListener("keyup", (e) => {
    const el = e.target;
    if (e.code === "Enter" && el.type === "checkbox")
      el.checked = !el.checked;
  });
  untilAppear(actionsBar).then((actionsBar2) => {
    actionsBar2.insertBefore(btn2, actionsBar2.lastChild);
    popupContainer().append(element2);
    adjustWidth();
    sections2.style.maxWidth = sections2.offsetWidth + "px";
  });
  const listener = () => {
    if (isOpen2)
      fixPosition();
  };
  window.addEventListener("scroll", listener);
  window.addEventListener("resize", listener);
};
// src/listeners/video-page.ts
var onVideoPage = async () => {
  const aboveTheFold2 = await untilAppear(aboveTheFold);
  username.val = (await untilAppear(channelUsernameElementGetter(aboveTheFold2))).href || "";
  untilAppear(plr).then(setPlr).then(() => {
    const doNotChangeSpeed = value.flags.standardMusicSpeed && !!artistChannelBadge(aboveTheFold2);
    applySettings(computeSettings(doNotChangeSpeed));
    if (!element2) {
      init();
    }
  });
  if (element2) {
    updateThisChannel();
  }
};
// src/listeners/page-change.ts
var lastUrl;
var onPageChange = (url) => {
  if (lastUrl !== url) {
    lastUrl = url;
    if (location.pathname.startsWith("/live") || location.pathname === "/watch") {
      setTimeout(onVideoPage, 1000);
    }
  }
};

// src/index.ts
Object.assign(text, translations[document.documentElement.lang]);
if (update(value)) {
  saveLS(value);
}
var updatePage = () => {
  onPageChange(location.href);
};
if (window.onurlchange === null) {
  window.addEventListener("urlchange", ({ url }) => {
    onPageChange(url);
  });
}
setInterval(() => {
  if (window.onurlchange !== null)
    updatePage();
  if (value.flags.hideShorts)
    findInNodeList(document.querySelectorAll("#title"), (el) => el.textContent === "Shorts")?.closest("ytd-rich-section-renderer")?.remove();
}, 1000);
updatePage();
document.addEventListener("click", onClick, { capture: true });
document.addEventListener("keyup", onKeyup, { capture: true });
document.head.append(style);
})()