-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDiscordSilentTyping.user.js
180 lines (163 loc) · 11.7 KB
/
DiscordSilentTyping.user.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
// ==UserScript==
// @name DiscordSilentTyping
// @namespace https://files.noodlebox.moe/
// @downloadURL https://files.noodlebox.moe/userscripts/DiscordSilentTyping.user.js
// @version 2.1.3
// @description Don't send typing notifications
// @author noodlebox
// @match *://*.discordapp.com/channels/*
// @match *://*.discordapp.com/invite/*
// @match *://*.discordapp.com/login
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
"use strict";
let cancel = null;
const start = function () {
const module = WebpackModules.findByUniqueProperties(["startTyping"]);
if (!module) {
console.error("SilentTyping:", "unable to monkey patch sendTyping method");
return;
}
cancel = monkeyPatch(module, "startTyping", {instead: ()=>{}});
};
const stop = function () {
if (cancel) {
cancel();
cancel = null;
}
};
/**
* Function with no arguments and no return value that may be called to reverse changes that is done by {@link monkeyPatch} method, restoring (unpatching) original method.
* @callback cancelPatch
*/
/**
* This is a shortcut for calling original method using this and arguments from data object. This is a function without input arguments. This function is defined as `() => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)`
* @callback originalMethodCall
* @return {*} The same value, which is returned from original method, also this value would be written into `data.returnValue`
*/
/**
* A callback that modifies method logic. Callback is called on each call of original method and have all data about original call. Any of the data can be modified if you need, but do it wisely.
* @callback doPatchCallback
* @param {PatchData} data Data object with all information about current that you may need in your patching callback.callback.
* @return {*} Makes sense only when used as `instead` parameter in {@link monkeyPatch}. If returned something other then undefined - it replaces value in `returnValue` param. If used as `before` or `after` parameters - return value if ignored.
*/
/**
* This is function for monkey-patching any object method. Can make patch before, after or instead of target method.
* Be careful when monkey-patching. Think not only about original functionality of target method and you changes, but also about develovers of other plugins, who may also patch this method before or after you. Try to change target method behaviour little as you can, and try to never change method signatures.
* By default this function makes log messages about each patching and unpatching, so you and other developers can see what methods a patched. This messages may be suppressed.
* Display name of patched method is changed, so you can see if function is patched and how many times while debuging or in the stack trace. Also patched function have property `__monkeyPatched` is set to true, in case you want to check something programmatically.
*
* @author samogot
* @param {object} what Object to be patched. You can can also pass class prototypes to patch all class instances. If you are patching prototype of react component you may also need {@link Renderer.rebindMethods}.
* @param {string} methodName The name of the target message to be patched.
* @param {object} options Options object. You should provide at least one of `before`, `after` or `instead` parameters. Other parameters are optional.
* @param {doPatchCallback} options.before Callback that will be called before original target method call. You can modify arguments here, so it will be passed to original method. Can be combined with `after`.
* @param {doPatchCallback} options.after Callback that will be called after original target method call. You can modify return value here, so it will be passed to external code which calls target method. Can be combined with `before`.
* @param {doPatchCallback} options.instead Callback that will be called instead of original target method call. You can get access to original method using `originalMethod` parameter if you want to call it, but you do not have to. Can't be combined with `before` and `after`.
* @param {boolean} [options.once=false] Set to true if you want automatically unpatch method after first call.
* @param {boolean} [options.silent=false] Set to true if you want to suppress log messages about patching and unpatching. Useful to avoid clogging the console in case of frequent conditional patching/unpatching, for example from another monkeyPatch callback.
* @param {boolean} [options.displayName] You can provide meaningful name of class/object provided in `what` param for logging purposes. By default there will be a try to determine name automatically.
* @return {cancelPatch} 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.
*/
const monkeyPatch = window.DiscordInternals && window.DiscordInternals.monkeyPatch || ((what, methodName, options) => {
const {before, after, instead, once = false, silent = false} = options;
const displayName = options.displayName || what.displayName || what.name || what.constructor.displayName || what.constructor.name;
if (!silent) console.log('patch', methodName, 'of', displayName);
const origMethod = what[methodName];
const cancel = () => {
if (!silent) console.log('unpatch', methodName, 'of', displayName);
what[methodName] = origMethod;
};
what[methodName] = function() {
/**
* @interface
* @name PatchData
* @property {object} thisObject Original `this` value in current call of patched method.
* @property {Arguments} methodArguments Original `arguments` object in current call of patched method. Please, never change function signatures, as it may cause a lot of problems in future.
* @property {cancelPatch} cancelPatch Function with no arguments and no return value that may be called to reverse patching of current method. Calling this function prevents running of this callback on further original method calls.
* @property {function} originalMethod Reference to the original method that is patched. You can use in if you need some special usage. You should explicitly provide this value and method arguments when you call this function.
* @property {originalMethodCall} callOriginalMethod This is a shortcut for calling original method using this and arguments from data object.
* @property {*} returnValue This is a value returned from original function call. This property is avilable only in `after` callback, or in `instead` callback after calling `callOriginalMethod` function
*/
const data = {
thisObject: this,
methodArguments: arguments,
cancelPatch: cancel,
originalMethod: origMethod,
callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)
};
if (instead) {
const tempRet = instead(data);
if (tempRet !== undefined)
data.returnValue = tempRet;
}
else {
if (before) before(data);
data.callOriginalMethod();
if (after) after(data);
}
if (once) cancel();
return data.returnValue;
};
what[methodName].__monkeyPatched = true;
what[methodName].displayName = 'patched ' + (what[methodName].displayName || methodName);
return cancel;
});
/**
* @author samogot
*/
const WebpackModules = window.DiscordInternals && window.DiscordInternals.WebpackModules || (() => {
const req = typeof(webpackJsonp) == "function" ? webpackJsonp([], {
'__extra_id__': (module, exports, req) => exports.default = req
}, ['__extra_id__']).default : webpackJsonp.push([[], {
'__extra_id__': (module, exports, req) => module.exports = req
}, [['__extra_id__']]]);
delete req.m['__extra_id__'];
delete req.c['__extra_id__'];
/**
* Predicate for searching module
* @callback modulePredicate
* @param {*} module Module to test
* @return {boolean} Thue if it is module that you need
*/
/**
* Look through all modules of internal Discord's Webpack and return first one that match filter predicate.
* At first this function will look thruogh alreary loaded modules cache. If no one of loaded modules is matched - then this function tries to load all modules and match for them. Loading any module may have unexpected side effects, like changing current locale of moment.js, so in that case there will be a warning the console. If no module matches - function will return null. You sould always take such predicate to match something, gut your code should be ready to recieve null in case if Discord update something in codebase.
* If module is ES6 module and hafe default property - only default would be considered, otherwise - full module object.
* @param {modulePredicate} filter Predicate to match module
* @return {*} First module that matched by filter or null if none is matched.
*/
const find = (filter) => {
for (let i in req.c) {
if (req.c.hasOwnProperty(i)) {
let m = req.c[i].exports;
if (m && m.__esModule && m.default)
m = m.default;
if (m && filter(m))
return m;
}
}
console.warn('Cannot find loaded module in cache.');
return null;
};
/**
* Look through all modules of internal Discord's Webpack and return first object that has all of following properties. You should be ready that in any moment, after Discord update, this function may start returning null (if no such object exists any more) or even some different object with the same properties. So you should provide all property names that you use, and often even some extra properties to make sure you'll get exactly what you want.
* @see Read {@link find} documentation for more details how search works
* @param {string[]} propNames Array of property names to look for
* @return {object} First module that matched by propNames or null if none is matched.
*/
const findByUniqueProperties = (propNames) => find(module => propNames.every(prop => module[prop] !== undefined));
/**
* Look through all modules of internal Discord's Webpack and return first object that has displayName property with following value. This is useful for searching React components by name. Take into account that not all components are exported as modules. Also there might be several components with same names
* @see Use {@link ReactComponents} as another way to get react components
* @see Read {@link find} documentation for more details how search works
* @param {string} displayName Display name property value to look for
* @return {object} First module that matched by displayName or null if none is matched.
*/
const findByDisplayName = (displayName) => find(module => module.displayName === displayName);
return {find, findByUniqueProperties, findByDisplayName};
})();
window.setTimeout(start, 5000);
})();