Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cae8b1a93b | ||
|
a1c1fec8cb | ||
|
55a66dbb39 | ||
|
a2f0c912f0 | ||
|
e29bbf73aa | ||
|
0ba3e9f469 | ||
|
6f200e9218 | ||
|
586b26d2d4 | ||
|
d482d33d6f | ||
|
37c2a8a5de | ||
|
265547213c | ||
|
87e46f5a5a | ||
|
e36f4e5b0a | ||
|
4aff11421f | ||
|
ea642d9e90 | ||
|
17c3496542 | ||
|
0fb79b763d | ||
|
5873bde6a6 | ||
|
0b79387800 | ||
|
6b493bc7d9 | ||
|
de53bc7991 | ||
|
4c5a56a8a5 |
@ -26,6 +26,10 @@ export default definePlugin({
|
||||
name: "Your Name",
|
||||
},
|
||||
],
|
||||
// Delete `patches` if you are not using code patches, as it will make
|
||||
// your plugin require restarts, and your stop() method will not be
|
||||
// invoked at all. The presence of the key in the object alone is
|
||||
// enough to trigger this behavior, even if the value is an empty array.
|
||||
patches: [],
|
||||
// Delete these two below if you are only using code patches
|
||||
start() {},
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"description": "The cutest Discord client mod",
|
||||
"keywords": [],
|
||||
"keywords": [ ],
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Vendicated/Vencord/issues"
|
||||
|
@ -27,7 +27,7 @@ export { PlainSettings, Settings };
|
||||
import "./utils/quickCss";
|
||||
import "./webpack/patchWebpack";
|
||||
|
||||
import { popNotice, showNotice } from "./api/Notices";
|
||||
import { showNotification } from "./api/Notifications";
|
||||
import { PlainSettings, Settings } from "./api/settings";
|
||||
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
||||
import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater";
|
||||
@ -49,32 +49,30 @@ async function init() {
|
||||
if (Settings.autoUpdate) {
|
||||
await update();
|
||||
const needsFullRestart = await rebuild();
|
||||
setTimeout(() => {
|
||||
showNotice(
|
||||
"Vencord has been updated!",
|
||||
"Restart",
|
||||
() => {
|
||||
if (Settings.autoUpdateNotification)
|
||||
setTimeout(() => showNotification({
|
||||
title: "Vencord has been updated!",
|
||||
body: "Click here to restart",
|
||||
permanent: true,
|
||||
onClick() {
|
||||
if (needsFullRestart)
|
||||
window.DiscordNative.app.relaunch();
|
||||
else
|
||||
location.reload();
|
||||
}
|
||||
);
|
||||
}, 10_000);
|
||||
}), 10_000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.notifyAboutUpdates)
|
||||
setTimeout(() => {
|
||||
showNotice(
|
||||
"A Vencord update is available!",
|
||||
"View Update",
|
||||
() => {
|
||||
popNotice();
|
||||
SettingsRouter.open("VencordUpdater");
|
||||
}
|
||||
);
|
||||
}, 10_000);
|
||||
setTimeout(() => showNotification({
|
||||
title: "A Vencord update is available!",
|
||||
body: "Click here to view the update",
|
||||
permanent: true,
|
||||
onClick() {
|
||||
SettingsRouter.open("VencordUpdater");
|
||||
}
|
||||
}), 10_000);
|
||||
} catch (err) {
|
||||
UpdateLogger.error("Failed to check for updates", err);
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ import type { ReactElement } from "react";
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, args?: Array<any>) => void;
|
||||
export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, ...args: Array<any>) => void;
|
||||
/**
|
||||
* @param The navId of the context menu being patched
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, args?: Array<any>) => void;
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, ...args: Array<any>) => void;
|
||||
|
||||
const ContextMenuLogger = new Logger("ContextMenu");
|
||||
|
||||
@ -119,12 +119,13 @@ interface ContextMenuProps {
|
||||
}
|
||||
|
||||
export function _patchContextMenu(props: ContextMenuProps) {
|
||||
props.contextMenuApiArguments ??= [];
|
||||
const contextMenuPatches = navPatches.get(props.navId);
|
||||
|
||||
if (contextMenuPatches) {
|
||||
for (const patch of contextMenuPatches) {
|
||||
try {
|
||||
patch(props.children, props.contextMenuApiArguments);
|
||||
patch(props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||
}
|
||||
@ -133,7 +134,7 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
||||
|
||||
for (const patch of globalPatches) {
|
||||
try {
|
||||
patch(props.navId, props.children, props.contextMenuApiArguments);
|
||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error("Global patch errored,", err);
|
||||
}
|
||||
|
@ -63,7 +63,10 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
||||
<button
|
||||
className="vc-notification-root"
|
||||
style={position === "bottom-right" ? { bottom: "1rem" } : { top: "3rem" }}
|
||||
onClick={onClick}
|
||||
onClick={() => {
|
||||
onClose!();
|
||||
onClick?.();
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -78,7 +81,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
||||
<div className="vc-notification-header">
|
||||
<h2 className="vc-notification-title">{title}</h2>
|
||||
<button
|
||||
style={{ all: "unset", cursor: "pointer" }}
|
||||
className="vc-notification-close-btn"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -86,7 +89,6 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="vc-notification-close-btn"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -40,6 +40,8 @@
|
||||
}
|
||||
|
||||
.vc-notification-close-btn {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
color: var(--interactive-normal);
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
|
@ -28,6 +28,7 @@ const logger = new Logger("Settings");
|
||||
export interface Settings {
|
||||
notifyAboutUpdates: boolean;
|
||||
autoUpdate: boolean;
|
||||
autoUpdateNotification: boolean,
|
||||
useQuickCss: boolean;
|
||||
enableReactDevtools: boolean;
|
||||
themeLinks: string[];
|
||||
@ -52,6 +53,7 @@ export interface Settings {
|
||||
const DefaultSettings: Settings = {
|
||||
notifyAboutUpdates: true,
|
||||
autoUpdate: false,
|
||||
autoUpdateNotification: true,
|
||||
useQuickCss: true,
|
||||
themeLinks: [],
|
||||
enableReactDevtools: false,
|
||||
|
@ -19,7 +19,8 @@
|
||||
import { debounce } from "@utils/debounce";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { makeCodeblock } from "@utils/misc";
|
||||
import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import { ReplaceFn } from "@utils/types";
|
||||
import { search } from "@webpack";
|
||||
import { Button, Clipboard, Forms, Parser, React, Switch, Text, TextInput } from "@webpack/common";
|
||||
|
||||
|
@ -38,9 +38,12 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
|
||||
|
||||
function handleChange(newValue) {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setError(null);
|
||||
if (typeof isValid === "string") setError(isValid);
|
||||
else if (!isValid) setError("Invalid input provided.");
|
||||
else if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
|
||||
|
||||
if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
|
||||
setState(`${Number.MAX_SAFE_INTEGER}`);
|
||||
onChange(serialize(newValue));
|
||||
} else {
|
||||
|
@ -36,6 +36,7 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
|
||||
if (typeof isValid === "string") setError(isValid);
|
||||
else if (!isValid) setError("Invalid input provided.");
|
||||
else {
|
||||
setError(null);
|
||||
setState(newValue);
|
||||
onChange(newValue);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
|
||||
if (typeof isValid === "string") setError(isValid);
|
||||
else if (!isValid) setError("Invalid input provided.");
|
||||
else {
|
||||
setError(null);
|
||||
setState(newValue);
|
||||
onChange(newValue);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
import "./Switch.css";
|
||||
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
interface SwitchProps {
|
||||
@ -33,7 +34,7 @@ const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
||||
export function Switch({ checked, onChange, disabled }: SwitchProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className={`${SwitchClasses.container} default-colors`} style={{
|
||||
<div className={classes(SwitchClasses.container, "default-colors", checked ? SwitchClasses.checked : void 0)} style={{
|
||||
backgroundColor: checked ? SWITCH_ON : SWITCH_OFF,
|
||||
opacity: disabled ? 0.3 : 1
|
||||
}}>
|
||||
|
@ -185,7 +185,7 @@ function Newer(props: CommonProps) {
|
||||
}
|
||||
|
||||
function Updater() {
|
||||
const settings = useSettings(["notifyAboutUpdates", "autoUpdate"]);
|
||||
const settings = useSettings(["notifyAboutUpdates", "autoUpdate", "autoUpdateNotification"]);
|
||||
|
||||
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
||||
|
||||
@ -205,7 +205,7 @@ function Updater() {
|
||||
<Switch
|
||||
value={settings.notifyAboutUpdates}
|
||||
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
||||
note="Shows a toast on startup"
|
||||
note="Shows a notification on startup"
|
||||
disabled={settings.autoUpdate}
|
||||
>
|
||||
Get notified about new updates
|
||||
@ -217,6 +217,14 @@ function Updater() {
|
||||
>
|
||||
Automatically update
|
||||
</Switch>
|
||||
<Switch
|
||||
value={settings.autoUpdateNotification}
|
||||
onChange={(v: boolean) => settings.autoUpdateNotification = v}
|
||||
note="Shows a notification when Vencord automatically updates"
|
||||
disabled={!settings.autoUpdate}
|
||||
>
|
||||
Get notified when an automatic update completes
|
||||
</Switch>
|
||||
|
||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||
|
||||
|
@ -18,9 +18,28 @@
|
||||
|
||||
import { Settings } from "@api/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { type PatchReplacement } from "@utils/types";
|
||||
import { addListener, removeListener } from "@webpack";
|
||||
|
||||
/**
|
||||
* The last var name corresponding to the Context Menu API (Discord, not ours) module
|
||||
*/
|
||||
let lastVarName = "";
|
||||
|
||||
/**
|
||||
* @param target The patch replacement object
|
||||
* @param exportKey The key exporting the build Context Menu component function
|
||||
*/
|
||||
function makeReplacementProxy(target: PatchReplacement, exportKey: string) {
|
||||
return new Proxy(target, {
|
||||
get(_, p) {
|
||||
if (p === "match") return RegExp(`${exportKey},{(?<=${lastVarName}\\.${exportKey},{)`, "g");
|
||||
// @ts-expect-error
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listener(exports: any, id: number) {
|
||||
if (!Settings.plugins.ContextMenuAPI.enabled) return removeListener(listener);
|
||||
|
||||
@ -37,13 +56,24 @@ function listener(exports: any, id: number) {
|
||||
all: true,
|
||||
noWarn: true,
|
||||
find: "navId:",
|
||||
replacement: [{
|
||||
match: RegExp(`${id}(?<=(\\i)=.+?).+$`),
|
||||
replace: (code, varName) => {
|
||||
const regex = RegExp(`${key},{(?<=${varName}\\.${key},{)`, "g");
|
||||
return code.replace(regex, "$&contextMenuApiArguments:arguments,");
|
||||
}
|
||||
}]
|
||||
replacement: [
|
||||
{
|
||||
// Set the lastVarName for our proxy to use
|
||||
match: RegExp(`${id}(?<=(\\i)=.+?)`),
|
||||
replace: (id, varName) => {
|
||||
lastVarName = varName;
|
||||
return id;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* We are using a proxy here to utilize the whole code the patcher gives us, instead of matching the entire module (which is super slow)
|
||||
* Our proxy returns the corresponding match for that module utilizing lastVarName, which is set by the patch before
|
||||
*/
|
||||
makeReplacementProxy({
|
||||
match: "", // Needed to canonicalizeDescriptor
|
||||
replace: "$&contextMenuApiArguments:arguments,",
|
||||
}, key)
|
||||
]
|
||||
});
|
||||
|
||||
removeListener(listener);
|
||||
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
||||
replacement: {
|
||||
// foo && !bar ? createElement(blah,...makeElement(addReactionData))
|
||||
match: /(\i&&!\i)\?\(0,\i\.jsxs?\)\(.{0,20}renderPopout:.{0,300}?(\i)\(.{3,20}\{key:"add-reaction".+?\}/,
|
||||
match: /(\i&&!\i)\?\(0,\i\.jsxs?\)\(.{0,200}renderPopout:.{0,300}?(\i)\(.{3,20}\{key:"add-reaction".+?\}/,
|
||||
replace: (m, bools, makeElement) => {
|
||||
const msg = m.match(/message:(.{1,3}),/)?.[1];
|
||||
if (!msg) throw new Error("Could not find message variable");
|
||||
|
@ -27,11 +27,16 @@ export default definePlugin({
|
||||
{
|
||||
find: "Masks.STATUS_ONLINE",
|
||||
replacement: {
|
||||
// we can use global replacement here - these are specific to the status icons and are used nowhere else,
|
||||
// so it keeps the patch and plugin small and simple
|
||||
match: /Masks\.STATUS_(?:IDLE|DND|STREAMING|OFFLINE)/g,
|
||||
replace: "Masks.STATUS_ONLINE"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".AVATAR_STATUS_MOBILE_16;",
|
||||
replacement: {
|
||||
match: /(\.fromIsMobile,.+?)\i.status/,
|
||||
replace: (_, rest) => `${rest}"online"`
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -42,6 +42,7 @@ const settings = definePluginSettings({
|
||||
});
|
||||
|
||||
let crashCount: number = 0;
|
||||
let lastCrashTimestamp: number = 0;
|
||||
|
||||
export default definePlugin({
|
||||
name: "CrashHandler",
|
||||
@ -80,6 +81,7 @@ export default definePlugin({
|
||||
});
|
||||
} catch { }
|
||||
|
||||
lastCrashTimestamp = Date.now();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -97,17 +99,21 @@ export default definePlugin({
|
||||
} catch (err) {
|
||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||
return false;
|
||||
} finally {
|
||||
lastCrashTimestamp = Date.now();
|
||||
}
|
||||
},
|
||||
|
||||
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Attempting to recover...",
|
||||
});
|
||||
} catch { }
|
||||
if (Date.now() - lastCrashTimestamp >= 1_000) {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Attempting to recover...",
|
||||
});
|
||||
} catch { }
|
||||
}
|
||||
|
||||
try {
|
||||
FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" });
|
||||
|
@ -16,12 +16,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch } from "@api/ContextMenu";
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { definePluginSettings } from "@api/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import Logger from "@utils/Logger";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { filters, findAll, search } from "@webpack";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
@ -65,6 +66,14 @@ interface FindData {
|
||||
args: Array<StringNode | FunctionNode>;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
notifyOnAutoConnect: {
|
||||
description: "Whether to notify when Dev Companion has automatically connected.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
function parseNode(node: Node) {
|
||||
switch (node.type) {
|
||||
case "string":
|
||||
@ -91,7 +100,7 @@ function initWs(isManual = false) {
|
||||
|
||||
logger.info("Connected to WebSocket");
|
||||
|
||||
showNotification({
|
||||
(settings.store.notifyOnAutoConnect || isManual) && showNotification({
|
||||
title: "Dev Companion Connected",
|
||||
body: "Connected to WebSocket"
|
||||
});
|
||||
@ -221,32 +230,36 @@ function initWs(isManual = false) {
|
||||
});
|
||||
}
|
||||
|
||||
const contextMenuPatch: NavContextMenuPatchCallback = kids => {
|
||||
if (kids.some(k => k?.props?.id === NAV_ID)) return;
|
||||
|
||||
kids.unshift(
|
||||
<Menu.MenuItem
|
||||
id={NAV_ID}
|
||||
label="Reconnect Dev Companion"
|
||||
action={() => {
|
||||
socket?.close(1000, "Reconnecting");
|
||||
initWs(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "DevCompanion",
|
||||
description: "Dev Companion Plugin",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["ContextMenuAPI"],
|
||||
settings,
|
||||
|
||||
start() {
|
||||
initWs();
|
||||
addContextMenuPatch("user-settings-cog", kids => {
|
||||
if (kids.some(k => k?.props?.id === NAV_ID)) return;
|
||||
|
||||
kids.unshift(
|
||||
<Menu.MenuItem
|
||||
id={NAV_ID}
|
||||
label="Reconnect Dev Companion"
|
||||
action={() => {
|
||||
socket?.close(1000, "Reconnecting");
|
||||
initWs(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
addContextMenuPatch("user-settings-cog", contextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
socket?.close(1000, "Plugin Stopped");
|
||||
socket = void 0;
|
||||
removeContextMenuPatch("user-settings-cog", contextMenuPatch);
|
||||
}
|
||||
});
|
||||
|
@ -176,9 +176,9 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
|
||||
);
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => {
|
||||
if (!args?.[0]) return;
|
||||
const { favoriteableId, emoteClonerDataAlt, itemHref, itemSrc, favoriteableType } = args[0];
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
if (!props) return;
|
||||
const { favoriteableId, emoteClonerDataAlt, itemHref, itemSrc, favoriteableType } = props;
|
||||
|
||||
if (!emoteClonerDataAlt || favoriteableType !== "emoji") return;
|
||||
|
||||
@ -188,7 +188,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, args) =>
|
||||
const src = itemHref ?? itemSrc;
|
||||
const isAnimated = new URL(src).pathname.endsWith(".gif");
|
||||
|
||||
const group = findGroupChildrenByChildId("save-image", children);
|
||||
const group = findGroupChildrenByChildId("copy-link", children);
|
||||
if (group && !group.some(child => child?.props?.id === "emote-cloner")) {
|
||||
group.push((
|
||||
<Menu.MenuItem
|
||||
|
42
src/plugins/f8break.ts
Normal file
42
src/plugins/f8break.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "F8Break",
|
||||
description: "Pause the client when you press F8 with DevTools (+ breakpoints) open.",
|
||||
authors: [Devs.lewisakura],
|
||||
|
||||
start() {
|
||||
window.addEventListener("keydown", this.event);
|
||||
},
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.event);
|
||||
},
|
||||
|
||||
event(e: KeyboardEvent) {
|
||||
if (e.code === "F8") {
|
||||
// Hi! You've just paused the client. Pressing F8 in DevTools or in the main window will unpause it again.
|
||||
// It's up to you on what to do, friend. Happy travels!
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
});
|
@ -20,12 +20,15 @@ import { addPreEditListener, addPreSendListener, removePreEditListener, removePr
|
||||
import { migratePluginSettings, Settings } from "@api/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
|
||||
import { getCurrentGuild } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, PermissionStore, UserStore } from "@webpack/common";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, FluxDispatcher, PermissionStore, UserStore } from "@webpack/common";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
|
||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||
const ProtoPreloadedUserSettings = findLazy(m => m.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings");
|
||||
|
||||
const USE_EXTERNAL_EMOJIS = 1n << 18n;
|
||||
const USE_EXTERNAL_STICKERS = 1n << 37n;
|
||||
@ -86,12 +89,12 @@ export default definePlugin({
|
||||
replace: (_, intention) => `,fakeNitroIntention=${intention}`
|
||||
},
|
||||
{
|
||||
match: /(?<=\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g,
|
||||
replace: ',typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
||||
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
||||
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
||||
},
|
||||
{
|
||||
match: /(?<=&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||
replace: (_, canUseExternal) => `(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
||||
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -99,16 +102,16 @@ export default definePlugin({
|
||||
find: "canUseAnimatedEmojis:function",
|
||||
predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true,
|
||||
replacement: {
|
||||
match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g,
|
||||
replace: (_, premiumCheck) => `,fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g,
|
||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "canUseStickersEverywhere:function",
|
||||
predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true,
|
||||
replacement: {
|
||||
match: /(?<=canUseStickersEverywhere:function\(\i\){)/,
|
||||
replace: "return true;"
|
||||
match: /canUseStickersEverywhere:function\(\i\){/,
|
||||
replace: "$&return true;"
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -128,8 +131,8 @@ export default definePlugin({
|
||||
"canStreamMidQuality"
|
||||
].map(func => {
|
||||
return {
|
||||
match: new RegExp(`(?<=${func}:function\\(\\i\\){)`),
|
||||
replace: "return true;"
|
||||
match: new RegExp(`${func}:function\\(\\i\\){`),
|
||||
replace: "$&return true;"
|
||||
};
|
||||
})
|
||||
},
|
||||
@ -144,8 +147,28 @@ export default definePlugin({
|
||||
{
|
||||
find: "canUseClientThemes:function",
|
||||
replacement: {
|
||||
match: /(?<=canUseClientThemes:function\(\i\){)/,
|
||||
replace: "return true;"
|
||||
match: /canUseClientThemes:function\(\i\){/,
|
||||
replace: "$&return true;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '.displayName="UserSettingsProtoStore"',
|
||||
replacement: [
|
||||
{
|
||||
match: /CONNECTION_OPEN:function\((\i)\){/,
|
||||
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
||||
},
|
||||
{
|
||||
match: /=(\i)\.local;/,
|
||||
replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "updateTheme:function",
|
||||
replacement: {
|
||||
match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\)/,
|
||||
replace: (_, rest, backgroundGradientPresetId, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme});`
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -184,7 +207,7 @@ export default definePlugin({
|
||||
},
|
||||
|
||||
get guildId() {
|
||||
return window.location.href.split("channels/")[1].split("/")[0];
|
||||
return getCurrentGuild()?.id;
|
||||
},
|
||||
|
||||
get canUseEmotes() {
|
||||
@ -195,6 +218,60 @@ export default definePlugin({
|
||||
return (UserStore.getCurrentUser().premiumType ?? 0) > 1;
|
||||
},
|
||||
|
||||
handleProtoChange(proto: any, user: any) {
|
||||
const premiumType: number = user?.premium_type ?? UserStore.getCurrentUser()?.premiumType ?? 0;
|
||||
|
||||
if (premiumType === 0) {
|
||||
const appearanceDummyProto = ProtoPreloadedUserSettings.create({
|
||||
appearance: {}
|
||||
});
|
||||
|
||||
proto.appearance ??= appearanceDummyProto.appearance;
|
||||
|
||||
if (UserSettingsProtoStore.settings.appearance?.theme != null) {
|
||||
proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme;
|
||||
}
|
||||
|
||||
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
|
||||
const clientThemeSettingsDummyProto = ProtoPreloadedUserSettings.create({
|
||||
appearance: {
|
||||
clientThemeSettings: {
|
||||
backgroundGradientPresetId: {
|
||||
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto.appearance.clientThemeSettings;
|
||||
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.appearance.clientThemeSettings.backgroundGradientPresetId;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleGradientThemeSelect(backgroundGradientPresetId: number | undefined, theme: number) {
|
||||
const proto = ProtoPreloadedUserSettings.create({
|
||||
appearance: {
|
||||
theme,
|
||||
clientThemeSettings: {
|
||||
backgroundGradientPresetId: backgroundGradientPresetId != null ? {
|
||||
value: backgroundGradientPresetId
|
||||
} : void 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "USER_SETTINGS_PROTO_UPDATE",
|
||||
local: true,
|
||||
partial: true,
|
||||
settings: {
|
||||
type: 1,
|
||||
proto
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
hasPermissionToUseExternalEmojis(channelId: string) {
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
|
||||
|
@ -23,7 +23,7 @@ import { findByProps } from "@webpack";
|
||||
|
||||
export default definePlugin({
|
||||
name: "FriendInvites",
|
||||
description: "Generate and manage friend invite links.",
|
||||
description: "Create and manage friend invite links via slash commands (/create friend invite, /view friend invites, /revoke friend invites).",
|
||||
authors: [Devs.afn],
|
||||
dependencies: ["CommandsAPI"],
|
||||
commands: [
|
||||
@ -37,8 +37,8 @@ export default definePlugin({
|
||||
|
||||
return void sendBotMessage(ctx.channel.id, {
|
||||
content: `
|
||||
discord.gg/${createInvite.code}
|
||||
Expires: <t:${new Date(createInvite.expires_at).getTime() / 1000}:R>
|
||||
discord.gg/${createInvite.code} ·
|
||||
Expires: <t:${new Date(createInvite.expires_at).getTime() / 1000}:R> ·
|
||||
Max uses: \`${createInvite.max_uses}\`
|
||||
`.trim().replace(/\s+/g, " ")
|
||||
});
|
||||
@ -52,25 +52,25 @@ export default definePlugin({
|
||||
const friendInvites = findByProps("createFriendInvite");
|
||||
const invites = await friendInvites.getAllFriendInvites();
|
||||
const friendInviteList = invites.map(i =>
|
||||
`_discord.gg/${i.code}_
|
||||
Expires: <t:${new Date(i.expires_at).getTime() / 1000}:R>
|
||||
`_discord.gg/${i.code}_ ·
|
||||
Expires: <t:${new Date(i.expires_at).getTime() / 1000}:R> ·
|
||||
Times used: \`${i.uses}/${i.max_uses}\``.trim().replace(/\s+/g, " ")
|
||||
);
|
||||
|
||||
return void sendBotMessage(ctx.channel.id, {
|
||||
content: friendInviteList.join("\n\n") || "You have no active friend invites!"
|
||||
content: friendInviteList.join("\n") || "You have no active friend invites!"
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "revoke friend invites",
|
||||
description: "Revokes ALL generated friend invite links.",
|
||||
description: "Revokes all generated friend invites.",
|
||||
inputType: ApplicationCommandInputType.BOT,
|
||||
execute: async (_, ctx) => {
|
||||
await findByProps("createFriendInvite").revokeFriendInvites();
|
||||
|
||||
return void sendBotMessage(ctx.channel.id, {
|
||||
content: "All friend links have been revoked."
|
||||
content: "All friend invites have been revoked."
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -131,8 +131,8 @@ export default definePlugin({
|
||||
{
|
||||
find: ".activeCommandOption",
|
||||
replacement: {
|
||||
match: /(.)\.push.{1,50}\(\i,\{.{1,30}\},"gift"\)\)/,
|
||||
replace: "$&;try{$1.push($self.chatBarIcon())}catch{}",
|
||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -20,18 +20,20 @@ import { addClickListener, removeClickListener } from "@api/MessageEvents";
|
||||
import { migratePluginSettings } from "@api/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { PermissionStore, UserStore } from "@webpack/common";
|
||||
|
||||
let isDeletePressed = false;
|
||||
const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true);
|
||||
const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false);
|
||||
|
||||
const MANAGE_CHANNELS = 1n << 4n;
|
||||
|
||||
migratePluginSettings("MessageClickActions", "MessageQuickActions");
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageClickActions",
|
||||
description: "Hold Delete and click to delete, double click to edit",
|
||||
description: "Hold Backspace and click to delete, double click to edit",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
@ -50,8 +52,6 @@ export default definePlugin({
|
||||
|
||||
start() {
|
||||
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
|
||||
const PermissionStore = findByPropsLazy("can", "initialize");
|
||||
const Permissions = findLazy(m => typeof m.MANAGE_MESSAGES === "bigint");
|
||||
const EditStore = findByPropsLazy("isEditing", "isEditingAny");
|
||||
|
||||
document.addEventListener("keydown", keydown);
|
||||
@ -64,7 +64,7 @@ export default definePlugin({
|
||||
MessageActions.startEditMessage(chan.id, msg.id, msg.content);
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (Vencord.Settings.plugins.MessageClickActions.enableDeleteOnClick && (isMe || PermissionStore.can(Permissions.MANAGE_MESSAGES, chan))) {
|
||||
} else if (Vencord.Settings.plugins.MessageClickActions.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, chan))) {
|
||||
MessageActions.deleteMessage(chan.id, msg.id);
|
||||
event.preventDefault();
|
||||
}
|
@ -24,11 +24,15 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import Logger from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { moment, Parser, Timestamp, UserStore } from "@webpack/common";
|
||||
|
||||
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||
import textStyle from "./deleteStyleText.css?managed";
|
||||
|
||||
const i18n = findLazy(m => m.Messages?.["en-US"]);
|
||||
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
|
||||
|
||||
function addDeleteStyle() {
|
||||
if (Settings.plugins.MessageLogger.deleteStyle === "text") {
|
||||
enableStyle(textStyle);
|
||||
@ -65,7 +69,7 @@ export default definePlugin({
|
||||
isEdited={true}
|
||||
isInline={false}
|
||||
>
|
||||
<span>{" "}(edited)</span>
|
||||
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
|
||||
</Timestamp>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
@ -170,6 +174,7 @@ export default definePlugin({
|
||||
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
|
||||
replace: "$1" +
|
||||
".update($3,m =>" +
|
||||
" (($2.message.flags & 64) === 64 || (Vencord.Settings.plugins.MessageLogger.ignoreBots && $2.message.author?.bot) || (Vencord.Settings.plugins.MessageLogger.ignoreSelf && $2.message.author?.id === Vencord.Webpack.Common.UserStore.getCurrentUser().id)) ? m :" +
|
||||
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" +
|
||||
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
|
||||
" m" +
|
||||
|
@ -37,16 +37,19 @@ export default definePlugin({
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "displayName=\"MessageStore\"",
|
||||
...[
|
||||
'displayName="MessageStore"',
|
||||
'displayName="ReadStateStore"'
|
||||
].map(find => ({
|
||||
find,
|
||||
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=MESSAGE_CREATE:function\((\w)\){var \w=\w\.channelId,\w=\w\.message,\w=\w\.isPushNotification,\w=\w\.\w\.getOrCreate\(\w\));/,
|
||||
replace: ";if($self.isBlocked(n))return;"
|
||||
match: /(?<=MESSAGE_CREATE:function\((\i)\){)/,
|
||||
replace: (_, props) => `if($self.isBlocked(${props}.message))return;`
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
],
|
||||
options: {
|
||||
ignoreBlockedMessages: {
|
||||
|
@ -23,11 +23,11 @@ import { Settings } from "@api/settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
const SessionStore = findByPropsLazy("getActiveSession");
|
||||
const SessionsStore = findStoreLazy("SessionsStore");
|
||||
|
||||
function Icon(path: string, viewBox = "0 0 24 24") {
|
||||
return ({ color, tooltip }: { color: string; tooltip: string; }) => (
|
||||
@ -70,7 +70,7 @@ const PlatformIndicator = ({ user, inline = false, marginLeft = "4px" }: { user:
|
||||
if (!user || user.bot) return null;
|
||||
|
||||
if (user.id === UserStore.getCurrentUser().id) {
|
||||
const sessions = SessionStore.getSessions();
|
||||
const sessions = SessionsStore.getSessions();
|
||||
if (typeof sessions !== "object") return null;
|
||||
const sortedSessions = Object.values(sessions).sort(({ status: a }: any, { status: b }: any) => {
|
||||
if (a === b) return 0;
|
||||
@ -156,7 +156,7 @@ const indicatorLocations = {
|
||||
export default definePlugin({
|
||||
name: "PlatformIndicators",
|
||||
description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
|
||||
authors: [Devs.kemo, Devs.TheSun],
|
||||
authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz],
|
||||
dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI"],
|
||||
|
||||
start() {
|
||||
@ -185,6 +185,55 @@ export default definePlugin({
|
||||
});
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".Masks.STATUS_ONLINE_MOBILE",
|
||||
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
|
||||
replacement: [
|
||||
{
|
||||
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
||||
match: /(?<=return \i\.\i\.Masks\.STATUS_TYPING;)(.+?)(\i)\?(\i\.\i\.Masks\.STATUS_ONLINE_MOBILE):/,
|
||||
replace: (_, rest, isMobile, mobileMask) => `if(${isMobile})return ${mobileMask};${rest}`
|
||||
},
|
||||
{
|
||||
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
||||
match: /(switch\(\i\){case \i\.\i\.ONLINE:return )(\i)\?({.+?}):/,
|
||||
replace: (_, rest, isMobile, component) => `if(${isMobile})return${component};${rest}`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".AVATAR_STATUS_MOBILE_16;",
|
||||
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
|
||||
replacement: [
|
||||
{
|
||||
// Return the AVATAR_STATUS_MOBILE size mask if the user is on mobile, no matter the status
|
||||
match: /\i===\i\.\i\.ONLINE&&(?=.{0,70}\.AVATAR_STATUS_MOBILE_16;)/,
|
||||
replace: ""
|
||||
},
|
||||
{
|
||||
// Fix sizes for mobile indicators which aren't online
|
||||
match: /(?<=\(\i\.status,)(\i)(?=,(\i),\i\))/,
|
||||
replace: (_, userStatus, isMobile) => `${isMobile}?"online":${userStatus}`
|
||||
},
|
||||
{
|
||||
// Make isMobile true no matter the status
|
||||
match: /(?<=\i&&!\i)&&\i===\i\.\i\.ONLINE/,
|
||||
replace: ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "isMobileOnline=function",
|
||||
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
|
||||
replacement: {
|
||||
// Make isMobileOnline return true no matter what is the user status
|
||||
match: /(?<=\i\[\i\.\i\.MOBILE\])===\i\.\i\.ONLINE/,
|
||||
replace: "!= null"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
options: {
|
||||
...Object.fromEntries(
|
||||
Object.entries(indicatorLocations).map(([key, value]) => {
|
||||
@ -196,6 +245,12 @@ export default definePlugin({
|
||||
default: true
|
||||
}];
|
||||
})
|
||||
)
|
||||
),
|
||||
colorMobileIndicator: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether to make the mobile indicator match the color of the user status.",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -34,15 +34,15 @@ function search(src: string, engine: string) {
|
||||
open(engine + encodeURIComponent(src), "_blank");
|
||||
}
|
||||
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => {
|
||||
if (!args?.[0]) return;
|
||||
const { reverseImageSearchType, itemHref, itemSrc } = args[0];
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
if (!props) return;
|
||||
const { reverseImageSearchType, itemHref, itemSrc } = props;
|
||||
|
||||
if (!reverseImageSearchType || reverseImageSearchType !== "img") return;
|
||||
|
||||
const src = itemHref ?? itemSrc;
|
||||
|
||||
const group = findGroupChildrenByChildId("save-image", children);
|
||||
const group = findGroupChildrenByChildId("copy-link", children);
|
||||
if (group && !group.some(child => child?.props?.id === "search-image")) {
|
||||
group.push((
|
||||
<Menu.MenuItem
|
||||
|
@ -52,8 +52,8 @@ export default definePlugin({
|
||||
find: 'className:"mention"',
|
||||
replacement: [
|
||||
{
|
||||
match: /user:(\i),channelId:(\i).{0,300}?"@"\.concat\(.+?\)/,
|
||||
replace: "$&,color:$self.getUserColor($1.id,{channelId:$2})"
|
||||
match: /user:(\i),channel:(\i).{0,300}?"@"\.concat\(.+?\)/,
|
||||
replace: "$&,color:$self.getUserColor($1.id,{channelId:$2?.id})"
|
||||
}
|
||||
],
|
||||
predicate: () => settings.store.chatMentions,
|
||||
@ -65,7 +65,7 @@ export default definePlugin({
|
||||
replacement: [
|
||||
{
|
||||
match: /function \i\((\i)\).{5,20}id.{5,20}guildId.{5,10}channelId.{100,150}hidePersonalInformation.{5,50}jsx.{5,20},{/,
|
||||
replace: "$&color:$self.getUserColor($1.id,{guildId:$1.guildId}),"
|
||||
replace: "$&color:$self.getUserColor($1.id,{guildId:$1?.guildId}),"
|
||||
}
|
||||
],
|
||||
predicate: () => settings.store.chatMentions,
|
||||
|
@ -20,10 +20,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { LazyComponent } from "@utils/misc";
|
||||
import { formatDuration } from "@utils/text";
|
||||
import { find, findByPropsLazy } from "@webpack";
|
||||
import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
||||
import type { Channel } from "discord-types/general";
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
import { VIEW_CHANNEL } from "..";
|
||||
|
||||
|
||||
enum SortOrderTypes {
|
||||
LATEST_ACTIVITY = 0,
|
||||
@ -167,7 +169,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} />
|
||||
|
||||
<div className="shc-lock-screen-heading-container">
|
||||
<Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text>
|
||||
<Text variant="heading-xxl/bold">This is a {!PermissionStore.can(VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text>
|
||||
{channel.isNSFW() &&
|
||||
<Tooltip text="NSFW">
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
|
@ -21,6 +21,7 @@ import "./style.css";
|
||||
import { definePluginSettings } from "@api/settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common";
|
||||
@ -30,7 +31,8 @@ import HiddenChannelLockScreen, { setChannelBeginHeaderComponent, setEmojiCompon
|
||||
|
||||
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
|
||||
|
||||
const VIEW_CHANNEL = 1n << 10n;
|
||||
export const VIEW_CHANNEL = 1n << 10n;
|
||||
const CONNECT = 1n << 20n;
|
||||
|
||||
enum ShowMode {
|
||||
LockIcon,
|
||||
@ -99,14 +101,14 @@ export default definePlugin({
|
||||
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
|
||||
},
|
||||
{
|
||||
// Make Discord think we are connected to a voice channel so it shows us inside it
|
||||
// Prevent Discord from trying to connect to hidden channels
|
||||
match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/,
|
||||
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
|
||||
},
|
||||
{
|
||||
// Make Discord think we are connected to a voice channel so it shows us inside it
|
||||
// Make Discord show inside the channel if clicking on a hidden or locked channel
|
||||
match: /(?<=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\);!__OVERLAY__&&\()/,
|
||||
replace: (_, channel) => `$self.isHiddenChannel(${channel})||`
|
||||
replace: (_, channel) => `$self.isHiddenChannel(${channel},true)||`
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -244,14 +246,54 @@ export default definePlugin({
|
||||
find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE",
|
||||
replacement: [
|
||||
{
|
||||
// Export the channel beggining header
|
||||
// Export the channel beginning header
|
||||
match: /computePermissionsForRoles.+?}\)}(?<=function (\i)\(.+?)(?=var)/,
|
||||
replace: (m, component) => `${m}$self.setChannelBeginHeaderComponent(${component});`
|
||||
},
|
||||
{
|
||||
// Patch the header to only return allowed users and roles if it's a hidden channel (Like when it's used on the HiddenChannelLockScreen)
|
||||
// Change the role permission check to CONNECT if the channel is locked
|
||||
match: /ADMINISTRATOR\)\|\|(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
|
||||
replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):`
|
||||
},
|
||||
{
|
||||
// Change the permissionOverwrite check to CONNECT if the channel is locked
|
||||
match: /permissionOverwrites\[.+?\i=(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
|
||||
replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):`
|
||||
},
|
||||
{
|
||||
// Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen)
|
||||
match: /MANAGE_ROLES.{0,60}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?]}\)))/,
|
||||
replace: (m, component, channel) => `${m} $self.isHiddenChannel(${channel})?${component}:`
|
||||
replace: (m, component, channel) => {
|
||||
// Export the channel for the users allowed component patch
|
||||
component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,channel:${channel}`);
|
||||
|
||||
return `${m} $self.isHiddenChannel(${channel},true)?${component}:`;
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "().avatars),children",
|
||||
replacement: [
|
||||
{
|
||||
// Create a variable for the channel prop
|
||||
match: /=(\i)\.maxUsers,/,
|
||||
replace: (m, props) => `${m}channel=${props}.channel,`
|
||||
},
|
||||
{
|
||||
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
|
||||
match: /\i>0(?=&&.{0,60}renderPopout)/,
|
||||
replace: m => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)?true:${m})`
|
||||
},
|
||||
{
|
||||
// Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
|
||||
match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/,
|
||||
replace: (_, amount) => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?0:1)`
|
||||
},
|
||||
{
|
||||
// Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
|
||||
match: /(?<="\+",)(\i)\+1/,
|
||||
replace: (m, amount) => `$self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?"":${m}`
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -261,22 +303,22 @@ export default definePlugin({
|
||||
{
|
||||
// Remove the divider and the open chat button for the HiddenChannelLockScreen
|
||||
match: /"more-options-popout"\)\);if\((?<=function \i\((\i)\).+?)/,
|
||||
replace: (m, props) => `${m}(!$self.isHiddenChannel(${props}.channel)||${props}.inCall)&&`
|
||||
replace: (m, props) => `${m}!${props}.inCall&&$self.isHiddenChannel(${props}.channel,true)){}else if(`
|
||||
},
|
||||
{
|
||||
// Render our HiddenChannelLockScreen component instead of the main voice channel component
|
||||
match: /this\.renderVoiceChannelEffects.+?children:(?<=renderContent=function.+?)/,
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?$self.HiddenChannelLockScreen(this.props.channel):"
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?$self.HiddenChannelLockScreen(this.props.channel):"
|
||||
},
|
||||
{
|
||||
// Disable gradients for the HiddenChannelLockScreen of voice channels
|
||||
match: /this\.renderVoiceChannelEffects.+?disableGradients:(?<=renderContent=function.+?)/,
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)||"
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)||"
|
||||
},
|
||||
{
|
||||
// Disable useless components for the HiddenChannelLockScreen of voice channels
|
||||
match: /(?:{|,)render(?!Header|ExternalHeader).{0,30}?:(?<=renderContent=function.+?)(?!void)/g,
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?null:"
|
||||
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?null:"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -321,9 +363,7 @@ export default definePlugin({
|
||||
],
|
||||
},
|
||||
{
|
||||
// The module wasn't being found, so lets just escape everything
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
find: "\^https\:\/\/\(\?\:canary\.\|ptb\.\)\?discord.com\/channels\/\(\\\\\d\+\|",
|
||||
find: "\"^/guild-stages/(\\\\d+)(?:/)?(\\\\d+)?\"",
|
||||
replacement: {
|
||||
// Make mentions of hidden channels work
|
||||
match: /\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,\i\)/,
|
||||
@ -337,19 +377,62 @@ export default definePlugin({
|
||||
match: /(?<=getChannel\((\i)\)\)(?=.{0,100}?selectVoiceChannel))/,
|
||||
replace: (_, channelId) => `&&!$self.isHiddenChannel({channelId:${channelId}})`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '.displayName="GuildChannelStore"',
|
||||
replacement: [
|
||||
{
|
||||
// Make GuildChannelStore contain hidden channels
|
||||
match: /isChannelGated\(.+?\)(?=\|\|)/,
|
||||
replace: m => `${m}||true`
|
||||
},
|
||||
{
|
||||
// Filter hidden channels from GuildChannelStore.getChannels unless told otherwise
|
||||
match: /(?<=getChannels=function\(\i)\).+?(?=return (\i)})/,
|
||||
replace: (rest, channels) => `,shouldIncludeHidden=false${rest}${channels}=$self.resolveGuildChannels(${channels},shouldIncludeHidden);`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".Messages.FORM_LABEL_MUTED",
|
||||
replacement: {
|
||||
// Make GuildChannelStore.getChannels return hidden channels
|
||||
match: /(?<=getChannels\(\i)(?=\))/,
|
||||
replace: ",true"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
setEmojiComponent,
|
||||
setChannelBeginHeaderComponent,
|
||||
|
||||
isHiddenChannel(channel: Channel & { channelId?: string; }) {
|
||||
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
||||
if (!channel) return false;
|
||||
|
||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||
|
||||
return !PermissionStore.can(VIEW_CHANNEL, channel);
|
||||
return !PermissionStore.can(VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(CONNECT, channel);
|
||||
},
|
||||
|
||||
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
||||
if (shouldIncludeHidden) return channels;
|
||||
|
||||
const res = {};
|
||||
for (const [key, maybeObjChannels] of Object.entries(channels)) {
|
||||
if (!Array.isArray(maybeObjChannels)) {
|
||||
res[key] = maybeObjChannels;
|
||||
continue;
|
||||
}
|
||||
|
||||
res[key] ??= [];
|
||||
|
||||
for (const objChannel of maybeObjChannels) {
|
||||
if (objChannel.channel.id === null || !this.isHiddenChannel(objChannel.channel)) res[key].push(objChannel);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />,
|
||||
|
@ -103,4 +103,5 @@
|
||||
.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] {
|
||||
margin-left: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -40,28 +40,30 @@ function SilentMessageToggle() {
|
||||
return (
|
||||
<Tooltip text="Toggle Silent Message">
|
||||
{tooltipProps => (
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={() => setEnabled(prev => !prev)}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ margin: "0px 8px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="currentColor">
|
||||
<path d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4C13 3.69264 13.0198 3.3899 13.0582 3.093C12.7147 3.03189 12.3611 3 12 3C8.686 3 6 5.686 6 9V14C6 15.657 4.656 17 3 17V18H21V17C19.344 17 18 15.657 18 14V10.7101ZM8.55493 19C9.24793 20.19 10.5239 21 11.9999 21C13.4759 21 14.7519 20.19 15.4449 19H8.55493Z" />
|
||||
<path d="M18.2624 5.50209L21 2.5V1H16.0349V2.49791H18.476L16 5.61088V7H21V5.50209H18.2624Z" />
|
||||
{!enabled && <line x1="22" y1="2" x2="2" y2="22" stroke="var(--red-500)" stroke-width="2.5" />}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={() => setEnabled(prev => !prev)}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ margin: "0px 8px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="currentColor">
|
||||
<path d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4C13 3.69264 13.0198 3.3899 13.0582 3.093C12.7147 3.03189 12.3611 3 12 3C8.686 3 6 5.686 6 9V14C6 15.657 4.656 17 3 17V18H21V17C19.344 17 18 15.657 18 14V10.7101ZM8.55493 19C9.24793 20.19 10.5239 21 11.9999 21C13.4759 21 14.7519 20.19 15.4449 19H8.55493Z" />
|
||||
<path d="M18.2624 5.50209L21 2.5V1H16.0349V2.49791H18.476L16 5.61088V7H21V5.50209H18.2624Z" />
|
||||
{!enabled && <line x1="22" y1="2" x2="2" y2="22" stroke="var(--red-500)" stroke-width="2.5" />}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
@ -75,8 +77,8 @@ export default definePlugin({
|
||||
{
|
||||
find: ".activeCommandOption",
|
||||
replacement: {
|
||||
match: /"gift"\)\);(?<=(\i)\.push.+?)/,
|
||||
replace: (m, array) => `${m}${array}.push($self.SilentMessageToggle());`
|
||||
match: /"gift"\)\);(?<=(\i)\.push.+?disabled:(\i),.+?)/,
|
||||
replace: (m, array, disabled) => `${m}${disabled}||${array}.push($self.SilentMessageToggle());`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -82,8 +82,8 @@ export default definePlugin({
|
||||
find: ".activeCommandOption",
|
||||
predicate: () => settings.store.showIcon,
|
||||
replacement: {
|
||||
match: /(.)\.push.{1,50}\(\i,\{.{1,30}\},"gift"\)\)/,
|
||||
replace: "$&;try{$1.push($self.chatBarIcon())}catch{}",
|
||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
|
||||
import { LazyComponent } from "@utils/misc";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { find, findLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildMemberStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { ChannelStore, GuildMemberStore, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { buildSeveralUsers } from "./typingTweaks";
|
||||
|
||||
@ -57,9 +57,9 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
|
||||
if (isChannelMuted) return null;
|
||||
}
|
||||
|
||||
delete typingUsers[UserStore.getCurrentUser().id];
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
const typingUsersArray = Object.keys(typingUsers);
|
||||
const typingUsersArray = Object.keys(typingUsers).filter(id => id !== myId && !(RelationshipStore.isBlocked(id) && !settings.store.includeBlockedUsers));
|
||||
let tooltipText: string;
|
||||
|
||||
switch (typingUsersArray.length) {
|
||||
@ -108,13 +108,18 @@ const settings = definePluginSettings({
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether to show the typing indicator for muted channels.",
|
||||
default: false
|
||||
},
|
||||
includeBlockedUsers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether to show the typing indicator for blocked users.",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "TypingIndicator",
|
||||
description: "Adds an indicator if someone is typing on a channel.",
|
||||
authors: [Devs.Nuckyz],
|
||||
authors: [Devs.Nuckyz, Devs.obscurity],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
|
@ -56,8 +56,8 @@ export default definePlugin({
|
||||
find: "AudioContextSettingsMigrated",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=updateAsync\("audioContextSettings".{0,50})(?=return (\i)\.volume=(\i))/,
|
||||
replace: (_, volumeOptions, newVolume) => `if(${newVolume}>200)return ${volumeOptions}.volume=200;`
|
||||
match: /(?<=updateAsync\("audioContextSettings".{0,350}return \i\.volume=)\i(?=})/,
|
||||
replace: "$&>200?200:$&"
|
||||
},
|
||||
{
|
||||
match: /(?<=Object\.entries\(\i\.localMutes\).+?volume:).+?(?=,)/,
|
||||
@ -74,10 +74,10 @@ export default definePlugin({
|
||||
find: '.displayName="MediaEngineStore"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/,
|
||||
replace: (_, localVolume, syncVolume, rest) => ""
|
||||
match: /(\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/,
|
||||
replace: (_, rest1, localVolume, syncVolume, rest2) => rest1
|
||||
+ `(${localVolume}>200?void 0:${localVolume}=${syncVolume})`
|
||||
+ rest
|
||||
+ rest2
|
||||
+ `${localVolume}??${syncVolume})`
|
||||
}
|
||||
]
|
||||
|
@ -16,9 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PatchReplacement } from "./types";
|
||||
|
||||
export type ReplaceFn = (match: string, ...groups: string[]) => string;
|
||||
import { PatchReplacement, ReplaceFn } from "./types";
|
||||
|
||||
export function canonicalizeMatch(match: RegExp | string) {
|
||||
if (typeof match === "string") return match;
|
||||
|
@ -19,13 +19,13 @@
|
||||
import { Command } from "@api/Commands";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
import type { ReplaceFn } from "./patches";
|
||||
|
||||
// exists to export default definePlugin({...})
|
||||
export default function definePlugin<P extends PluginDef>(p: P & Record<string, any>) {
|
||||
return p;
|
||||
}
|
||||
|
||||
export type ReplaceFn = (match: string, ...groups: string[]) => string;
|
||||
|
||||
export interface PatchReplacement {
|
||||
match: string | RegExp;
|
||||
replace: string | ReplaceFn;
|
||||
|
Reference in New Issue
Block a user