From cb00a5b2c0faa2ed40b3db6fdc98199ec9da2798 Mon Sep 17 00:00:00 2001 From: Ecorous Date: Wed, 24 Jun 2026 05:11:47 +0100 Subject: [PATCH] Add SedEnhanced plugin (#1188) * add SedEnchanced * [SedEnhanced] add README.md * [SedEnhanced] clarify in readme * [SedEnhanced] fix typo in readme * [SedEnhanced] make `sedRegex` const * [SedEnhanced] Apply strict checking suggestion Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * [SedEnhanced] remove `export` from `currentReply` Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * [SedEnhanced] fix dodgy patch regex * [SedEnhanced] improve patch regex further * [SedEnhanced] add license header * [SedEnhancer] add reasonable limits to patch `match` * [SedEnhancer] improve reply logic * [SedEnhanced] tighten patch further * [SedEnhanced] use \i for identifier * [SedEnhanced] add explanation for large pattern * [SedEnhanced] fix typescript errors * [SedEnhanced] fix remaining issues (hopefully) * remove stuff we dont care about because I hate myself * Update index.ts * Update index.ts * revert `[...flags]` back to `flags` --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/sedEnhanced/index.ts | 75 ++++++++++++++++++++++++ src/utils/constants.ts | 12 ++++ 2 files changed, 87 insertions(+) create mode 100644 src/equicordplugins/sedEnhanced/index.ts diff --git a/src/equicordplugins/sedEnhanced/index.ts b/src/equicordplugins/sedEnhanced/index.ts new file mode 100644 index 000000000..0002d54ec --- /dev/null +++ b/src/equicordplugins/sedEnhanced/index.ts @@ -0,0 +1,75 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { MessageActions, MessageStore, PendingReplyStore, UserStore } from "@webpack/common"; + +const sedRegex = /^s(?[/|$#@!])(?(?!\1)(?:(?![^\\]\1).)*.|)\1(?(?!\1)(?:(?![^\\]\1).)*.|)\1?(?[rgmisudyv]*)$/; +const settings = definePluginSettings({ + regexByDefault: { + description: "Inverts the `r` flag, so using the `r` flag enables non-regex mode, and omitting it uses regex mode.", + type: OptionType.BOOLEAN, + default: false + } +}); + +export default definePlugin({ + name: "SedEnhanced", + description: "Expands on Discord's rudimentary `sed` support.", + authors: [EquicordDevs.dawn, EquicordDevs.Willow, EquicordDevs.kat], + patches: [ + { + find: ".SLASH_COMMAND_USED,{", + replacement: { + match: /searchReplace:\{match:(\i\(\)\.anyScopeRegex)/, + replace: "searchReplace:{match:$1($self.sedRegex),action:$self.searchReplace},_eq_$&" + } + } + ], + settings, + sedRegex, + searchReplace(content, { isEdit, channel }) { + if (isEdit) return; + + const pendingReply = PendingReplyStore.getPendingReply(channel.id)?.message; + const toEdit = pendingReply ?? MessageStore.getLastEditableMessage(channel.id); + if (pendingReply && pendingReply.author.id !== UserStore.getCurrentUser()?.id || toEdit?.id == null) return { content: "" }; + + const groups = content.match(sedRegex)?.groups; + if (groups?.match == null || groups?.replace == null || groups?.modes == null) return; + + let { match: pattern, replace, modes } = groups; + const flags = modes.split(""); + const isRegex = flags.includes("r") !== settings.store.regexByDefault; + + if (!isRegex) { + const escapeChars = /\\([*?+/])/g; + pattern = pattern.replace(escapeChars, (_, x) => x); + replace = replace.replace(escapeChars, (_, x) => x); + } + + let find: string | RegExp = pattern; + if (isRegex) { + try { + const regexFlags = flags.filter(f => "gmisudyv".includes(f)).join(""); + find = new RegExp(pattern, regexFlags); + } catch { + return { content: "" }; + } + } + + const replaced = flags.includes("g") ? toEdit.content.replaceAll(find, replace) : toEdit.content.replace(find, replace); + if (!replaced.trim() && toEdit.attachments.length === 0) { + MessageActions.deleteMessage(channel.id, toEdit.id); + } else if (replaced !== toEdit.content) { + MessageActions.editMessage(channel.id, toEdit.id, { content: replaced }); + } + + return { content: "" }; + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 59a1fc932..e1f2d3c2a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1366,6 +1366,18 @@ export const EquicordDevs = Object.freeze({ name: "jahsohsani", id: 806442568564604968n }, + dawn: { + name: "dawn", + id: 604653220341743618n + }, + Willow: { + name: "Willow", + id: 965933914092888074n + }, + kat: { + name: "kat", + id: 665120188047556609n + }, } satisfies Record); // iife so #__PURE__ works correctly