You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/plugins/concepts/patching.md
+14-17Lines changed: 14 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,19 +14,21 @@ A function patch an advanced technique for plugins that allow you to modify exis
14
14
15
15
### Why would I use one?
16
16
17
-
It's a great way to modify or extend Discord's functionality with your own while keeping integration mostly seemless. It can also act as a way to modify the way Discord works currently. Take the plugin [HideDisabledEmojis](https://betterdiscord.app/plugin/HideDisabledEmojis) for example, it uses function patching to modify the way Discord's internal functions work to stop trying to render emojis the user cannot use. Your possibilities for the plugins you can make increase exponentially, and the quality usually ends up being higher due to the tight integration with Discord.
17
+
It's a great way to modify or extend Discord's functionality with your own while keeping integration mostly seamless. It can also act as a way to modify the way Discord works currently. Take the plugin [HideDisabledEmojis](https://betterdiscord.app/plugin/HideDisabledEmojis) for example, it uses function patching to modify the way Discord's internal functions work to stop trying to render emojis the user cannot use. Your possibilities for the plugins you can make increase exponentially, and the quality usually ends up being higher due to the tight integration with Discord.
18
18
19
19
### How can I patch a function?
20
20
21
-
Unfortunately, you can't patch a function *directly*, you have to modify the *reference* to the function that other code uses. That means if your target function is just a locally or globally available function like this
21
+
Unfortunately, you can't patch a function *directly*, you have to modify the *reference* to the function that other code uses. That means if your target function is just a locally or globally available function like this:
22
22
23
23
```js
24
24
functionyourTarget() {}
25
25
```
26
26
27
-
then you can't really affect it. However, if your target is part of an object in some way, like being contained in an imported module, you can overwrite that reference with your own function causing everyone to call your function instead.
27
+
then you can't really affect it.
28
28
29
-
```js:line-numbers
29
+
However, if your target is part of an object in some way, like being contained in an imported module, you can overwrite that reference with your own function causing everyone to call your function instead.
30
+
31
+
```js:line-numbers{13-17}
30
32
const someObject = {
31
33
yourTarget: function() {
32
34
console.log("red");
@@ -39,24 +41,22 @@ function targetUser() {
39
41
40
42
targetUser(); // Logs "red"
41
43
42
-
// highlight-start
43
44
function myNewFunction() {
44
45
console.log("green");
45
46
}
46
47
47
48
someObject.yourTarget = myNewFunction;
48
-
// highlight-end
49
49
50
50
targetUser(); // Now logs "green"
51
51
```
52
52
53
-
If you take a look at the highlighted section, we are creating a new function `myNewFunction` that logs `green` and assigning it to `someObject.yourTarget` effectively overwriting the target function. That means when `targetUser` is called again, your function gets run successfully because it references the `someObject` object. This here is known as an `instead` patch because it completely replaces the target. All patches start this way but can expanded to become a `before` or `after` patch by storing a reference and calling the original function. This also opens the door to subpatches and multiple users, but that can get complicated very fast.
53
+
If you take a look at the highlighted section, we are creating a new function `myNewFunction` that logs `green` and assigning it to `someObject.yourTarget` effectively overwriting the target function. That means when `targetUser` is called again, your function gets run successfully because it references the `someObject` object. This here is known as an `instead` patch because it completely replaces the target. All patches start this way but can be expanded to become a `before` or `after` patch by storing a reference and calling the original function. This also opens the door to subpatches and multiple users, but that can get complicated very fast.
54
54
55
55
#### BetterDiscord
56
56
57
57
Luckily, BetterDiscord already has a system in place to manage multiple patches per function and allows you to target different patch types. This means if you want to do a `before` or `after` patch, you no longer have to manually replace the function and retain references and call the original. All of this is done for you with `BdApi.Patcher`. Let's take a look at how our example above could be done with this module.
This code has the same effect as before, causing `targetUser` to instead log `green`. But lets take a closer look at the highlighted line. We have a call to `BdApi.Patcher.instead` which indicates we want to create an `instead` patch. We pass it `"MyPlugin"` which is an identifier used later to help removed all your patches with `BdApi.Patcher.unpatchAll`. Then we give it the target object `someObject` and the key of our target inside that object `yourTarget` and our new function to override the original. BetterDiscord takes care of the rest and even allows other plugins to patch on top of yours.
77
+
This code has the same effect as before, causing `targetUser` to instead log `green`. But let's take a closer look at the highlighted line. We have a call to `BdApi.Patcher.instead` which indicates we want to create an `instead` patch. We pass it `"MyPlugin"` which is an identifier used later to help removed all your patches with `BdApi.Patcher.unpatchAll`. Then we give it the target object `someObject` and the key of our target inside that object `yourTarget` and our new function to override the original. BetterDiscord takes care of the rest and even allows other plugins to patch on top of yours.
80
78
81
79
82
80
## Examples
@@ -101,7 +99,7 @@ const someModule = {
101
99
};
102
100
```
103
101
104
-
In this setup, `someGlobal` is a function that cannot be patched because there is no reference to replace. However `someModule.method` and `someModule.otherMethod` can both be patched.
102
+
In this setup, `someGlobal` is a function that cannot be patched because there is no reference to replace. However,`someModule.method` and `someModule.otherMethod` can both be patched.
In this example we didn't modify the arguments, we just wanted to log them out to see what kind of values we might get. This is a good technique to help modify arguments selectively. Suppose we don't mind that `something` is logged, but we don't like when `token` is logged. How might that look?
someModule.otherMethod("something"); // > My value something
@@ -155,7 +152,7 @@ someModule.method(1); // > Intercepted other
155
152
someModule.method(1); // > undefined
156
153
```
157
154
158
-
Take alook at the function we define in the `instead` patch. We have a new parameter `originalFunction` that BetterDiscord gives us to use as we see fit. In this example we use it for a specific value. If the value is `5` we let the original function run and return without modification. If the value is `1` we pass it to an external function and let that handle the arguments and the return. Otherwise, the function has no return value at all. This is a huge change to the function. It used to always return a value and now it only returns values for two cases. This is a good demonstration how much power function patching can have.
155
+
Take a look at the function we define in the `instead` patch. We have a new parameter `originalFunction` that BetterDiscord gives us to use as we see fit. In this example we use it for a specific value. If the value is `5` we let the original function run and return without modification. If the value is `1` we pass it to an external function and let that handle the arguments and the return. Otherwise, the function has no return value at all. This is a huge change to the function. It used to always return a value, and now it only returns values for two cases. This is a good demonstration how much power function patching can have.
159
156
160
157
### After
161
158
@@ -170,7 +167,7 @@ someModule.method(5); // > 16
170
167
someModule.method(); // > 6
171
168
```
172
169
173
-
You'll notice that `originalFunction` from before has turned into `returnValue`. Here we simply multiply that by `2` every time and return the value to the caller. So that means for any number we pass, the original function applies and returns, then our patch picks up that value and multiplies by `2`, then the function caller finally gets their value. The BetterDiscord `Patcher` will use whatever `return` value you use. However if you *don't* return anything, then the original return value is used. This can have profound effects. Consider this case below:
170
+
You'll notice that `originalFunction` from before has turned into `returnValue`. Here we simply multiply that by `2` every time and return the value to the caller. So that means for any number we pass, the original function applies and returns, then our patch picks up that value and multiplies by `2`, then the function caller finally gets their value. The BetterDiscord `Patcher` will use whatever `return` value you use. However, if you *don't* return anything, then the original return value is used. This can have profound effects. Consider this case below:
Copy file name to clipboardExpand all lines: docs/plugins/concepts/react.md
+13-13Lines changed: 13 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@ This guide involves [function patching](./patching.md). If you have not read tha
11
11
12
12
### What does it mean?
13
13
14
-
When we say React Injection, we're referring to adding/removing/alterting components in the React render tree used by Discord. In the [React](../tutorials/react.md) section of the guide, we went over rendering our own components using `ReactDOM` which created our own React trees rendering outside of Discord's tree. With injection we can either be part of Discord's tree with our own elements, or we can modify Discord's tree before a render finishes.
14
+
When we say React Injection, we're referring to adding/removing/altering components in the React render tree used by Discord. In the [React](../tutorials/react.md) section of the guide, we went over rendering our own components using `ReactDOM` which created our own React trees rendering outside of Discord's tree. With injection, we can either be part of Discord's tree with our own elements, or we can modify Discord's tree before a render finishes.
15
15
16
16
### Why would I need it?
17
17
@@ -27,7 +27,7 @@ It's important that you make your changes in an error-safe way whenever possible
27
27
28
28
:::
29
29
30
-
Well if you've got a hang of function patching, then you're already halfway there. You'll need to find your React component in an exposed module and override the render function with an `after` patch. From there you'll have to walk the rendered react nodes to find where you want to make your changes. There are traversal utilities in `BdApi` that can help with this, you'll see more about those in the walkthrough. Then you'll have to make your changes
30
+
Well if you've got a hang of function patching, then you're already halfway there. You'll need to find your React component in an exposed module and override the render function with an `after` patch. From there you'll have to walk the rendered React nodes to find where you want to make your changes. There are traversal utilities in `BdApi` that can help with this, you'll see more about those in the walkthrough. Then you'll have to make your changes
31
31
32
32
## Walkthrough
33
33
@@ -51,27 +51,27 @@ We want to add a new button here, so let's select it in React DevTools component
51
51
52
52

53
53
54
-
But take a look at the `props` on the righthand side. This seems to be just a simple container that is reusable and not specific to this component. It's not a good target for patching because it would have effects elsewhere as well. The first one that looks like it has potential is shown below.
54
+
But take a look at the `props` on the right-hand side. This seems to be just a simple container that is reusable and not specific to this component. It's not a good target for patching because it would have effects elsewhere as well. The first one that looks like it has potential is shown below.
55
55
56
56

57
57
58
58
Let's take a look at this component and see if it's exported like we did in the previous chapter. To start, click to view the source of the component.
59
59
60
60

61
61
62
-
And of course also beautify the code with the button a the bottom left. You'll see a render function much like this.
62
+
And of course also beautify the code with the button at the bottom left. You'll see a render function much like this.
63
63
64
64

65
65
66
-
As we did in the last chapter, let's scroll up and check for this `i` to be exported. As we scroll up it appears that `i` is wrapped inside of this module and when we get to the top we can see only an object called `z` is exported.
66
+
As we did in the last chapter, let's scroll up and check for this `i` to be exported. As we scroll up it appears that `i` is wrapped inside this module and when we get to the top we can see only an object called `z` is exported.
67
67
68
68

69
69
70
-
Scroll back and you can find this `z` that uses `i` internally and does not expose it in any other way. Let's go back to the Components panel and keep going up this subtree until we find another candidate. We find one at the top of our subtree.
70
+
Scroll back, and you can find this `z` that uses `i` internally and does not expose it in any other way. Let's go back to the Components panel and keep going up this subtree until we find another candidate. We find one at the top of our subtree.
71
71
72
72

73
73
74
-
Let's take a look at the source once more. The code looks oddly familiar and it's already formatted. It's actually the same module we were looking at before! Except this time we are using the `z` component, so since we know this one is exported, we have found our target.
74
+
Let's take a look at the source once more. The code looks oddly familiar, and it's already formatted. It's actually the same module we were looking at before! Except this time we are using the `z` component, so since we know this one is exported, we have found our target.
With this simple patch, we will log out the return value on ever render call but let the original return value still work. With that in place, try switching to a guild and then back to your DM list. You should see a new log in your console.
105
+
With this simple patch, we will log out the return value on every render call but let the original return value still work. With that in place, try switching to a guild and then back to your DM list. You should see a new log in your console.
106
106
107
107
::: details Right-Click
108
108

@@ -139,7 +139,7 @@ This patch should just add a simple button saying `Hello World` to this list of
139
139
140
140

141
141
142
-
And there we have it! A react button of our own creation rendered inside of Discord's React tree inside of Discord's UI. There are more complicated situations, but this should be a good jump start to help you get on your way. If you're interested in more, there's some additional information below.
142
+
And there we have it! A React button of our own creation rendered inside of Discord's React tree inside of Discord's UI. There are more complicated situations, but this should be a good jump start to help you get on your way. If you're interested in more, there's some additional information below.
143
143
144
144
## Tips & Tricks
145
145
@@ -160,7 +160,7 @@ You can also make use of error boundaries to prevent the error from crashing the
160
160
161
161
### Multi-Patching
162
162
163
-
One thing to keep in mind when making your patches is that you may not be the only plugin attempting to patch a certain component. There are a couple quick steps to massively improve your compatibility with one another.
163
+
One thing to keep in mind when making your patches is that you may not be the only plugin attempting to patch a certain component. There are a couple of quick steps to massively improve your compatibility with one another.
164
164
165
165
Let's say we want to add a child component where one doesn't exist. Simple as setting the `children` property to your component right? Well that works great but what about if another plugin wanted to do the same? It would have been better if you started with an array `[]` so future patches can just add to the array.
166
166
@@ -185,16 +185,16 @@ Before we move on, notice that we added an `Array.isArray()` check to the filter
185
185
186
186
Now we can actually rewrite our patch from earlier.
It's an easy change but it makes the code so much more robust. And take a look at the highlighted line. We're making use of the optional chaining operator `?.` which will protect us in cases where `findInTree` is unable to find our target due to Discord changes. Now you can take this technique and make even the most complex patches much more resilient to updates.
200
+
It's an easy change, but it makes the code so much more robust. And take a look at the highlighted line. We're making use of the optional chaining operator `?.` which will protect us in cases where `findInTree` is unable to find our target due to Discord changes. Now you can take this technique and make even the most complex patches much more resilient to updates.
0 commit comments