YouTube Defaulter

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

目前为 2024-12-21 提交的版本。查看 最新版本

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