-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinput.ts
181 lines (151 loc) · 5.37 KB
/
input.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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>) {
const origListener = typeof args[1] === "function" ? args[1] : args[1].handleEvent;
args[1] = function(...a) {
if(!beforeUnloadEnabled && args[0] === "beforeunload")
return info("Prevented beforeunload event listener from being called");
else
return origListener.apply(this, a);
};
original.apply(this, args);
};
// @ts-ignore
})(window.__proto__.addEventListener);
getFeatures().then(feats => {
if(feats.disableBeforeUnloadPopup)
disableBeforeUnload();
});
}