123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- import { addGlobalStyle } from "@sv443-network/userutils";
- import { initOnSelector } from "./utils";
- import { clearConfig, getFeatures, initConfig } from "./config";
- import { defaultLogLevel, mode, scriptInfo } from "./constants";
- import { error, getDomain, info, getSessionId, log, setLogLevel } from "./utils";
- import { initSiteEvents, siteEvents } from "./siteEvents";
- import { initTranslations, setLocale } from "./translations";
- import { emitInterface, initInterface } from "./interface";
- import { addCfgMenu } from "./menu/menu_old";
- import { addWelcomeMenu, showWelcomeMenu } from "./menu/welcomeMenu";
- import { initObservers, observers } from "./observers";
- import {
- // other:
- featInfo,
- // features:
- // layout
- preInitLayout,
- addWatermark,
- removeUpgradeTab, initVolumeFeatures,
- removeShareTrackingParam, fixSpacing,
- addScrollToActiveBtn,
- // song lists
- preInitSongLists,
- initQueueButtons,
- // behavior
- preInitBehavior,
- initBeforeUnloadHook, disableBeforeUnload,
- initAutoCloseToasts, initRememberSongTime,
- disableDarkReader,
- // input
- preInitInput,
- initArrowKeySkip, initSiteSwitch,
- addAnchorImprovements, initNumKeysSkip,
- // lyrics
- addMediaCtrlLyricsBtn, geniUrlBase,
- // menu
- addConfigMenuOption,
- // other
- checkVersion,
- } from "./features/index";
- {
- // console watermark with sexy gradient
- const styleGradient = "background: rgba(165, 38, 38, 1); background: linear-gradient(90deg, rgb(154, 31, 103) 0%, rgb(135, 31, 31) 40%, rgb(184, 64, 41) 100%);";
- const styleCommon = "color: #fff; font-size: 1.5em; padding-left: 6px; padding-right: 6px;";
- console.log();
- console.log(
- `%c${scriptInfo.name}%cv${scriptInfo.version}%c\n\nBuild ${scriptInfo.buildNumber} ─ ${scriptInfo.namespace}`,
- `font-weight: bold; ${styleCommon} ${styleGradient}`,
- `background-color: #333; ${styleCommon}`,
- "padding: initial;",
- );
- console.log([
- "Powered by:",
- "─ Lots of ambition",
- `─ My song metadata API: ${geniUrlBase}`,
- "─ My userscript utility library: https://github.com/Sv443-Network/UserUtils",
- "─ This tiny event listener library: https://github.com/ai/nanoevents",
- "─ The React library: https://github.com/facebook/react",
- ].join("\n"));
- console.log();
- }
- let domLoaded = false;
- const domain = getDomain();
- /** Stuff that needs to be called ASAP, before anything async happens */
- function preInit() {
- log("Session ID:", getSessionId());
- initInterface();
- setLogLevel(defaultLogLevel);
- if(domain === "ytm")
- initBeforeUnloadHook();
- init();
- }
- async function init() {
- try {
- registerMenuCommands();
- }
- catch(e) {
- void e;
- }
- try {
- document.addEventListener("DOMContentLoaded", () => {
- domLoaded = true;
- });
- const features = await initConfig();
- await initTranslations(features.locale ?? "en_US");
- setLocale(features.locale ?? "en_US");
- setLogLevel(features.logLevel);
- preInitLayout(features);
- preInitBehavior(features);
- preInitInput(features);
- preInitSongLists(features);
- if(features.disableBeforeUnloadPopup && domain === "ytm")
- disableBeforeUnload();
- if(!domLoaded)
- document.addEventListener("DOMContentLoaded", onDomLoad);
- else
- onDomLoad();
- if(features.rememberSongTime)
- initRememberSongTime();
- }
- catch(err) {
- error("General Error:", err);
- }
- // init menu separately from features
- try {
- void "TODO(v1.2):";
- // initMenu();
- }
- catch(err) {
- error("Couldn't initialize menu:", err);
- }
- }
- /** Called when the DOM has finished loading and can be queried and altered by the userscript */
- async function onDomLoad() {
- // post-build these double quotes are replaced by backticks (because if backticks are used here, the bundler converts them to double quotes)
- addGlobalStyle("#{{GLOBAL_STYLE}}");
- initObservers();
- initOnSelector();
- const features = getFeatures();
- const ftInit = [] as Promise<void>[];
- await checkVersion();
- log(`DOM loaded. Initializing features for domain "${domain}"...`);
- try {
- if(domain === "ytm") {
- disableDarkReader();
- ftInit.push(initSiteEvents());
- if(typeof await GM.getValue("bytm-installed") !== "string") {
- // open welcome menu with language selector
- await addWelcomeMenu();
- info("Showing welcome menu");
- await showWelcomeMenu();
- await GM.setValue("bytm-installed", JSON.stringify({ timestamp: Date.now(), version: scriptInfo.version }));
- }
- try {
- ftInit.push(addCfgMenu()); // TODO(v1.2): remove
- }
- catch(err) {
- error("Couldn't add menu:", err);
- }
- observers.body.addListener("tp-yt-iron-dropdown #contentWrapper ytd-multi-page-menu-renderer #container.menu-container", {
- listener: addConfigMenuOption,
- });
- if(features.arrowKeySupport)
- ftInit.push(initArrowKeySkip());
- if(features.removeUpgradeTab)
- ftInit.push(removeUpgradeTab());
- if(features.watermarkEnabled)
- ftInit.push(addWatermark());
- if(features.geniusLyrics)
- ftInit.push(addMediaCtrlLyricsBtn());
- if(features.deleteFromQueueButton || features.lyricsQueueButton)
- ftInit.push(initQueueButtons());
- if(features.anchorImprovements)
- ftInit.push(addAnchorImprovements());
- if(features.closeToastsTimeout > 0)
- ftInit.push(initAutoCloseToasts());
- if(features.removeShareTrackingParam)
- ftInit.push(removeShareTrackingParam());
- if(features.numKeysSkipToTime)
- ftInit.push(initNumKeysSkip());
- if(features.fixSpacing)
- ftInit.push(fixSpacing());
- if(features.scrollToActiveSongBtn)
- ftInit.push(addScrollToActiveBtn());
- ftInit.push(initVolumeFeatures());
- }
- if(["ytm", "yt"].includes(domain)) {
- if(features.switchBetweenSites)
- ftInit.push(initSiteSwitch(domain));
- }
- Promise.allSettled(ftInit).then(() => {
- emitInterface("bytm:ready");
- });
- }
- catch(err) {
- error("Feature error:", err);
- }
- }
- void ["TODO:", initFeatures];
- async function initFeatures() {
- const ftInit = [] as Promise<void>[];
- log(`DOM loaded. Initializing features for domain "${domain}"...`);
- for(const [ftKey, ftInfo] of Object.entries(featInfo)) {
- try {
- const res = ftInfo.enable() as void | Promise<void>;
- if(res instanceof Promise)
- ftInit.push(res);
- else
- ftInit.push(Promise.resolve());
- }
- catch(err) {
- error(`Couldn't initialize feature "${ftKey}" due to error:`, err);
- }
- }
- siteEvents.on("configOptionChanged", (ftKey, oldValue, newValue) => {
- try {
- // @ts-ignore
- if(featInfo[ftKey].change) {
- // @ts-ignore
- featInfo[ftKey].change(oldValue, newValue);
- }
- // @ts-ignore
- else if(featInfo[ftKey].disable) {
- // @ts-ignore
- const disableRes = featInfo[ftKey].disable();
- if(disableRes instanceof Promise)
- disableRes.then(() => featInfo[ftKey].enable());
- else
- featInfo[ftKey].enable();
- }
- else {
- // TODO: set "page reload required" flag in new menu
- if(confirm("[Work in progress]\nYou changed an option that requires a page reload to be applied.\nReload the page now?")) {
- disableBeforeUnload();
- location.reload();
- }
- }
- }
- catch(err) {
- error(`Couldn't change feature "${ftKey}" due to error:`, err);
- }
- });
- Promise.all(ftInit).then(() => {
- emitInterface("bytm:ready");
- });
- }
- function registerMenuCommands() {
- if(mode === "development") {
- GM.registerMenuCommand("Reset config", async () => {
- if(confirm("Reset the configuration to its default values?\nThis will automatically reload the page.")) {
- await clearConfig();
- disableBeforeUnload();
- location.reload();
- }
- }, "r");
- GM.registerMenuCommand("List GM values", async () => {
- const keys = await GM.listValues();
- console.log("GM values:");
- if(keys.length === 0)
- console.log(" No values found.");
- for(const key of keys)
- console.log(` ${key} -> ${await GM.getValue(key)}`);
- alert("See console.");
- }, "l");
- GM.registerMenuCommand("Delete all GM values", async () => {
- if(confirm("Clear all GM values?\nSee console for details.")) {
- const keys = await GM.listValues();
- console.log("Clearing GM values:");
- if(keys.length === 0)
- console.log(" No values found.");
- for(const key of keys) {
- await GM.deleteValue(key);
- console.log(` Deleted ${key}`);
- }
- }
- }, "d");
- GM.registerMenuCommand("Delete GM value by name", async () => {
- const key = prompt("Enter the name of the GM value to delete.\nEmpty input cancels the operation.");
- if(key && key.length > 0) {
- const oldVal = await GM.getValue(key);
- await GM.deleteValue(key);
- console.log(`Deleted GM value '${key}' with previous value '${oldVal}'`);
- }
- }, "n");
- GM.registerMenuCommand("Reset install timestamp", async () => {
- await GM.deleteValue("bytm-installed");
- console.log("Reset install time.");
- }, "t");
- GM.registerMenuCommand("Reset version check timestamp", async () => {
- await GM.deleteValue("bytm-version-check");
- console.log("Reset version check time.");
- }, "v");
- GM.registerMenuCommand("List active selector listeners", async () => {
- const lines = [] as string[];
- let listenersAmt = 0;
- for(const [obsName, obs] of Object.entries(observers)) {
- const listeners = obs.getAllListeners();
- lines.push(`- "${obsName}" (${listeners.size} listeners):`);
- [...listeners].forEach(([k, v]) => {
- listenersAmt += v.length;
- lines.push(` [${v.length}] ${k}`);
- v.forEach(({ all, continuous }, i) => {
- lines.push(` ${v.length > 1 && i !== v.length - 1 ? "├" : "└"}> ${continuous ? "continuous" : "single-shot"}, ${all ? "select multiple" : "select single"}`);
- });
- });
- }
- console.log(`Showing currently active listeners for ${Object.keys(observers).length} observers with ${listenersAmt} total listeners:\n${lines.join("\n")}`);
- alert("See console.");
- }, "s");
- }
- }
- preInit();
|