Last active
February 9, 2023 09:11
-
-
Save tie/24ad82495e9beee587652a864e37872f to your computer and use it in GitHub Desktop.
BetterDiscord plugin that enables auto-correct (text replacements) for message text area
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @name TextReplacement | |
* @author Ivan Trubach | |
* @authorId 783087994419675201 | |
* @description Enable autocorrect for messages, specifically text replacements on macOS. | |
* @version 0.0.4 | |
*/ | |
const memoSymbol = Symbol.for("react.memo") | |
module.exports = class TextReplacementPlugin { | |
// Contains plugin metadata parsed by BetterDiscord from the comment | |
// header. | |
#meta | |
constructor(meta) { | |
this.#meta = meta | |
} | |
// Returns the plugin ID used to identify applied patches. | |
get #pluginID() { | |
return this.#meta.id | |
} | |
// Returns human-readable plugin name for UI alerts on load error. | |
get #pluginName() { | |
return this.#meta.name | |
} | |
start() { | |
const component = findChannelTextArea() | |
const patch = BdApi.Patcher.after( | |
this.#pluginID, | |
component, "render", | |
enableAutocorrect, | |
) | |
if (patch === null) { | |
BdApi.UI.alert( | |
this.#pluginName, | |
// TODO: localize message | |
"Failed to find message text area implementation for patching.", | |
) | |
} | |
} | |
stop() { | |
BdApi.Patcher.unpatchAll(this.#pluginID) | |
} | |
} | |
function enableAutocorrect(that, args, ret) { | |
const inner = findChildDivInReactTree( | |
findChildDivInReactTree( | |
findChildDivInReactTree(ret), | |
), | |
) | |
const focusRing = findInReactTree(inner, (n) => { | |
return n?.ringTarget !== undefined | |
}) | |
// Yikes, that’s a dirty workaround but I didn’t find a better solution… | |
// Still, this makes Discord typing experience much better, so who cares. | |
const target = focusRing?.ringTarget?.current | |
const editor = target?.querySelector?.('div[role="textbox"]') | |
editor?.setAttribute?.("autocorrect", true) | |
} | |
// Traverses props and children for the given React tree. Returns the first node | |
// matching filter. | |
function findInReactTree(root, filter) { | |
return BdApi.Utils.findInTree(root, filter, { | |
walkable: ["props", "children"], | |
}) | |
} | |
// Returns the first div element from the given React node’s children. | |
function findChildDivInReactTree(root) { | |
return findInReactTree(root?.props?.children, (n) => { | |
return n?.type == "div" | |
}) | |
} | |
// Returns the ChannelTextArea component implementation for patching. | |
function findChannelTextArea() { | |
const matches = BdApi.Webpack.getModule(filterChannelTextArea, { | |
searchExports: true, | |
first: false, | |
}) | |
if (matches?.length != 1) { | |
return undefined | |
} | |
const [module] = matches | |
const { type: component } = module | |
return component | |
} | |
// Filters ChannelTextArea component module. The component is a function wrapped | |
// in React.memo. | |
function filterChannelTextArea(exports) { | |
if (exports?.$$typeof != memoSymbol) { | |
return false | |
} | |
const source = exports?.type?.render?.toString?.() | |
return source?.includes("channelTextArea") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment