YouTube Defaulter

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

当前为 2024-12-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Defaulter
// @namespace    https://greasyfork.org/ru/users/901750-gooseob
// @version      1.11.4
// @description  Set speed, quality, subtitles and volume as default globally or specialize for each channel
// @author       GooseOb
// @license      MIT
// @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/on-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(item);
    },
    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) {
      return findInNodeList(this.openItem(this.settingItems[name]), finder);
    }
  }
};
// src/logger.ts
var logger = {
  err(...msgs) {
    console.error("[YT-Defaulter]", ...msgs);
  },
  outOfRange(what) {
    this.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 {
      logger.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 {
        logger.outOfRange("Volume");
      }
    }
  },
  quality(value3) {
    this._ytSettingItem("quality", value3);
  }
};
// src/player/apply-settings.ts
var applySettings = (settings) => {
  restoreFocusAfter(() => {
    if (!isNaN(+settings.customSpeed)) {
      valueSetters.customSpeed(settings.customSpeed);
    }
    delete settings.customSpeed;
    for (const setting in settings) {
      valueSetters[setting](settings[setting]);
    }
    plr2.menu.setOpen(false);
  });
};
// src/compute-settings.ts
var computeSettings = (doNotChangeSpeed) => {
  const settings = {
    ...value.global,
    ...channel.get()
  };
  const isChannelSpeed = "speed" in channel.get();
  const isChannelCustomSpeed = "customSpeed" in channel.get();
  if (doNotChangeSpeed) {
    settings.speed = plr2.speedNormal;
    delete settings.customSpeed;
  } else if (isChannelCustomSpeed) {
    delete settings.speed;
  } else if (isChannelSpeed) {
    delete settings.customSpeed;
  }
  return settings;
};

// src/listeners/on-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/on-video-page.ts
var onVideoPage = async () => {
  const aboveTheFold2 = await untilAppear(aboveTheFold);
  channel.username = (await untilAppear(channelUsernameElementGetter(aboveTheFold2))).href || "";
  await plr2.set(await untilAppear(plr));
  const doNotChangeSpeed = value.flags.standardMusicSpeed && !!artistChannelBadge(aboveTheFold2);
  applySettings(computeSettings(doNotChangeSpeed));
  if (value3.element) {
    controls.updateThisChannel(channel.get());
  } else {
    await init();
  }
};
// src/index.ts
Object.assign(text, translations[document.documentElement.lang]);
if (update(value)) {
  saveLS(value);
}
var lastHref;
setInterval(() => {
  if (lastHref !== location.href) {
    lastHref = location.href;
    if (location.pathname === "/watch") {
      setTimeout(onVideoPage, 1000);
    }
  }
}, 1000);
document.addEventListener("click", onClick, { capture: true });
document.addEventListener("keyup", onKeyup, { capture: true });
document.head.append(style);
})()