Skip to content

Instantly share code, notes, and snippets.

@tie
Last active February 9, 2023 09:11
Show Gist options
  • Save tie/24ad82495e9beee587652a864e37872f to your computer and use it in GitHub Desktop.
Save tie/24ad82495e9beee587652a864e37872f to your computer and use it in GitHub Desktop.
BetterDiscord plugin that enables auto-correct (text replacements) for message text area
/**
* @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