Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions docs/plugins/concepts/patching.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,21 @@ A function patch an advanced technique for plugins that allow you to modify exis

### Why would I use one?

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.
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.

### How can I patch a function?

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
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:

```js
function yourTarget() {}
```

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.
then you can't really affect it.

```js:line-numbers
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.

```js:line-numbers{13-17}
const someObject = {
yourTarget: function() {
console.log("red");
Expand All @@ -39,24 +41,22 @@ function targetUser() {

targetUser(); // Logs "red"

// highlight-start
function myNewFunction() {
console.log("green");
}

someObject.yourTarget = myNewFunction;
// highlight-end

targetUser(); // Now logs "green"
```

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.
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.

#### BetterDiscord

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.

```js:line-numbers
```js:line-numbers{13}
const someObject = {
yourTarget: function() {
console.log("red");
Expand All @@ -69,14 +69,12 @@ function targetUser() {

targetUser(); // Logs "red"

// highlight-start
BdApi.Patcher.instead("MyPlugin", someObject, "yourTarget", () => console.log("green"));
// highlight-end

targetUser(); // Now logs "green"
```

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.
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.


## Examples
Expand All @@ -101,7 +99,7 @@ const someModule = {
};
```

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 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.

### Before

Expand All @@ -119,14 +117,13 @@ someModule.otherMethod("something");

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?

```js:line-numbers
```js:line-numbers{4-6}
BdApi.Patcher.before("MyPlugin", someModule, "otherMethod", (thisObject, args) => {
const firstArgument = args[0];
// highlight-start

if (firstArgument === "token") {
args[0] = "redacted";
}
// highlight-end
});

someModule.otherMethod("something"); // > My value something
Expand Down Expand Up @@ -155,7 +152,7 @@ someModule.method(1); // > Intercepted other
someModule.method(1); // > undefined
```

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.
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.

### After

Expand All @@ -170,7 +167,7 @@ someModule.method(5); // > 16
someModule.method(); // > 6
```

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:
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:

```js
const myNewNumber = 5 / someModule.method(5);
Expand Down
26 changes: 13 additions & 13 deletions docs/plugins/concepts/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This guide involves [function patching](./patching.md). If you have not read tha

### What does it mean?

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.
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.

### Why would I need it?

Expand All @@ -27,7 +27,7 @@ It's important that you make your changes in an error-safe way whenever possible

:::

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
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

## Walkthrough

Expand All @@ -51,27 +51,27 @@ We want to add a new button here, so let's select it in React DevTools component

![react_parent](./img/react_parent.png)

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.
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.

![react_candidate](./img/react_candidate.png)

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.

![view_source](./img/view_source.png)

And of course also beautify the code with the button a the bottom left. You'll see a render function much like this.
And of course also beautify the code with the button at the bottom left. You'll see a render function much like this.

![react_render](./img/react_render.png)

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.
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.

![react_exports](./img/react_exports.png)

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.
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.

![react_ancestor](./img/react_ancestor.png)

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.
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.

### Getting The Target

Expand Down Expand Up @@ -102,7 +102,7 @@ BdApi.Patcher.after("debug", PrivateChannels, "Z", (_, __, returnValue) => {
});
```

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.
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.

::: details Right-Click
![return_value](./img/return_value.png)
Expand Down Expand Up @@ -139,7 +139,7 @@ This patch should just add a simple button saying `Hello World` to this list of

![our_button](./img/our_button.png)

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.
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.

## Tips & Tricks

Expand All @@ -160,7 +160,7 @@ You can also make use of error boundaries to prevent the error from crashing the

### Multi-Patching

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.
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.

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.

Expand All @@ -185,16 +185,16 @@ Before we move on, notice that we added an `Array.isArray()` check to the filter

Now we can actually rewrite our patch from earlier.

```js
```js{8}
const myFilter = prop => Array.isArray(prop) && prop.some(element => element.key === "friends");
const PrivateChannels = BdApi.Webpack.getByStrings("getPrivateChannelIds", {defaultExport: false});

BdApi.Patcher.after("debug", PrivateChannels, "Z", (_, __, returnValue) => {
const myElement = BdApi.React.createElement("button", null, "Hello World!");
const buttons = BdApi.Utils.findInTree(returnValue, myFilter, {walkable: ["props", "children"]});
// highlight-next-line

buttons?.push(myElement);
});
```

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.
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.
Loading