forked from Vendicated/Vencord
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathstuffFromBD.js
363 lines (330 loc) · 18.1 KB
/
stuffFromBD.js
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/* eslint-disable simple-header/header */
/* eslint-disable eqeqeq */
/* eslint-disable no-prototype-builtins */
/* globals BdApi window document Vencord */
/* eslint no-undef:error */
/**
* This file contains code taken from https://github.com/BetterDiscord/BetterDiscord
* @see https://github.com/BetterDiscord/BetterDiscord/blob/main/LICENSE
*/
!function () { };
import { addLogger } from "./utils";
const Logger = addLogger();
/**
* @summary Code taken from BetterDiscord
* @description Changes:
*
* Formatting changed
*/
function monkeyPatch(what, methodName, options) {
const { before, after, instead, once = false, callerId = "BdApi" } = options;
const patchType = before ? "before" : after ? "after" : instead ? "instead" : "";
if (!patchType) return Logger.err("BdApi", "Must provide one of: after, before, instead");
const originalMethod = what[methodName];
const data = {
originalMethod: originalMethod,
callOriginalMethod: () => data.originalMethod.apply(data.thisObject, data.methodArguments)
};
data.cancelPatch = Patcher[patchType](callerId, what, methodName, (thisObject, args, returnValue) => {
data.thisObject = thisObject;
data.methodArguments = args;
data.returnValue = returnValue;
try {
const patchReturn = Reflect.apply(options[patchType], null, [data]);
if (once) data.cancelPatch();
return patchReturn;
}
catch (err) {
Logger.stacktrace(`${callerId}:monkeyPatch`, `Error in the ${patchType} of ${methodName}`, err);
}
});
return data.cancelPatch;
}
/**
* @summary Code taken from BetterDiscord
* @description Changes:
*
* Occurrences of "DiscordModules" replaced with "this.DiscordModules"
*
* Occurrences of "WebpackModules" replaced with "BdApi.Webpack"
*
* Added static method "setup"
*
* Formatting changed
*/
class Patcher {
static setup(DiscordModules) {
this.DiscordModules = DiscordModules;
}
static get patches() { return this._patches || (this._patches = []); }
/**
* Returns all the patches done by a specific caller
* @param {string} name - Name of the patch caller
* @method
*/
static getPatchesByCaller(name) {
if (!name) return [];
const patches = [];
for (const patch of this.patches) {
for (const childPatch of patch.children) {
if (childPatch.caller === name) patches.push(childPatch);
}
}
return patches;
}
/**
* Unpatches all patches passed, or when a string is passed unpatches all
* patches done by that specific caller.
* @param {Array|string} patches - Either an array of patches to unpatch or a caller name
*/
static unpatchAll(patches) {
if (typeof patches === "string") patches = this.getPatchesByCaller(patches);
for (const patch of patches) {
patch.unpatch();
}
}
static resolveModule(module) {
if (!module || typeof (module) === "function" || (typeof (module) === "object" && !Array.isArray(module))) return module;
if (typeof module === "string") return this.DiscordModules[module];
if (Array.isArray(module)) return BdApi.Webpack.findByUniqueProperties(module);
return null;
}
static makeOverride(patch) {
return function () {
let returnValue;
if (!patch.children || !patch.children.length) return patch.originalFunction.apply(this, arguments);
for (const superPatch of patch.children.filter(c => c.type === "before")) {
try {
superPatch.callback(this, arguments);
}
catch (err) {
Logger.err("Patcher", `Could not fire before callback of ${patch.functionName} for ${superPatch.caller}`, err);
}
}
const insteads = patch.children.filter(c => c.type === "instead");
if (!insteads.length) { returnValue = patch.originalFunction.apply(this, arguments); }
else {
for (const insteadPatch of insteads) {
try {
const tempReturn = insteadPatch.callback(this, arguments, patch.originalFunction.bind(this));
if (typeof (tempReturn) !== "undefined") returnValue = tempReturn;
}
catch (err) {
Logger.err("Patcher", `Could not fire instead callback of ${patch.functionName} for ${insteadPatch.caller}`, err);
}
}
}
for (const slavePatch of patch.children.filter(c => c.type === "after")) {
try {
const tempReturn = slavePatch.callback(this, arguments, returnValue);
if (typeof (tempReturn) !== "undefined") returnValue = tempReturn;
}
catch (err) {
Logger.err("Patcher", `Could not fire after callback of ${patch.functionName} for ${slavePatch.caller}`, err);
}
}
return returnValue;
};
}
static rePatch(patch) {
patch.proxyFunction = patch.module[patch.functionName] = this.makeOverride(patch);
}
static makePatch(module, functionName, name) {
const patch = {
name,
module,
functionName,
originalFunction: module[functionName],
proxyFunction: null,
revert: () => { // Calling revert will destroy any patches added to the same module after this
patch.module[patch.functionName] = patch.originalFunction;
patch.proxyFunction = null;
patch.children = [];
},
counter: 0,
children: []
};
patch.proxyFunction = module[functionName] = this.makeOverride(patch);
Object.assign(module[functionName], patch.originalFunction);
module[functionName].__originalFunction = patch.originalFunction;
module[functionName].toString = () => patch.originalFunction.toString();
this.patches.push(patch);
return patch;
}
/**
* Function with no arguments and no return value that may be called to revert changes made by {@link module:Patcher}, restoring (unpatching) original method.
* @callback module:Patcher~unpatch
*/
/**
* A callback that modifies method logic. This callback is called on each call of the original method and is provided all data about original call. Any of the data can be modified if necessary, but do so wisely.
*
* The third argument for the callback will be `undefined` for `before` patches. `originalFunction` for `instead` patches and `returnValue` for `after` patches.
*
* @callback module:Patcher~patchCallback
* @param {object} thisObject - `this` in the context of the original function.
* @param {arguments} args - The original arguments of the original function.
* @param {(function|*)} extraValue - For `instead` patches, this is the original function from the module. For `after` patches, this is the return value of the function.
* @return {*} Makes sense only when using an `instead` or `after` patch. If something other than `undefined` is returned, the returned value replaces the value of `returnValue`. If used for `before` the return value is ignored.
*/
/**
* This method patches onto another function, allowing your code to run beforehand.
* Using this, you are also able to modify the incoming arguments before the original method is run.
*
* @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care.
* @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype.
* @param {string} functionName - Name of the method to be patched
* @param {module:Patcher~patchCallback} callback - Function to run before the original method
* @param {object} options - Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for class/object provided in `what` param for logging purposes. By default, this function will try to determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function doesnt exist. (Adds noop function in place).
* @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped.
*/
static before(caller, moduleToPatch, functionName, callback, options = {}) { return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: "before" })); }
/**
* This method patches onto another function, allowing your code to run after.
* Using this, you are also able to modify the return value, using the return of your code instead.
*
* @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care.
* @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype.
* @param {string} functionName - Name of the method to be patched
* @param {module:Patcher~patchCallback} callback - Function to run instead of the original method
* @param {object} options - Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for class/object provided in `what` param for logging purposes. By default, this function will try to determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function doesnt exist. (Adds noop function in place).
* @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped.
*/
static after(caller, moduleToPatch, functionName, callback, options = {}) { return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: "after" })); }
/**
* This method patches onto another function, allowing your code to run instead.
* Using this, you are also able to modify the return value, using the return of your code instead.
*
* @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care.
* @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype.
* @param {string} functionName - Name of the method to be patched
* @param {module:Patcher~patchCallback} callback - Function to run after the original method
* @param {object} options - Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for class/object provided in `what` param for logging purposes. By default, this function will try to determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function doesnt exist. (Adds noop function in place).
* @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped.
*/
static instead(caller, moduleToPatch, functionName, callback, options = {}) { return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: "instead" })); }
/**
* This method patches onto another function, allowing your code to run before, instead or after the original function.
* Using this you are able to modify the incoming arguments before the original function is run as well as the return
* value before the original function actually returns.
*
* @param {string} caller - Name of the caller of the patch function. Using this you can undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""` if you don't care.
* @param {object} moduleToPatch - Object with the function to be patched. Can also patch an object's prototype.
* @param {string} functionName - Name of the method to be patched
* @param {module:Patcher~patchCallback} callback - Function to run after the original method
* @param {object} options - Object used to pass additional options.
* @param {string} [options.type=after] - Determines whether to run the function `before`, `instead`, or `after` the original.
* @param {string} [options.displayName] You can provide meaningful name for class/object provided in `what` param for logging purposes. By default, this function will try to determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function doesnt exist. (Adds noop function in place).
* @return {module:Patcher~unpatch} Function with no arguments and no return value that should be called to cancel (unpatch) this patch. You should save and run it when your plugin is stopped.
*/
static pushChildPatch(caller, moduleToPatch, functionName, callback, options = {}) {
const { type = "after", forcePatch = true } = options;
const module = this.resolveModule(moduleToPatch);
if (!module) return null;
if (!module[functionName] && forcePatch) module[functionName] = function () { };
if (!(module[functionName] instanceof Function)) return null;
if (typeof moduleToPatch === "string") options.displayName = moduleToPatch;
const displayName = options.displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
const patchId = `${displayName}.${functionName}`;
const patch = this.patches.find(p => p.module == module && p.functionName == functionName) || this.makePatch(module, functionName, patchId);
if (!patch.proxyFunction) this.rePatch(patch);
const child = {
caller,
type,
id: patch.counter,
callback,
unpatch: () => {
patch.children.splice(patch.children.findIndex(cpatch => cpatch.id === child.id && cpatch.type === type), 1);
if (patch.children.length <= 0) {
const patchNum = this.patches.findIndex(p => p.module == module && p.functionName == functionName);
if (patchNum < 0) return;
this.patches[patchNum].revert();
this.patches.splice(patchNum, 1);
}
}
};
patch.children.push(child);
patch.counter++;
return child.unpatch;
}
}
/**
* @summary Code taken from BetterDiscord
* @description Changes:
*
* None
*/
const hasThrown = new WeakSet();
/**
* @summary Code taken from BetterDiscord
* @description Changes:
*
* Formatting changed
*/
const wrapFilter = filter => (exports, module, moduleId) => {
try {
if (exports?.default?.remove && exports?.default?.set && exports?.default?.clear && exports?.default?.get && !exports?.default?.sort) return false;
if (exports.remove && exports.set && exports.clear && exports.get && !exports.sort) return false;
if (exports?.default?.getToken || exports?.default?.getEmail || exports?.default?.showToken) return false;
if (exports.getToken || exports.getEmail || exports.showToken) return false;
return filter(exports, module, moduleId);
}
catch (err) {
if (!hasThrown.has(filter)) Logger.warn("WebpackModules~getModule", "Module filter threw an exception.", filter, err);
hasThrown.add(filter);
return false;
}
};
/**
* @summary Code taken from BetterDiscord
* @description Changes:
*
* Occurrences of "this.getAllModules()" replaced with "Vencord.Webpack.cache"
*
* Formatting changed
*/
function getModule(filter, options = {}) {
const { first = true, defaultExport = true, searchExports = false } = options;
const wrappedFilter = wrapFilter(filter);
const modules = Vencord.Webpack.cache;
const rm = [];
const indices = Object.keys(modules);
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
if (!modules.hasOwnProperty(index)) continue;
let module = null;
try { module = modules[index]; } catch { continue; }
const { exports } = module;
if (!exports || exports === window || exports === document.documentElement || exports[Symbol.toStringTag] === "DOMTokenList") continue;
if (typeof (exports) === "object" && searchExports && !exports.TypedArray) {
for (const key in exports) {
let foundModule = null;
let wrappedExport = null;
try { wrappedExport = exports[key]; } catch { continue; }
if (!wrappedExport) continue;
if (wrappedFilter(wrappedExport, module, index)) foundModule = wrappedExport;
if (!foundModule) continue;
if (first) return foundModule;
rm.push(foundModule);
}
}
else {
let foundModule = null;
if (exports.Z && wrappedFilter(exports.Z, module, index)) foundModule = defaultExport ? exports.Z : exports;
if (exports.ZP && wrappedFilter(exports.ZP, module, index)) foundModule = defaultExport ? exports.ZP : exports;
if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports;
if (wrappedFilter(exports, module, index)) foundModule = exports;
if (!foundModule) continue;
if (first) return foundModule;
rm.push(foundModule);
}
}
return first || rm.length == 0 ? undefined : rm;
}
export { getModule, monkeyPatch, Patcher };