YouTube Defaulter

Set speed, quality, subtitles and volume as default globally or specialize for each channel

目前為 2024-12-21 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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.11.12
// @description  Set speed, quality, subtitles and volume as default globally or specialize for each 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: {
    shortsToUsual: 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/config/current-channel.ts
var channel = {
  username: "",
  get() {
    value.channels[this.username] ||= {};
    return value.channels[this.username];
  }
};
// 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)",
    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 a usual video",
  NEW_TAB: "Open videos in a 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)",
  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 function(...args) {
    clearTimeout(timeout);
    timeout = window.setTimeout(() => {
      callback.apply(this, 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 wasn't found"));
    }
  }, msReqTimeout);
});
var untilAppear = (getItem, msToWait) => until(getItem, Boolean, msToWait);
// src/utils/find-in-node-list.ts
var findInNodeList = (list, finder) => {
  for (const item of list) {
    if (finder(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 { shortsToUsual, newTab } = value.flags;
  if (shortsToUsual || newTab) {
    let el = e.target;
    if (el.tagName !== "A") {
      el = el.closest("a");
    }
    if (el) {
      const isShorts = el.href.includes("/shorts/");
      if (shortsToUsual && isShorts) {
        el.href = el.href.replace("shorts/", "watch?v=");
      }
      const isUsual = el.href.includes("/watch?v=");
      if (newTab && (isShorts || isUsual)) {
        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 plrGetters = (plr2) => ({
  ad: () => plr2.querySelector(".ytp-ad-player-overlay"),
  video: () => plr2.querySelector(".html5-main-video"),
  subtitlesBtn: () => plr2.querySelector(".ytp-subtitles-button"),
  muteBtn: () => plr2.querySelector(".ytp-mute-button"),
  menu: {
    element: () => plr2.querySelector(".ytp-settings-menu"),
    btn: () => plr2.querySelector(".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/plr.ts
var plr2 = {
  async set(el) {
    const getEl = plrGetters(el);
    this.isSpeedApplied = false;
    await delay(1000);
    await until(getEl.ad, (ad) => !ad, 200000);
    this.video ||= getEl.video();
    this.subtitlesBtn ||= getEl.subtitlesBtn();
    this.muteBtn ||= getEl.muteBtn();
    this.menu.element ||= getEl.menu.element();
    this.menu._btn ||= getEl.menu.btn();
    const clickBtn = () => {
      this.menu._btn.click();
    };
    restoreFocusAfter(clickBtn);
    await delay(50);
    restoreFocusAfter(clickBtn);
    plr2.menu.setSettingItems(await until(plrMenuItemsGetter(plr2.menu.element), (arr) => !!arr.length));
    if (!this.speedNormal)
      restoreFocusAfter(() => {
        this.speedNormal = plr2.menu.findInItem("speed", (btn) => !+btn.textContent).textContent;
      });
  },
  isSpeedApplied: false,
  speedNormal: "",
  element: null,
  video: null,
  subtitlesBtn: null,
  muteBtn: null,
  menu: {
    element: null,
    _btn: null,
    isOpen() {
      return this.element.style.display !== "none";
    },
    setOpen(bool) {
      if (bool !== this.isOpen())
        this._btn.click();
    },
    openItem(item) {
      this.setOpen(true);
      item.click();
      return menuSubItems(this.element);
    },
    settingItems: {
      speed: null,
      quality: null
    },
    setSettingItems(items) {
      const findIcon = (d2) => findInNodeList(items, (el) => !!el.querySelector(`path[d="${d2}"]`));
      this.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");
      this.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");
    },
    findInItem(name, finder) {
      const prevItems = new Set(menuSubItems(this.element));
      return findInNodeList(this.openItem(this.settingItems[name]), (item) => !prevItems.has(item) && finder(item));
    }
  }
};
// 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 valueSetters = {
  _ytSettingItem(settingName, value3) {
    const isOpen = plr2.menu.isOpen();
    const compare = comparators[settingName];
    const btn = plr2.menu.findInItem(settingName, (btn2) => compare(value3, btn2.textContent));
    if (btn) {
      btn.click();
      plr2.menu.setOpen(isOpen);
    }
  },
  speed(value3) {
    this._ytSettingItem("speed", plr2.isSpeedApplied ? plr2.speedNormal : value3);
    plr2.isSpeedApplied = !plr2.isSpeedApplied;
  },
  customSpeed(value3) {
    try {
      plr2.video.playbackRate = plr2.isSpeedApplied ? 1 : +value3;
    } catch {
      outOfRange("Custom speed");
      return;
    }
    plr2.isSpeedApplied = !plr2.isSpeedApplied;
  },
  subtitles(value3) {
    if (plr2.subtitlesBtn.ariaPressed !== value3.toString())
      plr2.subtitlesBtn.click();
  },
  volume(value3) {
    const num = +value3;
    const isMuted = plr2.muteBtn.dataset.titleNoTooltip !== "Mute";
    if (num === 0) {
      if (!isMuted)
        plr2.muteBtn.click();
    } else {
      if (isMuted)
        plr2.muteBtn.click();
      try {
        plr2.video.volume = num / 100;
      } catch {
        outOfRange("Volume");
      }
    }
  },
  quality(value3) {
    this._ytSettingItem("quality", value3);
  }
};
// src/player/apply-settings.ts
var applySettings = (settings) => {
  if (!isNaN(+settings.customSpeed)) {
    valueSetters.customSpeed(settings.customSpeed);
  }
  delete settings.customSpeed;
  restoreFocusAfter(() => {
    for (const setting in settings) {
      valueSetters[setting](settings[setting]);
    }
    plr2.menu.setOpen(false);
  });
};
// src/compute-settings.ts
var computeSettings = (doUseNormalSpeed) => {
  const channel2 = channel.get();
  const settings = {
    ...value.global,
    ...channel2
  };
  if (doUseNormalSpeed) {
    settings.speed = plr2.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 plr4 = videoPlr();
      if (plr4?.classList.contains("ytp-fullscreen")) {
        const text2 = Array.from(videoPlrCaptions(plr4), (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 controls = {
  global: channelControls(),
  thisChannel: channelControls(),
  flags: {
    shortsToUsual: null,
    newTab: null,
    copySubs: null,
    standardMusicSpeed: null,
    enhancedBitrate: null
  },
  updateThisChannel(channelConfig) {
    updateValuesIn(this.thisChannel, channelConfig);
  },
  updateValues(cfg) {
    updateValuesIn(this.global, cfg.global);
    this.updateThisChannel();
    for (const key in cfg.flags) {
      this.flags[key].checked = cfg.flags[key];
    }
  }
};
// src/utils/with.ts
var withHint = (hint, getItem) => [hint.element, getItem(hint).item];
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/value.ts
var value3 = {
  element: null,
  btn: null,
  isOpen: false,
  width: 0,
  _closeListener: {
    onClick(e) {
      const el = e.target;
      if (!isDescendantOrTheSame(el, [value3.element, value3.btn]))
        value3.toggle();
    },
    onKeyUp(e) {
      if (e.code === "Escape") {
        value3._setOpen(false);
        value3.btn.focus();
      }
    },
    add() {
      document.addEventListener("click", this.onClick);
      document.addEventListener("keyup", this.onKeyUp);
    },
    remove() {
      document.removeEventListener("click", this.onClick);
      document.removeEventListener("keyup", this.onKeyUp);
    }
  },
  firstFocusable: null,
  _setOpen(bool) {
    if (bool) {
      this.fixPosition();
      this.element.style.visibility = "visible";
      this._closeListener.add();
      this.firstFocusable.focus();
    } else {
      this.element.style.visibility = "hidden";
      this._closeListener.remove();
    }
    this.isOpen = bool;
  },
  toggle: debounce(function() {
    this._setOpen(!this.isOpen);
  }, 100),
  fixPosition() {
    const { y, height, width, left } = this.btn.getBoundingClientRect();
    this.element.style.top = y + height + 8 + "px";
    this.element.style.left = left + width - this.width + "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 value4 = val.get();
      if (value4 === val.default) {
        delete sectionCfg[name];
      } else {
        sectionCfg[name] = value4;
      }
    });
    const val = initVal(elem);
    const cfgValue = sectionCfg[name];
    if (cfgValue) {
      setTimeout(() => {
        val.set(cfgValue.toString());
      });
    }
    item.append(labelEl(id, { textContent: label }), elem);
    controls[sectionId][name] = elem;
    return { item, elem };
  });
  const speedSelect = control.select("speed", text.SPEED, {
    values: [
      "2",
      "1.75",
      "1.5",
      "1.25",
      plr2.speedNormal,
      "0.75",
      "0.5",
      "0.25"
    ],
    getText: (val) => val
  });
  if (sectionId === "global")
    value3.firstFocusable = 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 element = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  for (const [prop, value4] of [
    ["viewBox", "0 0 24 24"],
    ["width", "24"],
    ["height", "24"],
    ["fill", "var(--yt-spec-text-primary)"]
  ]) {
    element.setAttribute(prop, value4);
  }
  element.append($("settings"));
  return element;
};

// src/menu/init.ts
var controlCheckboxDiv = (id, flagName, textContent) => {
  const cont = div({ className: "check-cont" });
  id = "YTDef-" + id;
  const elem = withOnClick(checkbox({
    id,
    checked: value.flags[flagName]
  }), function() {
    value.flags[flagName] = this.checked;
  });
  controls.flags[flagName] = elem;
  cont.append(labelEl(id, { textContent }), elem);
  return cont;
};
var init = async () => {
  const sections = div({ className: "YTDef-" + "sections" });
  sections.append(section("global", text.GLOBAL, value.global), section("thisChannel", text.LOCAL, channel.get()));
  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), async () => {
    try {
      save(await navigator.clipboard.readText());
    } catch (e) {
      updateControlStatus("Import: " + e.message);
      return;
    }
    updateControlStatus(text.IMPORT);
    controls.updateValues(value);
  }));
  value3.btn = withOnClick(button("", {
    id: "YTDef-btn",
    ariaLabel: text.OPEN_SETTINGS,
    tabIndex: 0
  }), () => {
    value3.toggle();
  });
  value3.btn.setAttribute("aria-controls", "YTDef-menu");
  value3.btn.classList.add(btnClass + "--icon-button");
  value3.btn.append(settingsIcon());
  value3.element = div({
    id: "YTDef-menu"
  });
  value3.element.append(sections, controlCheckboxDiv("shorts", "shortsToUsual", text.SHORTS), controlCheckboxDiv("new-tab", "newTab", text.NEW_TAB), controlCheckboxDiv("copy-subs", "copySubs", text.COPY_SUBS), controlCheckboxDiv("standard-music-speed", "standardMusicSpeed", text.STANDARD_MUSIC_SPEED), controlCheckboxDiv("enhanced-bitrate", "enhancedBitrate", text.ENHANCED_BITRATE), controlDiv, controlStatus);
  value3.element.addEventListener("keyup", (e) => {
    const el = e.target;
    if (e.code === "Enter" && el.type === "checkbox")
      el.checked = !el.checked;
  });
  const actionsBar2 = await untilAppear(actionsBar);
  actionsBar2.insertBefore(value3.btn, actionsBar2.lastChild);
  popupContainer().append(value3.element);
  value3.width = value3.element.getBoundingClientRect().width;
  sections.style.maxWidth = sections.offsetWidth + "px";
  const listener = () => {
    if (value3.isOpen)
      value3.fixPosition();
  };
  window.addEventListener("scroll", listener);
  window.addEventListener("resize", listener);
};
// src/listeners/video-page.ts
var onVideoPage = async () => {
  const aboveTheFold2 = await untilAppear(aboveTheFold);
  channel.username = (await untilAppear(channelUsernameElementGetter(aboveTheFold2))).href || "";
  const plrSettingPromise = untilAppear(plr).then((elem) => plr2.set(elem));
  plrSettingPromise.then(() => {
    const doNotChangeSpeed = value.flags.standardMusicSpeed && !!artistChannelBadge(aboveTheFold2);
    applySettings(computeSettings(doNotChangeSpeed));
  });
  if (value3.element) {
    controls.updateThisChannel(channel.get());
  } else {
    plrSettingPromise.then(() => {
      init();
    });
  }
};
// src/listeners/page-change.ts
var lastUrl;
var onPageChange = (url) => {
  if (lastUrl !== url) {
    lastUrl = url;
    if (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);
  });
  updatePage();
} else {
  setInterval(updatePage, 1000);
}
document.addEventListener("click", onClick, { capture: true });
document.addEventListener("keyup", onKeyup, { capture: true });
document.head.append(style);
})()