123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- import { getUnsafeWindow } from "@sv443-network/userutils";
- import { error, getVideoTime, info, log, warn } from "../utils";
- import type { Domain } from "../types";
- import { getFeatures } from "../config";
- //#MARKER arrow key skip
- export function initArrowKeySkip() {
- document.addEventListener("keydown", onKeyDown);
- log("Added key press listener");
- }
- /** Called when the user presses any key, anywhere */
- function onKeyDown(evt: KeyboardEvent) {
- if(["ArrowLeft", "ArrowRight"].includes(evt.code)) {
- // discard the event when a (text) input is currently active, like when editing a playlist
- if(["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement?.tagName ?? "_"))
- return info(`Captured valid key but the current active element is <${document.activeElement!.tagName.toLowerCase()}>, so the keypress is ignored`);
- log(`Captured key '${evt.code}' in proxy listener`);
- // ripped this stuff from the console, most of these are probably unnecessary but this was finnicky af and I am sick and tired of trial and error
- const defaultProps = {
- altKey: false,
- ctrlKey: false,
- metaKey: false,
- shiftKey: false,
- target: document.body,
- currentTarget: document.body,
- originalTarget: document.body,
- explicitOriginalTarget: document.body,
- srcElement: document.body,
- type: "keydown",
- bubbles: true,
- cancelBubble: false,
- cancelable: true,
- isTrusted: true,
- repeat: false,
- // needed because otherwise YTM errors out - see https://github.com/Sv443/BetterYTM/issues/18#show_issue
- view: getUnsafeWindow(),
- };
- let invalidKey = false;
- let keyProps = {};
- switch(evt.code) {
- case "ArrowLeft":
- keyProps = {
- code: "KeyH",
- key: "h",
- keyCode: 72,
- which: 72,
- };
- break;
- case "ArrowRight":
- keyProps = {
- code: "KeyL",
- key: "l",
- keyCode: 76,
- which: 76,
- };
- break;
- default:
- invalidKey = true;
- break;
- }
- if(!invalidKey) {
- const proxyProps = { code: "", ...defaultProps, ...keyProps };
- document.body.dispatchEvent(new KeyboardEvent("keydown", proxyProps));
- log(`Dispatched proxy keydown event: [${evt.code}] -> [${proxyProps.code}]`);
- }
- else
- warn(`Captured key '${evt.code}' has no defined behavior`);
- }
- }
- //#MARKER site switch
- /** switch sites only if current video time is greater than this value */
- const videoTimeThreshold = 3;
- /** Initializes the site switch feature */
- export function initSiteSwitch(domain: Domain) {
- document.addEventListener("keydown", (e) => {
- if(e.key === "F9")
- switchSite(domain === "yt" ? "ytm" : "yt");
- });
- log("Initialized site switch listener");
- }
- /** Switches to the other site (between YT and YTM) */
- async function switchSite(newDomain: Domain) {
- try {
- if(newDomain === "ytm" && !location.href.includes("/watch"))
- return warn("Not on a video page, so the site switch is ignored");
- let subdomain;
- if(newDomain === "ytm")
- subdomain = "music";
- else if(newDomain === "yt")
- subdomain = "www";
- if(!subdomain)
- throw new Error(`Unrecognized domain '${newDomain}'`);
- disableBeforeUnload();
- const { pathname, search, hash } = new URL(location.href);
- const vt = await getVideoTime();
- log(`Found video time of ${vt} seconds`);
- const cleanSearch = search.split("&")
- .filter((param) => !param.match(/^\??t=/))
- .join("&");
- const newSearch = typeof vt === "number" && vt > videoTimeThreshold ?
- cleanSearch.includes("?")
- ? `${cleanSearch.startsWith("?")
- ? cleanSearch
- : "?" + cleanSearch
- }&t=${vt}`
- : `?t=${vt}`
- : cleanSearch;
- const newUrl = `https://${subdomain}.youtube.com${pathname}${newSearch}${hash}`;
- info(`Switching to domain '${newDomain}' at ${newUrl}`);
- location.assign(newUrl);
- }
- catch(err) {
- error("Error while switching site:", err);
- }
- }
- //#MARKER beforeunload popup
- let beforeUnloadEnabled = true;
- /** Disables the popup before leaving the site */
- export function disableBeforeUnload() {
- beforeUnloadEnabled = false;
- info("Disabled popup before leaving the site");
- }
- /** (Re-)enables the popup before leaving the site */
- export function enableBeforeUnload() {
- beforeUnloadEnabled = true;
- info("Enabled popup before leaving the site");
- }
- /**
- * Adds a spy function into `window.__proto__.addEventListener` to selectively discard `beforeunload`
- * event listeners before they can be called by the site.
- */
- export function initBeforeUnloadHook() {
- Error.stackTraceLimit = 1000; // default is 25 on FF so this should hopefully be more than enough
- (function(original: typeof window.addEventListener) {
- // @ts-ignore
- window.__proto__.addEventListener = function(...args: Parameters<typeof window.addEventListener>) {
- if(!beforeUnloadEnabled && args[0] === "beforeunload")
- return info("Prevented beforeunload event listener from being called");
- else
- return original.apply(this, args);
- };
- // @ts-ignore
- })(window.__proto__.addEventListener);
- getFeatures().then(feats => {
- if(feats.disableBeforeUnloadPopup)
- disableBeforeUnload();
- });
- }
|