Compare commits

...

54 Commits

Author SHA1 Message Date
V
16365d3ea1 bump to 1.2.2 2023-05-13 18:48:40 +02:00
V
1ec28a345b EmoteCloner: Add Sticker cloning (#1118) 2023-05-13 18:47:46 +02:00
V
2fdc00b11e BadgesAPI: Fix canary crash (new pomelo badge, the horror) 2023-05-12 23:34:30 +02:00
V
3da112680d oops, add missing else 2023-05-12 04:17:12 +02:00
V
1d93162036 MessageClickActions: Add double click to reply 2023-05-12 04:15:35 +02:00
V
7dcd32e838 PlatformIndicators: Fix weird spacing in badges 2023-05-12 03:54:57 +02:00
V
ade31f993b Implement plugin tags 2023-05-12 03:41:15 +02:00
AutumnVN
3c7496ac6d TextReplace: Visible Linebreak in settings (#1063)
Co-authored-by: V <vendicated@riseup.net>
2023-05-12 02:59:51 +02:00
Supertiger
63387a48ee silentMessageToggle: Add setting to disable auto disable (#1062)
Co-authored-by: V <vendicated@riseup.net>
2023-05-12 00:58:53 +00:00
V
3bb68467bb Update README.md 2023-05-12 02:40:31 +02:00
V
2b337eace1 [skip ci] ShowHiddenChannel: Fix broken patch 2023-05-12 02:30:51 +02:00
V
5c5b009c41 Settings: Fix resetting scroll/search when getting a ping (#1106) 2023-05-12 01:40:43 +02:00
Nuckyz
0c54b1fa1d [skip ci] Fix InvisibleChat button being added to wrong chat box (#1100) 2023-05-11 17:44:33 +00:00
Kode
393f76749a USRBG: Hide Nitro badge if banner's source is USRBG (#1096)
* Hide Nitro badge if banner's source is USRBG, tweaks to description

* Rename function

* Update src/plugins/usrbg/index.tsx

---------

Co-authored-by: V <vendicated@riseup.net>
2023-05-11 19:33:08 +02:00
V
1fe7f3c297 ViewIcons: More consistent context menu position 2023-05-11 19:26:55 +02:00
V
622e8dc3e0 [skip ci] Translate: Shift/Right click shortcut 2023-05-11 19:00:18 +02:00
V
a8b6aea26f bump to 1.2.1 2023-05-11 02:39:59 +02:00
V
e50c2fafa5 Settings > Plugins: Hide API plugins 2023-05-11 02:38:59 +02:00
V
6e3cafce42 Translate: Only add button in chat box 2023-05-11 02:35:06 +02:00
V
4d0a064425 WebContextMenus: Fix slate menu 2023-05-11 02:35:05 +02:00
V
d1ad6c47a7 Fix ServerListAPI 2023-05-11 02:35:05 +02:00
V
d5c35055f3 experiments: Add a warning card to experiments page (#1097) 2023-05-11 02:34:39 +02:00
V
cb385d1b28 New Plugin: Translate (#1089)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-05-10 23:14:04 +02:00
V
195f1a032f ShowConnections: Add verified & copy/link icons in tooltip (#1092) 2023-05-10 23:13:47 +02:00
V
dfda9e7f84 New plugin: ValidUser (#1081) 2023-05-10 23:13:28 +02:00
Vendicated
0d5e2d0696 [skip ci] Refactor utils 2023-05-06 01:36:00 +02:00
Vendicated
2834bed518 MemberCount: Do not count offline group lol 2023-05-06 01:06:48 +02:00
Vendicated
0b2c3c834a MemberCount: Fix not properly updating on channel switch 2023-05-05 18:41:04 +02:00
AutumnVN
3a54a24c70 VencordToolbox: Change img icon to svg (#1059)
Co-authored-by: V <vendicated@riseup.net>
2023-05-05 16:41:01 +00:00
Vendicated
244d10dc9e [skip ci] Fix handleComponentFailed spam 2023-05-05 16:57:58 +02:00
Đỗ Văn Hoài Tuân
c25bc0ff4b USRBG: Add VoiceBackground (#1038)
Co-authored-by: V <vendicated@riseup.net>
2023-05-05 02:54:33 +02:00
Kode
22334663cf TextReplace: Test rules section (#1039) 2023-05-05 02:51:01 +02:00
Đỗ Văn Hoài Tuân
8813f81bde ShowConnections: Remove "(click to copy/open)" from tooltip (#1041) 2023-05-05 02:47:59 +02:00
Đỗ Văn Hoài Tuân
84371ed456 TextReplace: Fix Linebreak (#1057) 2023-05-05 02:47:31 +02:00
Nuckyz
8f61119b99 ShowAllMessageButtons plugin (#1029) 2023-05-05 02:47:26 +02:00
JSEverything
0a89d09727 style(Settings): Make the text about raw theme links bold (#1052) 2023-05-05 02:47:18 +02:00
Nuckyz
474932161f Fix FakeNitro Stickers (#1048) 2023-05-05 02:47:08 +02:00
Vendicated
bd95a25f4c TextReplace: Do not apply rules in #textreplace-rules channel 2023-05-02 04:39:27 +02:00
Vendicated
6a57ecc22b Delete FxTwitter & NoCanaryMessageLinks: Use TextReplace 2023-05-02 04:37:16 +02:00
Vendicated
0d665b7e0b Badge duplication glitch was too good to be true 2023-05-02 03:11:40 +02:00
Vendicated
d94b28fb8e Fix VencordToolbox 2023-05-02 02:57:25 +02:00
V
bc1d8694d4 new plugin: VencordToolbox (#998) 2023-05-02 02:55:38 +02:00
V
7bc1362cbd MessageEvents: Support sendMessage with attachments (#1023) 2023-05-02 02:55:21 +02:00
Vendicated
4dce836ff7 RelationshipNotifier: Fix false positives when using AccountSwitcher 2023-05-02 02:52:09 +02:00
Vendicated
9f534c0685 SupportHelper: Add OpenAsar info 2023-05-02 02:52:03 +02:00
V
c62d05e1b3 Refactor ipc to be strongly typed and hide impl details (#1018) 2023-05-02 02:50:51 +02:00
Nuckyz
6a1cb133cd Fix blank errors on plugin reports (#1021) 2023-05-01 07:26:27 +00:00
Vendicated
c6196dff81 [skip ci] PlatformIndicator: Fix 1px misalignment 2023-04-30 21:47:33 +02:00
Vendicated
acdb390302 [skip ci] Bump deps 2023-04-30 17:11:38 +02:00
Nuckyz
08d88b326d Fix Plugin Reporting false dependency errors and other stuff (#1017) 2023-04-30 13:34:38 +00:00
LordElias
a73858d131 UserVoiceShow: Fix lack of bottom padding for popout on current user and broken modal patch (#873) 2023-04-30 10:29:45 -03:00
Đỗ Văn Hoài Tuân
b0caa6f4db feat(plugin): TextReplace (#994)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-30 01:19:08 +00:00
Vendicated
168d4b4cd9 PlatformIndicators: Fix layout reflows - The trilogy 2023-04-30 02:07:57 +02:00
Vendicated
06cee75a56 PlatformIndicators: Fix layout reflows 2: Electric Boogaloo 2023-04-30 01:56:12 +02:00
160 changed files with 3111 additions and 1338 deletions

View File

@ -62,7 +62,7 @@
"indent": ["error", 4, { "SwitchCase": 1 }], "indent": ["error", 4, { "SwitchCase": 1 }],
"arrow-parens": ["error", "as-needed"], "arrow-parens": ["error", "as-needed"],
"eol-last": ["error", "always"], "eol-last": ["error", "always"],
"func-call-spacing": ["error", "never"], "@typescript-eslint/func-call-spacing": ["error", "never"],
"no-multi-spaces": "error", "no-multi-spaces": "error",
"no-trailing-spaces": "error", "no-trailing-spaces": "error",
"no-whitespace-before-property": "error", "no-whitespace-before-property": "error",

View File

@ -26,5 +26,8 @@ jobs:
- name: Lint & Test if desktop version compiles - name: Lint & Test if desktop version compiles
run: pnpm test run: pnpm test
- name: Lint & Test if web version compiles - name: Test if web version compiles
run: pnpm testWeb run: pnpm buildWeb
- name: Test if plugin structure is valid
run: pnpm generatePluginJson

View File

@ -10,7 +10,7 @@ The cutest Discord client mod
- Super easy to install (Download Installer, open, click install button, done) - Super easy to install (Download Installer, open, click install button, done)
- 100+ plugins built in: [See a list](https://vencord.dev/plugins) - 100+ plugins built in: [See a list](https://vencord.dev/plugins)
- Some highlights: SpotifyControls, GameActivityToggle, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB - Some highlights: SpotifyControls, MessageLogger, Experiments, GameActivityToggle, Translate, NoTrack, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
- Fairly lightweight despite the many inbuilt plugins - Fairly lightweight despite the many inbuilt plugins
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript - Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!) - Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)

View File

@ -16,51 +16,70 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "../src/api/DataStore";
import IpcEvents from "../src/utils/IpcEvents"; import { debounce } from "../src/utils";
import { getTheme, Theme } from "../src/utils/discord";
// Discord deletes this so need to store in variable // Discord deletes this so need to store in variable
const { localStorage } = window; const { localStorage } = window;
// listeners for ipc.on // listeners for ipc.on
const listeners = {} as Record<string, Set<Function>>; const cssListeners = new Set<(css: string) => void>();
const NOOP = () => { };
const NOOP_ASYNC = async () => { };
const handlers = { const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css));
[IpcEvents.GET_REPO]: () => "https://github.com/Vendicated/Vencord", // shrug
[IpcEvents.GET_SETTINGS_DIR]: () => "LocalStorage",
[IpcEvents.GET_QUICK_CSS]: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
[IpcEvents.SET_QUICK_CSS]: (css: string) => {
DataStore.set("VencordQuickCss", css);
listeners[IpcEvents.QUICK_CSS_UPDATE]?.forEach(l => l(null, css));
},
[IpcEvents.GET_SETTINGS]: () => localStorage.getItem("VencordSettings") || "{}",
[IpcEvents.SET_SETTINGS]: (s: string) => localStorage.setItem("VencordSettings", s),
[IpcEvents.GET_UPDATES]: () => ({ ok: true, value: [] }),
[IpcEvents.OPEN_EXTERNAL]: (url: string) => open(url, "_blank"),
};
function onEvent(event: string, ...args: any[]) {
const handler = handlers[event];
if (!handler) throw new Error(`Event ${event} not implemented.`);
return handler(...args);
}
// probably should make this less cursed at some point // probably should make this less cursed at some point
window.VencordNative = { window.VencordNative = {
getVersions: () => ({}), native: {
ipc: { getVersions: () => ({}),
send: (event: string, ...args: any[]) => void onEvent(event, ...args), openExternal: async (url) => void open(url, "_blank")
sendSync: onEvent,
on(event: string, listener: () => {}) {
(listeners[event] ??= new Set()).add(listener);
},
off(event: string, listener: () => {}) {
return listeners[event]?.delete(listener);
},
invoke: (event: string, ...args: any[]) => Promise.resolve(onEvent(event, ...args))
}, },
updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Vendicated/Vencord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
},
quickCss: {
get: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
set: async (css: string) => {
await DataStore.set("VencordQuickCss", css);
cssListeners.forEach(l => l(css));
},
addChangeListener(cb) {
cssListeners.add(cb);
},
openFile: NOOP_ASYNC,
async openEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () =>
getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
},
},
settings: {
get: () => localStorage.getItem("VencordSettings") || "{}",
set: async (s: string) => localStorage.setItem("VencordSettings", s),
getSettingsDir: async () => "LocalStorage"
}
}; };

View File

@ -26,10 +26,6 @@ export default definePlugin({
name: "Your Name", 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: [], patches: [],
// Delete these two below if you are only using code patches // Delete these two below if you are only using code patches
start() {}, start() {},

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.2.0", "version": "1.2.2",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -32,20 +32,20 @@
}, },
"dependencies": { "dependencies": {
"@vap/core": "0.0.12", "@vap/core": "0.0.12",
"@vap/shiki": "0.10.3", "@vap/shiki": "0.10.5",
"fflate": "^0.7.4", "fflate": "^0.7.4",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/diff": "^5.0.2", "@types/diff": "^5.0.3",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.194",
"@types/node": "^18.11.18", "@types/node": "^18.16.3",
"@types/react": "^18.0.27", "@types/react": "^18.2.0",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.2.1",
"@types/yazl": "^2.4.2", "@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.49.0", "@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.49.0", "@typescript-eslint/parser": "^5.59.1",
"diff": "^5.1.0", "diff": "^5.1.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
@ -53,17 +53,17 @@
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-header": "^3.1.1", "eslint-plugin-header": "^3.1.1",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^8.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0", "highlight.js": "10.6.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"puppeteer-core": "^19.6.0", "puppeteer-core": "^19.11.1",
"standalone-electron-types": "^1.0.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^14.16.1", "stylelint": "^15.6.0",
"stylelint-config-standard": "^29.0.0", "stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.6", "tsx": "^3.12.7",
"type-fest": "^3.5.3", "type-fest": "^3.9.0",
"typescript": "^4.9.4" "typescript": "^5.0.4"
}, },
"packageManager": "pnpm@8.1.1", "packageManager": "pnpm@8.1.1",
"pnpm": { "pnpm": {

734
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,7 @@ interface Dev {
interface PluginData { interface PluginData {
name: string; name: string;
description: string; description: string;
tags: string[];
authors: Dev[]; authors: Dev[];
dependencies: string[]; dependencies: string[];
hasPatches: boolean; hasPatches: boolean;
@ -106,6 +107,7 @@ async function parseFile(fileName: string) {
hasCommands: false, hasCommands: false,
enabledByDefault: false, enabledByDefault: false,
required: false, required: false,
tags: [] as string[]
} as PluginData; } as PluginData;
for (const prop of pluginObj.properties) { for (const prop of pluginObj.properties) {
@ -131,6 +133,13 @@ async function parseFile(fileName: string) {
return devs[getName(e)!]; return devs[getName(e)!];
}); });
break; break;
case "tags":
if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal");
data.tags = value.elements.map(e => {
if (!isStringLiteral(e)) throw fail("tags array contains non-string literals");
return e.text;
});
break;
case "dependencies": case "dependencies":
if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal"); if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal");
const { elements } = value; const { elements } = value;

View File

@ -130,7 +130,7 @@ async function printReport() {
}, },
{ {
title: "Discord Errors", title: "Discord Errors",
description: toCodeBlock(report.otherErrors.join("\n")), description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00 color: report.otherErrors.length ? 0xff0000 : 0x00ff00
} }
] ]
@ -194,9 +194,10 @@ page.on("console", async e => {
return a.toString(); return a.toString();
} }
}) })
).then(a => a.join(" ")); ).then(a => a.join(" ").trim());
if (!text.startsWith("Failed to load resource: the server responded with a status of")) {
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of")) {
console.error("Got unexpected error", text); console.error("Got unexpected error", text);
report.otherErrors.push(text); report.otherErrors.push(text);
} }
@ -233,7 +234,7 @@ function runTime(token: string) {
// Needs native server to run // Needs native server to run
if (p.name === "WebRichPresence (arRPC)") return; if (p.name === "WebRichPresence (arRPC)") return;
p.required = true; Vencord.Settings.plugins[p.name].enabled = true;
p.patches?.forEach(patch => { p.patches?.forEach(patch => {
patch.plugin = p.name; patch.plugin = p.name;
delete patch.predicate; delete patch.predicate;

View File

@ -28,17 +28,15 @@ import "./utils/quickCss";
import "./webpack/patchWebpack"; import "./webpack/patchWebpack";
import { showNotification } from "./api/Notifications"; import { showNotification } from "./api/Notifications";
import { PlainSettings, Settings } from "./api/settings"; import { PlainSettings, Settings } from "./api/Settings";
import { patches, PMLogger, startAllPlugins } from "./plugins"; import { patches, PMLogger, startAllPlugins } from "./plugins";
import { localStorage } from "./utils/localStorage"; import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native"; import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync"; import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack"; import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common"; import { SettingsRouter } from "./webpack/common";
export let Components: any;
async function syncSettings() { async function syncSettings() {
if ( if (
Settings.cloud.settingsSync && // if it's enabled Settings.cloud.settingsSync && // if it's enabled
@ -65,7 +63,6 @@ async function syncSettings() {
async function init() { async function init() {
await onceReady; await onceReady;
startAllPlugins(); startAllPlugins();
Components = await import("./components");
syncSettings(); syncSettings();
@ -76,7 +73,6 @@ async function init() {
if (Settings.autoUpdate) { if (Settings.autoUpdate) {
await update(); await update();
await rebuild();
if (Settings.autoUpdateNotification) if (Settings.autoUpdateNotification)
setTimeout(() => showNotification({ setTimeout(() => showNotification({
title: "Vencord has been updated!", title: "Vencord has been updated!",

View File

@ -16,34 +16,46 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import IPC_EVENTS from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { IpcRenderer, ipcRenderer } from "electron"; import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron";
function assertEventAllowed(event: string) { function invoke<T = any>(event: IpcEvents, ...args: any[]) {
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`); return ipcRenderer.invoke(event, ...args) as Promise<T>;
} }
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.sendSync(event, ...args) as T;
}
export default { export default {
getVersions: () => process.versions, updater: {
ipc: { getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES),
send(event: string, ...args: any[]) { update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE),
assertEventAllowed(event); rebuild: () => invoke<IpcRes<boolean>>(IpcEvents.BUILD),
ipcRenderer.send(event, ...args); getRepo: () => invoke<IpcRes<string>>(IpcEvents.GET_REPO),
},
settings: {
get: () => sendSync<string>(IpcEvents.GET_SETTINGS),
set: (settings: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings),
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
},
quickCss: {
get: () => invoke<string>(IpcEvents.GET_QUICK_CSS),
set: (css: string) => invoke<void>(IpcEvents.SET_QUICK_CSS, css),
addChangeListener(cb: (newCss: string) => void) {
ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css));
}, },
sendSync<T = any>(event: string, ...args: any[]): T {
assertEventAllowed(event); openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),
return ipcRenderer.sendSync(event, ...args); openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR),
}, },
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
assertEventAllowed(event); native: {
ipcRenderer.on(event, listener); getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
}, openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
off(event: string, listener: Parameters<IpcRenderer["off"]>[1]) { },
assertEventAllowed(event);
ipcRenderer.off(event, listener);
},
invoke<T = any>(event: string, ...args: any[]): Promise<T> {
assertEventAllowed(event);
return ipcRenderer.invoke(event, ...args);
}
}
}; };

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { makeCodeblock } from "@utils/misc"; import { makeCodeblock } from "@utils/text";
import { sendBotMessage } from "./commandHelpers"; import { sendBotMessage } from "./commandHelpers";
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types"; import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import type { ReactElement } from "react"; import type { ReactElement } from "react";
type ContextMenuPatchCallbackReturn = (() => void) | void; type ContextMenuPatchCallbackReturn = (() => void) | void;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { MessageStore } from "@webpack/common"; import { MessageStore } from "@webpack/common";
import type { Channel, Message } from "discord-types/general"; import type { Channel, Message } from "discord-types/general";
import type { Promisable } from "type-fest"; import type { Promisable } from "type-fest";
@ -36,10 +36,50 @@ export interface Emoji {
export interface MessageObject { export interface MessageObject {
content: string, content: string,
validNonShortcutEmojis: Emoji[]; validNonShortcutEmojis: Emoji[];
invalidEmojis: any[];
tts: boolean;
}
export interface Upload {
classification: string;
currentSize: number;
description: string | null;
filename: string;
id: string;
isImage: boolean;
isVideo: boolean;
item: {
file: File;
platform: number;
};
loaded: number;
mimeType: string;
preCompressionSize: number;
responseUrl: string;
sensitive: boolean;
showLargeMessageDialog: boolean;
spoiler: boolean;
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
uniqueId: string;
uploadedFilename: string;
}
export interface MessageReplyOptions {
messageReference: Message["messageReference"];
allowedMentions?: {
parse: Array<string>;
repliedUser: boolean;
};
} }
export interface MessageExtra { export interface MessageExtra {
stickerIds?: string[]; stickers?: string[];
uploads?: Upload[];
replyOptions: MessageReplyOptions;
content: string;
channel: Channel;
type?: any;
openWarningPopout: (props: any) => any;
} }
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
@ -48,7 +88,8 @@ export type EditListener = (channelId: string, messageId: string, messageObj: Me
const sendListeners = new Set<SendListener>(); const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>(); const editListeners = new Set<EditListener>();
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra) { export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
extra.replyOptions = replyOptions;
for (const listener of sendListeners) { for (const listener of sendListeners) {
try { try {
const result = await listener(channelId, messageObj, extra); const result = await listener(channelId, messageObj, extra);

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
import type { MouseEventHandler } from "react"; import type { MouseEventHandler } from "react";

View File

@ -18,7 +18,7 @@
import "./styles.css"; import "./styles.css";
import { useSettings } from "@api/settings"; import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common"; import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { ReactDOM } from "@webpack/common"; import { ReactDOM } from "@webpack/common";
import type { ReactNode } from "react"; import type { ReactNode } from "react";

View File

@ -17,10 +17,10 @@
*/ */
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { useAwaiter } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react"; import type { DispatchWithoutAction } from "react";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
const logger = new Logger("ServerListAPI"); const logger = new Logger("ServerListAPI");

View File

@ -17,9 +17,8 @@
*/ */
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents";
import { localStorage } from "@utils/localStorage"; import { localStorage } from "@utils/localStorage";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/misc"; import { mergeDefaults } from "@utils/misc";
import { putCloudSettings } from "@utils/settingsSync"; import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
@ -94,7 +93,7 @@ const DefaultSettings: Settings = {
}; };
try { try {
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings; var settings = JSON.parse(VencordNative.settings.get()) as Settings;
mergeDefaults(settings, DefaultSettings); mergeDefaults(settings, DefaultSettings);
} catch (err) { } catch (err) {
var settings = mergeDefaults({} as Settings, DefaultSettings); var settings = mergeDefaults({} as Settings, DefaultSettings);
@ -173,7 +172,7 @@ function makeProxy(settings: any, root = settings, path = ""): Settings {
PlainSettings.cloud.settingsSyncVersion = Date.now(); PlainSettings.cloud.settingsSyncVersion = Date.now();
localStorage.Vencord_settingsDirty = true; localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction(); saveSettingsOnFrequentAction();
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4)); VencordNative.settings.set(JSON.stringify(root, null, 4));
return true; return true;
} }
}); });
@ -249,10 +248,7 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
logger.info(`Migrating settings from old name ${oldName} to ${name}`); logger.info(`Migrating settings from old name ${oldName} to ${name}`);
plugins[name] = plugins[oldName]; plugins[name] = plugins[oldName];
delete plugins[oldName]; delete plugins[oldName];
VencordNative.ipc.invoke( VencordNative.settings.set(JSON.stringify(settings, null, 4));
IpcEvents.SET_SETTINGS,
JSON.stringify(settings, null, 4)
);
break; break;
} }
} }

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { proxyLazy } from "@utils/lazy";
import { proxyLazy } from "@utils/proxyLazy"; import { Logger } from "@utils/Logger";
import { findModuleId, wreq } from "@webpack"; import { findModuleId, wreq } from "@webpack";
import { Settings } from "./settings"; import { Settings } from "./Settings";
interface Setting<T> { interface Setting<T> {
/** /**

View File

@ -28,6 +28,7 @@ import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices"; import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications"; import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList"; import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings";
import * as $SettingsStore from "./SettingsStore"; import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles"; import * as $Styles from "./Styles";
@ -86,6 +87,10 @@ export const MessageDecorations = $MessageDecorations;
* An API allowing you to add components to member list users, in both DM's and servers * An API allowing you to add components to member list users, in both DM's and servers
*/ */
export const MemberListDecorators = $MemberListDecorators; export const MemberListDecorators = $MemberListDecorators;
/**
* An API allowing you to persist data
*/
export const Settings = $Settings;
/** /**
* An API allowing you to read, manipulate and automatically update components based on Discord settings * An API allowing you to read, manipulate and automatically update components based on Discord settings
*/ */

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import IpcEvents from "@utils/IpcEvents";
import { Button } from "@webpack/common"; import { Button } from "@webpack/common";
import { Heart } from "./Heart"; import { Heart } from "./Heart";
@ -27,9 +26,7 @@ export default function DonateButton(props: any) {
{...props} {...props}
look={Button.Looks.LINK} look={Button.Looks.LINK}
color={Button.Colors.TRANSPARENT} color={Button.Colors.TRANSPARENT}
onClick={() => onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated")
}
> >
<Heart /> <Heart />
Donate Donate

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { LazyComponent } from "@utils/misc"; import { LazyComponent } from "@utils/react";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { ErrorCard } from "./ErrorCard"; import { ErrorCard } from "./ErrorCard";

83
src/components/Icons.tsx Normal file
View File

@ -0,0 +1,83 @@
/*
* 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 { classes } from "@utils/misc";
import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
}
interface IconProps {
className?: string;
height?: number;
width?: number;
}
function Icon({ height = 24, width = 24, className, children, viewBox }: PropsWithChildren<BaseIconProps>) {
return (
<svg
className={classes(className, "vc-icon")}
aria-hidden="true"
role="img"
width={width}
height={height}
viewBox={viewBox}
>
{children}
</svg>
);
}
/**
* Discord's link icon, as seen in the Message context menu "Copy Message Link" option
*/
export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
return (
<Icon
height={height}
width={width}
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} />
</g>
</Icon>
);
}
/**
* Discord's copy icon, as seen in the user popout right of the username when clicking
* your own username in the bottom left user panel
*/
export function CopyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-copy-icon")}
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" />
</g>
</Icon>
);
}

View File

@ -1,51 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 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 { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents";
import { Queue } from "@utils/Queue";
import { find } from "@webpack";
import monacoHtml from "~fileContent/monacoWin.html";
const queue = new Queue();
const setCss = debounce((css: string) => {
queue.push(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css));
});
export async function launchMonacoEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCss;
win.getCurrentCss = () => VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
win.getTheme = () =>
find(m =>
m.ProtoClass?.typeName.endsWith("PreloadedUserSettings")
)?.getCurrentValue()?.appearance?.theme === 2
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
window.__VENCORD_MONACO_WIN__ = new WeakRef(win);
}

View File

@ -17,13 +17,14 @@
*/ */
import { generateId } from "@api/Commands"; import { generateId } from "@api/Commands";
import { useSettings } from "@api/settings"; import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, LazyComponent } from "@utils/misc"; import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { proxyLazy } from "@utils/proxyLazy"; import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types"; import { OptionType, Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByCode, findByPropsLazy } from "@webpack";
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
@ -128,6 +129,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>; return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} else { } else {
const options = Object.entries(plugin.options).map(([key, setting]) => { const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.hidden) return null;
function onChange(newValue: any) { function onChange(newValue: any) {
setTempSettings(s => ({ ...s, [key]: newValue })); setTempSettings(s => ({ ...s, [key]: newValue }));
} }

View File

@ -20,19 +20,19 @@ import "./styles.css";
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices"; import { showNotice } from "@api/Notices";
import { useSettings } from "@api/settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { Badge } from "@components/PluginSettings/components"; import { Badge } from "@components/PluginSettings/components";
import PluginModal from "@components/PluginSettings/PluginModal"; import PluginModal from "@components/PluginSettings/PluginModal";
import { Switch } from "@components/Switch"; import { Switch } from "@components/Switch";
import { SettingsTab } from "@components/VencordSettings/shared";
import { ChangeList } from "@utils/ChangeList"; import { ChangeList } from "@utils/ChangeList";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, LazyComponent, useAwaiter } from "@utils/misc"; import { classes } from "@utils/misc";
import { openModalLazy } from "@utils/modal"; import { openModalLazy } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByCode, findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
@ -94,7 +94,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
} }
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = useSettings([`plugins.${plugin.name}.enabled`]).plugins[plugin.name]; const settings = Settings.plugins[plugin.name];
const isEnabled = () => settings.enabled ?? false; const isEnabled = () => settings.enabled ?? false;
@ -125,7 +125,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
} }
// if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes. // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.
if (plugin.patches) { if (plugin.patches?.length) {
settings.enabled = !wasEnabled; settings.enabled = !wasEnabled;
onRestartNeeded(plugin.name); onRestartNeeded(plugin.name);
return; return;
@ -177,7 +177,7 @@ enum SearchStatus {
DISABLED DISABLED
} }
export default ErrorBoundary.wrap(function PluginSettings() { export default function PluginSettings() {
const settings = useSettings(); const settings = useSettings();
const changes = React.useMemo(() => new ChangeList<string>(), []); const changes = React.useMemo(() => new ChangeList<string>(), []);
@ -228,9 +228,12 @@ export default ErrorBoundary.wrap(function PluginSettings() {
if (enabled && searchValue.status === SearchStatus.DISABLED) return false; if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
if (!searchValue.value.length) return true; if (!searchValue.value.length) return true;
const v = searchValue.value.toLowerCase();
return ( return (
plugin.name.toLowerCase().includes(searchValue.value.toLowerCase()) || plugin.name.toLowerCase().includes(v) ||
plugin.description.toLowerCase().includes(searchValue.value.toLowerCase()) plugin.description.toLowerCase().includes(v) ||
plugin.tags?.some(t => t.toLowerCase().includes(v))
); );
}; };
@ -258,6 +261,9 @@ export default ErrorBoundary.wrap(function PluginSettings() {
requiredPlugins = []; requiredPlugins = [];
for (const p of sortedPlugins) { for (const p of sortedPlugins) {
if (!p.options && p.name.endsWith("API") && searchValue.value !== "API")
continue;
if (!pluginFilter(p)) continue; if (!pluginFilter(p)) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled); const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
@ -298,7 +304,7 @@ export default ErrorBoundary.wrap(function PluginSettings() {
} }
return ( return (
<Forms.FormSection className={Margins.top16}> <SettingsTab title="Plugins">
<ReloadRequiredCard required={changes.hasChanges} /> <ReloadRequiredCard required={changes.hasChanges} />
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}> <Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
@ -337,12 +343,9 @@ export default ErrorBoundary.wrap(function PluginSettings() {
<div className={cl("grid")}> <div className={cl("grid")}>
{requiredPlugins} {requiredPlugins}
</div> </div>
</Forms.FormSection > </SettingsTab >
); );
}, { }
message: "Failed to render the Plugin Settings. If this persists, try using the installer to reinstall!",
onError: handleComponentFailed,
});
function makeDependencyList(deps: string[]) { function makeDependencyList(deps: string[]) {
return ( return (

View File

@ -16,16 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync"; import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
import { Button, Card, Forms, Text } from "@webpack/common"; import { Button, Card, Text } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
function BackupRestoreTab() { function BackupRestoreTab() {
return ( return (
<Forms.FormSection title="Settings Sync" className={Margins.top16}> <SettingsTab title="Backup & Restore">
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}> <Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
<Flex flexDirection="column"> <Flex flexDirection="column">
<strong>Warning</strong> <strong>Warning</strong>
@ -59,8 +60,8 @@ function BackupRestoreTab() {
Export Settings Export Settings
</Button> </Button>
</Flex> </Flex>
</Forms.FormSection> </SettingsTab>
); );
} }
export default ErrorBoundary.wrap(BackupRestoreTab); export default wrapTab(BackupRestoreTab, "Backup & Restore");

View File

@ -17,15 +17,16 @@
*/ */
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { Settings, useSettings } from "@api/settings"; import { Settings, useSettings } from "@api/Settings";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
function validateUrl(url: string) { function validateUrl(url: string) {
try { try {
new URL(url); new URL(url);
@ -114,7 +115,7 @@ function CloudTab() {
const settings = useSettings(["cloud.authenticated", "cloud.url"]); const settings = useSettings(["cloud.authenticated", "cloud.url"]);
return ( return (
<> <SettingsTab title="Vencord Cloud">
<Forms.FormSection title="Cloud Settings" className={Margins.top16}> <Forms.FormSection title="Cloud Settings" className={Margins.top16}>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}> <Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
Vencord comes with a cloud integration that adds goodies like settings sync across devices. Vencord comes with a cloud integration that adds goodies like settings sync across devices.
@ -157,8 +158,8 @@ function CloudTab() {
<Forms.FormDivider className={Margins.top16} /> <Forms.FormDivider className={Margins.top16} />
</Forms.FormSection > </Forms.FormSection >
<SettingsSyncSection /> <SettingsSyncSection />
</> </SettingsTab>
); );
} }
export default ErrorBoundary.wrap(CloudTab); export default wrapTab(CloudTab, "Cloud");

View File

@ -16,16 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { CheckedTextInput } from "@components/CheckedTextInput";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { makeCodeblock } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import { makeCodeblock } from "@utils/text";
import { ReplaceFn } from "@utils/types"; import { ReplaceFn } from "@utils/types";
import { search } from "@webpack"; import { search } from "@webpack";
import { Button, Clipboard, Forms, Parser, React, Switch, Text, TextInput } from "@webpack/common"; import { Button, Clipboard, Forms, Parser, React, Switch, TextInput } from "@webpack/common";
import { CheckedTextInput } from "./CheckedTextInput"; import { SettingsTab, wrapTab } from "./shared";
import ErrorBoundary from "./ErrorBoundary";
// Do not include diff in non dev builds (side effects import) // Do not include diff in non dev builds (side effects import)
if (IS_DEV) { if (IS_DEV) {
@ -258,8 +258,7 @@ function PatchHelper() {
} }
return ( return (
<Forms.FormSection> <SettingsTab title="Patch Helper">
<Text variant="heading-md/normal" tag="h2" className={Margins.bottom8}>Patch Helper</Text>
<Forms.FormTitle>find</Forms.FormTitle> <Forms.FormTitle>find</Forms.FormTitle>
<TextInput <TextInput
type="text" type="text"
@ -304,8 +303,8 @@ function PatchHelper() {
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button> <Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
</> </>
)} )}
</Forms.FormSection> </SettingsTab>
); );
} }
export default IS_DEV ? ErrorBoundary.wrap(PatchHelper) : null; export default IS_DEV ? wrapTab(PatchHelper, "PatchHelper") : null;

View File

@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import PluginSettings from "@components/PluginSettings"; import PluginSettings from "@components/PluginSettings";
export default ErrorBoundary.wrap(PluginSettings); import { wrapTab } from "./shared";
export default wrapTab(PluginSettings, "Plugins");

View File

@ -16,14 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { useSettings } from "@api/settings"; import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { useAwaiter } from "@utils/misc"; import { useAwaiter } from "@utils/react";
import { findLazy } from "@webpack"; import { findLazy } from "@webpack";
import { Card, Forms, React, TextArea } from "@webpack/common"; import { Card, Forms, React, TextArea } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const TextAreaProps = findLazy(m => typeof m.textarea === "string");
function Validator({ link }: { link: string; }) { function Validator({ link }: { link: string; }) {
@ -74,8 +75,8 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
); );
} }
export default ErrorBoundary.wrap(function () { function ThemesTab() {
const settings = useSettings(); const settings = useSettings(["themeLinks"]);
const [themeText, setThemeText] = React.useState(settings.themeLinks.join("\n")); const [themeText, setThemeText] = React.useState(settings.themeLinks.join("\n"));
function onBlur() { function onBlur() {
@ -89,11 +90,11 @@ export default ErrorBoundary.wrap(function () {
} }
return ( return (
<> <SettingsTab title="Themes">
<Card className="vc-settings-card vc-text-selectable"> <Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">Paste links to .theme.css files here</Forms.FormTitle> <Forms.FormTitle tag="h5">Paste links to .theme.css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText> <Forms.FormText><strong>Make sure to use the raw links or github.io links!</strong></Forms.FormText>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> <Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle> <Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
<div style={{ marginBottom: ".5em" }}> <div style={{ marginBottom: ".5em" }}>
@ -124,6 +125,8 @@ export default ErrorBoundary.wrap(function () {
onBlur={onBlur} onBlur={onBlur}
/> />
<Validators themeLinks={settings.themeLinks} /> <Validators themeLinks={settings.themeLinks} />
</> </SettingsTab>
); );
}); }
export default wrapTab(ThemesTab, "Themes");

View File

@ -16,20 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { useSettings } from "@api/settings"; import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, useAwaiter } from "@utils/misc"; import { classes } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater"; import { useAwaiter } from "@utils/react";
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common"; import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import { SettingsTab, wrapTab } from "./shared";
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) { function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
return async () => { return async () => {
dispatcher(true); dispatcher(true);
@ -125,7 +126,6 @@ function Updatable(props: CommonProps) {
onClick={withDispatcher(setIsUpdating, async () => { onClick={withDispatcher(setIsUpdating, async () => {
if (await update()) { if (await update()) {
setUpdates([]); setUpdates([]);
await rebuild();
await new Promise<void>(r => { await new Promise<void>(r => {
Alerts.show({ Alerts.show({
title: "Update Success!", title: "Update Success!",
@ -198,7 +198,7 @@ function Updater() {
}; };
return ( return (
<Forms.FormSection className={Margins.top16}> <SettingsTab title="Vencord Updater">
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
<Switch <Switch
value={settings.notifyAboutUpdates} value={settings.notifyAboutUpdates}
@ -245,11 +245,8 @@ function Updater() {
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle> <Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
{isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />} {isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
</Forms.FormSection > </SettingsTab>
); );
} }
export default IS_WEB ? null : ErrorBoundary.wrap(Updater, { export default IS_WEB ? null : wrapTab(Updater, "Updater");
message: "Failed to render the Updater. If this persists, try using the installer to reinstall!",
onError: handleComponentFailed,
});

View File

@ -18,17 +18,18 @@
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import IpcEvents from "@utils/IpcEvents";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity, useAwaiter } from "@utils/misc"; import { identity } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native"; import { relaunch, showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common"; import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
const cl = classNameFactory("vc-settings-"); const cl = classNameFactory("vc-settings-");
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
@ -39,7 +40,7 @@ type KeysOfType<Object, Type> = {
}[keyof Object]; }[keyof Object];
function VencordSettings() { function VencordSettings() {
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), { const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
fallbackValue: "Loading..." fallbackValue: "Loading..."
}); });
const settings = useSettings(); const settings = useSettings();
@ -97,44 +98,39 @@ function VencordSettings() {
]; ];
return ( return (
<React.Fragment> <SettingsTab title="Vencord Settings">
<DonateCard image={donateImage} /> <DonateCard image={donateImage} />
<Forms.FormSection title="Quick Actions"> <Forms.FormSection title="Quick Actions">
<Card className={cl("quick-actions-card")}> <Card className={cl("quick-actions-card")}>
{IS_WEB ? ( <React.Fragment>
<Button {!IS_WEB && (
onClick={() => require("../Monaco").launchMonacoEditor()}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
Open QuickCSS File
</Button>
) : (
<React.Fragment>
<Button <Button
onClick={relaunch} onClick={relaunch}
size={Button.Sizes.SMALL}> size={Button.Sizes.SMALL}>
Restart Client Restart Client
</Button> </Button>
<Button )}
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_MONACO_EDITOR)} <Button
size={Button.Sizes.SMALL} onClick={() => VencordNative.quickCss.openEditor()}
disabled={settingsDir === "Loading..."}> size={Button.Sizes.SMALL}
Open QuickCSS File disabled={settingsDir === "Loading..."}>
</Button> Open QuickCSS File
</Button>
{!IS_WEB && (
<Button <Button
onClick={() => showItemInFolder(settingsDir)} onClick={() => showItemInFolder(settingsDir)}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={settingsDirPending}> disabled={settingsDirPending}>
Open Settings Folder Open Settings Folder
</Button> </Button>
<Button )}
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/Vendicated/Vencord")} <Button
size={Button.Sizes.SMALL} onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
disabled={settingsDirPending}> size={Button.Sizes.SMALL}
Open in GitHub disabled={settingsDirPending}>
</Button> Open in GitHub
</React.Fragment> </Button>
)} </React.Fragment>
</Card> </Card>
</Forms.FormSection> </Forms.FormSection>
@ -158,7 +154,7 @@ function VencordSettings() {
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />} {typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />}
</React.Fragment> </SettingsTab>
); );
} }
@ -268,4 +264,4 @@ function DonateCard({ image }: DonateCardProps) {
); );
} }
export default ErrorBoundary.wrap(VencordSettings); export default wrapTab(VencordSettings, "Vencord Settings");

View File

@ -1,93 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 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 "./settingsStyles.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { isMobile } from "@utils/misc";
import { Forms, SettingsRouter, TabBar, Text } from "@webpack/common";
import BackupRestoreTab from "./BackupRestoreTab";
import CloudTab from "./CloudTab";
import PluginsTab from "./PluginsTab";
import ThemesTab from "./ThemesTab";
import Updater from "./Updater";
import VencordSettings from "./VencordTab";
const cl = classNameFactory("vc-settings-");
interface SettingsProps {
tab: string;
}
interface SettingsTab {
name: string;
component?: React.ComponentType;
}
const SettingsTabs: Record<string, SettingsTab> = {
VencordSettings: { name: "Vencord", component: () => <VencordSettings /> },
VencordPlugins: { name: "Plugins", component: () => <PluginsTab /> },
VencordThemes: { name: "Themes", component: () => <ThemesTab /> },
VencordUpdater: { name: "Updater" }, // Only show updater if IS_WEB is false
VencordCloud: { name: "Cloud", component: () => <CloudTab /> },
VencordSettingsSync: { name: "Backup & Restore", component: () => <BackupRestoreTab /> }
};
if (!IS_WEB) SettingsTabs.VencordUpdater.component = () => Updater && <Updater />;
function Settings(props: SettingsProps) {
const { tab = "VencordSettings" } = props;
const CurrentTab = SettingsTabs[tab]?.component ?? null;
if (isMobile) {
return CurrentTab && <CurrentTab />;
}
return <Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">Vencord Settings</Text>
<TabBar
type="top"
look="brand"
className={cl("tab-bar")}
selectedItem={tab}
onItemSelect={SettingsRouter.open}
>
{Object.entries(SettingsTabs).map(([key, { name, component }]) => {
if (!component) return null;
return <TabBar.Item
id={key}
className={cl("tab-bar-item")}
key={key}>
{name}
</TabBar.Item>;
})}
</TabBar>
<Forms.FormDivider />
{CurrentTab && <CurrentTab />}
</Forms.FormSection >;
}
export default function (props: SettingsProps) {
return <ErrorBoundary onError={handleComponentFailed}>
<Settings tab={props.tab} />
</ErrorBoundary>;
}

View File

@ -15,7 +15,7 @@
display: flex; display: flex;
gap: 1em; gap: 1em;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-evenly;
flex-grow: 1; flex-grow: 1;
flex-flow: row wrap; flex-flow: row wrap;
margin-bottom: 1em; margin-bottom: 1em;
@ -29,14 +29,12 @@
.vc-settings-card { .vc-settings-card {
padding: 1em; padding: 1em;
margin-bottom: 1em; margin-bottom: 1em;
margin-top: 1em;
} }
.vc-backup-restore-card { .vc-backup-restore-card {
background-color: var(--info-warning-background); background-color: var(--info-warning-background);
border-color: var(--info-warning-foreground); border-color: var(--info-warning-foreground);
color: var(--info-warning-text); color: var(--info-warning-text);
margin-top: 0;
} }
.vc-settings-theme-links { .vc-settings-theme-links {
@ -59,7 +57,7 @@
} }
.vc-text-selectable, .vc-text-selectable,
.vc-text-selectable :not(a, button) { .vc-text-selectable :not(a, button, a *, button *) {
/* make text selectable, silly discord makes the entirety of settings not selectable */ /* make text selectable, silly discord makes the entirety of settings not selectable */
user-select: text; user-select: text;

View File

@ -0,0 +1,51 @@
/*
* 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 "./settingsStyles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { Margins } from "@utils/margins";
import { onlyOnce } from "@utils/onlyOnce";
import { Forms, Text } from "@webpack/common";
import type { ComponentType, PropsWithChildren } from "react";
export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) {
return (
<Forms.FormSection>
<Text
variant="heading-lg/semibold"
tag="h2"
className={Margins.bottom16}
>
{title}
</Text>
{children}
</Forms.FormSection>
);
}
const onError = onlyOnce(handleComponentFailed);
export function wrapTab(component: ComponentType, tab: string) {
return ErrorBoundary.wrap(component, {
message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`,
onError,
});
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
if (IS_DEV) { if (IS_DEV) {
var traces = {} as Record<string, [number, any[]]>; var traces = {} as Record<string, [number, any[]]>;

View File

@ -19,7 +19,7 @@
import "./updater"; import "./updater";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell } from "electron"; import { BrowserWindow, ipcMain, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
@ -44,7 +44,7 @@ export function readSettings() {
} }
} }
export function getSettings(): typeof import("@api/settings").Settings { export function getSettings(): typeof import("@api/Settings").Settings {
try { try {
return JSON.parse(readSettings()); return JSON.parse(readSettings());
} catch { } catch {

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import IpcEvents from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { execFile as cpExecFile } from "child_process"; import { execFile as cpExecFile } from "child_process";
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { join } from "path"; import { join } from "path";

View File

@ -17,7 +17,7 @@
*/ */
import { VENCORD_USER_AGENT } from "@utils/constants"; import { VENCORD_USER_AGENT } from "@utils/constants";
import IpcEvents from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { writeFile } from "fs/promises"; import { writeFile } from "fs/promises";
import { join } from "path"; import { join } from "path";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -22,12 +22,11 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart"; import { Heart } from "@components/Heart";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import IpcEvents from "@utils/IpcEvents"; import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { closeModal, Modals, openModal } from "@utils/modal"; import { closeModal, Modals, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Forms } from "@webpack/common"; import { Forms, Toasts } from "@webpack/common";
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png";
@ -48,7 +47,29 @@ const ContributorBadge: ProfileBadge = {
link: "https://github.com/Vendicated/Vencord" link: "https://github.com/Vendicated/Vencord"
}; };
const DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">[]>; let DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">[]>;
async function loadBadges(noCache = false) {
DonorBadges = {};
const init = {} as RequestInit;
if (noCache)
init.cache = "no-cache";
const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv", init)
.then(r => r.text());
const lines = badges.trim().split("\n");
if (lines.shift() !== "id,tooltip,image") {
new Logger("BadgeAPI").error("Invalid badges.csv file!");
return;
}
for (const line of lines) {
const [id, description, image] = line.split(",");
(DonorBadges[id] ??= []).push({ image, description });
}
}
export default definePlugin({ export default definePlugin({
name: "BadgeAPI", name: "BadgeAPI",
@ -61,8 +82,8 @@ export default definePlugin({
find: "Messages.PROFILE_USER_BADGES,role:", find: "Messages.PROFILE_USER_BADGES,role:",
replacement: [ replacement: [
{ {
match: /null==\i\?void 0:(\i)\.getBadges\(\)/, match: /(?<=(\i)\.isTryItOutFlow,)(.{0,300})null==\i\?void 0:(\i)\.getBadges\(\)/,
replace: (_, badgesMod) => `Vencord.Api.Badges._getBadges(arguments[0]).concat(${badgesMod}?.getBadges()??[])`, replace: (_, props, restCode, badgesMod) => `vencordProps=${props},${restCode}Vencord.Api.Badges._getBadges(vencordProps).concat(${badgesMod}?.getBadges()??[])`,
}, },
{ {
// alt: "", aria-hidden: false, src: originalSrc // alt: "", aria-hidden: false, src: originalSrc
@ -82,24 +103,27 @@ export default definePlugin({
} }
], ],
toolboxActions: {
async "Refetch Badges"() {
await loadBadges(true);
Toasts.show({
id: Toasts.genId(),
message: "Successfully refetched badges!",
type: Toasts.Type.SUCCESS
});
}
},
async start() {
Vencord.Api.Badges.addBadge(ContributorBadge);
await loadBadges();
},
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => { renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
const Component = badge.component!; const Component = badge.component!;
return <Component {...badge} />; return <Component {...badge} />;
}, { noop: true }), }, { noop: true }),
async start() {
Vencord.Api.Badges.addBadge(ContributorBadge);
const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv").then(r => r.text());
const lines = badges.trim().split("\n");
if (lines.shift() !== "id,tooltip,image") {
new Logger("BadgeAPI").error("Invalid badges.csv file!");
return;
}
for (const line of lines) {
const [id, description, image] = line.split(",");
(DonorBadges[id] ??= []).push({ image, description });
}
},
getDonorBadges(userId: string) { getDonorBadges(userId: string) {
return DonorBadges[userId]?.map(badge => ({ return DonorBadges[userId]?.map(badge => ({
@ -115,7 +139,7 @@ export default definePlugin({
const modalKey = openModal(props => ( const modalKey = openModal(props => (
<ErrorBoundary noop onError={() => { <ErrorBoundary noop onError={() => {
closeModal(modalKey); closeModal(modalKey);
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated"); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}> }}>
<Modals.ModalRoot {...props}> <Modals.ModalRoot {...props}>
<Modals.ModalHeader> <Modals.ModalHeader>

View File

@ -22,17 +22,29 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "MessageEventsAPI", name: "MessageEventsAPI",
description: "Api required by anything using message events.", description: "Api required by anything using message events.",
authors: [Devs.Arjix, Devs.hunt], authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [ patches: [
{ {
find: '"MessageActionCreators"', find: '"MessageActionCreators"',
replacement: [{ replacement: {
match: /_sendMessage:(function\([^)]+\)){/, // editMessage: function (...) {
replace: "_sendMessage:async $1{if(await Vencord.Api.MessageEvents._handlePreSend(...arguments))return;" match: /\beditMessage:(function\(.+?\))\{/,
}, { // editMessage: async function (...) { await handlePreEdit(...); ...
match: /\beditMessage:(function\([^)]+\)){/,
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);" replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
}] }
},
{
find: ".handleSendMessage=",
replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(props\.chatInputType.+?\.then\(\()(function.+?var (\i)=\i\.\i\.parse\((\i),.+?var (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shoudClear:true,shouldRefocus:true};"
}
}, },
{ {
find: '("interactionUsernameProfile', find: '("interactionUsernameProfile',

View File

@ -32,7 +32,7 @@ export default definePlugin({
} }
}, },
{ {
find: "Messages.SERVERS", find: "Messages.SERVERS,children",
replacement: { replacement: {
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/, match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/,
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)" replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)"

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";

View File

@ -18,7 +18,7 @@
import "./betterFolders.css"; import "./betterFolders.css";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";

View File

@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { makeLazy } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
export default definePlugin({ export default definePlugin({
@ -30,7 +29,7 @@ export default definePlugin({
{ {
find: "hideNote:", find: "hideNote:",
all: true, all: true,
predicate: makeLazy(() => Vencord.Settings.plugins.BetterNotesBox.hide), predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
replacement: { replacement: {
match: /hideNote:.+?(?=[,}])/g, match: /hideNote:.+?(?=[,}])/g,
replace: "hideNote:true", replace: "hideNote:true",

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Clipboard, Toasts } from "@webpack/common"; import { Clipboard, Toasts } from "@webpack/common";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -17,9 +17,9 @@
*/ */
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { closeAllModals } from "@utils/modal"; import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater"; import { maybePromptToUpdate } from "@utils/updater";

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards"; import { isTruthy } from "@utils/guards";
import { useAwaiter } from "@utils/misc"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { import {

View File

@ -16,15 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findAll, search } from "@webpack"; import { filters, findAll, search } from "@webpack";
import { Menu } from "@webpack/common";
const PORT = 8485; const PORT = 8485;
const NAV_ID = "dev-companion-reconnect"; const NAV_ID = "dev-companion-reconnect";
@ -238,33 +236,25 @@ function initWs(isManual = false) {
}); });
} }
const contextMenuPatch: NavContextMenuPatchCallback = children => () => {
children.unshift(
<Menu.MenuItem
id={NAV_ID}
label="Reconnect Dev Companion"
action={() => {
socket?.close(1000, "Reconnecting");
initWs(true);
}}
/>
);
};
export default definePlugin({ export default definePlugin({
name: "DevCompanion", name: "DevCompanion",
description: "Dev Companion Plugin", description: "Dev Companion Plugin",
authors: [Devs.Ven], authors: [Devs.Ven],
settings, settings,
toolboxActions: {
"Reconnect"() {
socket?.close(1000, "Reconnecting");
initWs(true);
}
},
start() { start() {
initWs(); initWs();
addContextMenuPatch("user-settings-cog", contextMenuPatch);
}, },
stop() { stop() {
socket?.close(1000, "Plugin Stopped"); socket?.close(1000, "Plugin Stopped");
socket = void 0; socket = void 0;
removeContextMenuPatch("user-settings-cog", contextMenuPatch);
} }
}); });

View File

@ -19,19 +19,104 @@
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { Forms, GuildStore, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common"; import { FluxDispatcher, Forms, GuildStore, Menu, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest";
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n; const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
const GuildEmojiStore = findByPropsLazy("getGuilds", "getGuildEmoji"); const GuildEmojiStore = findStoreLazy("EmojiStore");
const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS("); const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
function getGuildCandidates(isAnimated: boolean) { interface Sticker {
t: "Sticker";
description: string;
format_type: number;
guild_id: string;
id: string;
name: string;
tags: string;
type: number;
}
interface Emoji {
t: "Emoji";
id: string;
name: string;
isAnimated: boolean;
}
type Data = Emoji | Sticker;
const StickerExt = [, "png", "png", "json", "gif"] as const;
function getUrl(data: Data) {
if (data.t === "Emoji")
return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.${data.isAnimated ? "gif" : "png"}`;
return `${location.origin}/stickers/${data.id}.${StickerExt[data.format_type]}`;
}
async function fetchSticker(id: string) {
const cached = StickersStore.getStickerById(id);
if (cached) return cached;
const { body } = await RestAPI.get({
url: `/stickers/${id}`
});
FluxDispatcher.dispatch({
type: "STICKER_FETCH_SUCCESS",
sticker: body
});
return body as Sticker;
}
async function cloneSticker(guildId: string, sticker: Sticker) {
const data = new FormData();
data.append("name", sticker.name);
data.append("tags", sticker.tags);
data.append("description", sticker.description);
data.append("file", await fetchBlob(getUrl(sticker)));
const { body } = await RestAPI.post({
url: `/guilds/${guildId}/stickers`,
body: data,
});
FluxDispatcher.dispatch({
type: "GUILD_STICKERS_CREATE_SUCCESS",
guildId,
sticker: {
...body,
user: UserStore.getCurrentUser()
}
});
}
async function cloneEmoji(guildId: string, emoji: Emoji) {
const data = await fetchBlob(getUrl(emoji));
const dataUrl = await new Promise<string>(resolve => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.readAsDataURL(data);
});
return uploadEmoji({
guildId,
name: emoji.name.split("~")[0],
image: dataUrl
});
}
function getGuildCandidates(data: Data) {
const meId = UserStore.getCurrentUser().id; const meId = UserStore.getCurrentUser().id;
return Object.values(GuildStore.getGuilds()).filter(g => { return Object.values(GuildStore.getGuilds()).filter(g => {
@ -39,6 +124,10 @@ function getGuildCandidates(isAnimated: boolean) {
BigInt(PermissionStore.getGuildPermissions({ id: g.id }) & MANAGE_EMOJIS_AND_STICKERS) === MANAGE_EMOJIS_AND_STICKERS; BigInt(PermissionStore.getGuildPermissions({ id: g.id }) & MANAGE_EMOJIS_AND_STICKERS) === MANAGE_EMOJIS_AND_STICKERS;
if (!canCreate) return false; if (!canCreate) return false;
if (data.t === "Sticker") return true;
const { isAnimated } = data as Emoji;
const emojiSlots = g.getMaxEmojiSlots(); const emojiSlots = g.getMaxEmojiSlots();
const { emojis } = GuildEmojiStore.getGuilds()[g.id]; const { emojis } = GuildEmojiStore.getGuilds()[g.id];
@ -49,33 +138,34 @@ function getGuildCandidates(isAnimated: boolean) {
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
} }
async function doClone(guildId: string, id: string, name: string, isAnimated: boolean) { async function fetchBlob(url: string) {
const data = await fetch(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`) const res = await fetch(url);
.then(r => r.blob()); if (!res.ok)
const reader = new FileReader(); throw new Error(`Failed to fetch ${url} - ${res.status}`);
reader.onload = () => { return res.blob();
uploadEmoji({ }
guildId,
name: name.split("~")[0], async function doClone(guildId: string, data: Sticker | Emoji) {
image: reader.result try {
}).then(() => { if (data.t === "Sticker")
Toasts.show({ await cloneSticker(guildId, data);
message: `Successfully cloned ${name}!`, else
type: Toasts.Type.SUCCESS, await cloneEmoji(guildId, data);
id: Toasts.genId()
}); Toasts.show({
}).catch((e: any) => { message: `Successfully cloned ${data.name} to ${GuildStore.getGuild(guildId)?.name ?? "your server"}!`,
new Logger("EmoteCloner").error("Failed to upload emoji", e); type: Toasts.Type.SUCCESS,
Toasts.show({ id: Toasts.genId()
message: "Oopsie something went wrong :( Check console!!!",
type: Toasts.Type.FAILURE,
id: Toasts.genId()
});
}); });
}; } catch (e) {
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
reader.readAsDataURL(data); Toasts.show({
message: "Oopsie something went wrong :( Check console!!!",
type: Toasts.Type.FAILURE,
id: Toasts.genId()
});
}
} }
const getFontSize = (s: string) => { const getFontSize = (s: string) => {
@ -86,20 +176,23 @@ const getFontSize = (s: string) => {
const nameValidator = /^\w+$/i; const nameValidator = /^\w+$/i;
function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: string; isAnimated: boolean; }) { function CloneModal({ data }: { data: Sticker | Emoji; }) {
const [isCloning, setIsCloning] = React.useState(false); const [isCloning, setIsCloning] = React.useState(false);
const [name, setName] = React.useState(emojiName); const [name, setName] = React.useState(data.name);
const [x, invalidateMemo] = React.useReducer(x => x + 1, 0); const [x, invalidateMemo] = React.useReducer(x => x + 1, 0);
const guilds = React.useMemo(() => getGuildCandidates(isAnimated), [isAnimated, x]); const guilds = React.useMemo(() => getGuildCandidates(data), [data.id, x]);
return ( return (
<> <>
<Forms.FormTitle className={Margins.top20}>Custom Name</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Custom Name</Forms.FormTitle>
<CheckedTextInput <CheckedTextInput
value={name} value={name}
onChange={setName} onChange={v => {
data.name = v;
setName(v);
}}
validate={v => validate={v =>
(v.length > 1 && v.length < 32 && nameValidator.test(v)) (v.length > 1 && v.length < 32 && nameValidator.test(v))
|| "Name must be between 2 and 32 characters and only contain alphanumeric characters" || "Name must be between 2 and 32 characters and only contain alphanumeric characters"
@ -135,7 +228,7 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
}} }}
onClick={isCloning ? void 0 : async () => { onClick={isCloning ? void 0 : async () => {
setIsCloning(true); setIsCloning(true);
doClone(g.id, id, name, isAnimated).finally(() => { doClone(g.id, data).finally(() => {
invalidateMemo(); invalidateMemo();
setIsCloning(false); setIsCloning(false);
}); });
@ -175,32 +268,38 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
); );
} }
function buildMenuItem(id: string, name: string, isAnimated: boolean) { function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Omit<Sticker | Emoji, "t">>) {
return ( return (
<Menu.MenuItem <Menu.MenuItem
id="emote-cloner" id="emote-cloner"
key="emote-cloner" key="emote-cloner"
label="Clone Emote" label={`Clone ${type}`}
action={() => action={() =>
openModal(modalProps => ( openModalLazy(async () => {
<ModalRoot {...modalProps}> const res = await fetchData();
<ModalHeader> const data = { t: type, ...res } as Sticker | Emoji;
<img const url = getUrl(data);
role="presentation"
aria-hidden return modalProps => (
src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`} <ModalRoot {...modalProps}>
alt="" <ModalHeader>
height={24} <img
width={24} role="presentation"
style={{ marginRight: "0.5em" }} aria-hidden
/> src={url}
<Forms.FormText>Clone {name}</Forms.FormText> alt=""
</ModalHeader> height={24}
<ModalContent> width={24}
<CloneModal id={id} name={name} isAnimated={isAnimated} /> style={{ marginRight: "0.5em" }}
</ModalContent> />
</ModalRoot> <Forms.FormText>Clone {data.name}</Forms.FormText>
)) </ModalHeader>
<ModalContent>
<CloneModal data={data} />
</ModalContent>
</ModalRoot>
);
})
} }
/> />
); );
@ -213,28 +312,53 @@ function isGifUrl(url: string) {
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {}; const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
if (!favoriteableId || favoriteableType !== "emoji") return; if (!favoriteableId) return;
const match = props.message.content.match(RegExp(`<a?:(\\w+)(?:~\\d+)?:${favoriteableId}>|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`)); const menuItem = (() => {
if (!match) return; switch (favoriteableType) {
const name = match[1] ?? "FakeNitroEmoji"; case "emoji":
const match = props.message.content.match(RegExp(`<a?:(\\w+)(?:~\\d+)?:${favoriteableId}>|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`));
if (!match) return;
const name = match[1] ?? "FakeNitroEmoji";
const group = findGroupChildrenByChildId("copy-link", children); return buildMenuItem("Emoji", () => ({
if (group) group.push(buildMenuItem(favoriteableId, name, isGifUrl(itemHref ?? itemSrc))); id: favoriteableId,
name,
isAnimated: isGifUrl(itemHref ?? itemSrc)
}));
case "sticker":
const sticker = props.message.stickerItems.find(s => s.id === favoriteableId);
if (sticker?.format_type === 3 /* LOTTIE */) return;
return buildMenuItem("Sticker", () => fetchSticker(favoriteableId));
}
})();
if (menuItem)
findGroupChildrenByChildId("copy-link", children)?.push(menuItem);
}; };
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => { const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => {
const { id, name, type } = props?.target?.dataset ?? {}; const { id, name, type } = props?.target?.dataset ?? {};
if (!id || !name || type !== "emoji") return; if (!id) return;
const firstChild = props.target.firstChild as HTMLImageElement; if (type === "emoji" && name) {
const firstChild = props.target.firstChild as HTMLImageElement;
children.push(buildMenuItem(id, name, firstChild && isGifUrl(firstChild.src))); children.push(buildMenuItem("Emoji", () => ({
id,
name,
isAnimated: firstChild && isGifUrl(firstChild.src)
})));
} else if (type === "sticker" && !props.target.className?.includes("lottieCanvas")) {
children.push(buildMenuItem("Sticker", () => fetchSticker(id)));
}
}; };
export default definePlugin({ export default definePlugin({
name: "EmoteCloner", name: "EmoteCloner",
description: "Adds a Clone context menu item to emotes to clone them your own server", description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
tags: ["StickerCloner"],
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz],
start() { start() {

View File

@ -16,8 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Forms, React } from "@webpack/common"; import { Forms, React } from "@webpack/common";
@ -87,6 +90,13 @@ export default definePlugin({
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/, match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/,
replace: "true" replace: "true"
} }
},
{
find: 'H1,title:"Experiments"',
replacement: {
match: 'title:"Experiments",children:[',
replace: "$&$self.WarningCard(),"
}
} }
], ],
@ -109,5 +119,19 @@ export default definePlugin({
</Forms.FormText> </Forms.FormText>
</React.Fragment> </React.Fragment>
); );
} },
WarningCard: ErrorBoundary.wrap(() => (
<ErrorCard id="vc-experiments-warning-card" className={Margins.bottom16}>
<Forms.FormTitle tag="h2">Hold on!!</Forms.FormTitle>
<Forms.FormText>
Experiments are unreleased Discord features. They might not work, or even break your client or get your account disabled.
</Forms.FormText>
<Forms.FormText className={Margins.top8}>
Only use experiments if you know what you're doing. Vencord is not responsible for any damage caused by enabling experiments.
</Forms.FormText>
</ErrorCard>
), { noop: true })
}); });

View File

@ -17,11 +17,11 @@
*/ */
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/proxyLazy"; import { proxyLazy } from "@utils/lazy";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
@ -641,7 +641,7 @@ export default definePlugin({
if (!settings.enableStickerBypass) if (!settings.enableStickerBypass)
break stickerBypass; break stickerBypass;
const sticker = StickerStore.getStickerById(extra?.stickerIds?.[0]!); const sticker = StickerStore.getStickerById(extra.stickers?.[0]!);
if (!sticker) if (!sticker)
break stickerBypass; break stickerBypass;
@ -663,7 +663,7 @@ export default definePlugin({
link = `https://distok.top/stickers/${packId}/${sticker.id}.gif`; link = `https://distok.top/stickers/${packId}/${sticker.id}.gif`;
} }
delete extra.stickerIds; extra.stickers!.length = 0;
messageObj.content += " " + link + `&name=${encodeURIComponent(sticker.name)}`; messageObj.content += " " + link + `&name=${encodeURIComponent(sticker.name)}`;
} }
} }

View File

@ -17,7 +17,7 @@
*/ */
// This plugin is a port from Alyxia's Vendetta plugin // This plugin is a port from Alyxia's Vendetta plugin
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";

View File

@ -17,7 +17,7 @@
*/ */
import { ApplicationCommandOptionType } from "@api/Commands"; import { ApplicationCommandOptionType } from "@api/Commands";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/misc"; import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { Tooltip } from "webpack/common"; import { Tooltip } from "webpack/common";

View File

@ -17,7 +17,7 @@
*/ */
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -130,6 +130,8 @@ export default definePlugin({
name: "ImageZoom", name: "ImageZoom",
description: "Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size", description: "Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size",
authors: [Devs.Aria], authors: [Devs.Aria],
tags: ["ImageUtilities"],
patches: [ patches: [
{ {
find: '"renderLinkComponent","maxWidth"', find: '"renderLinkComponent","maxWidth"',

View File

@ -17,8 +17,8 @@
*/ */
import { registerCommand, unregisterCommand } from "@api/Commands"; import { registerCommand, unregisterCommand } from "@api/Commands";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Patch, Plugin } from "@utils/types"; import { Patch, Plugin } from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
@ -99,9 +99,9 @@ export function startDependenciesRecursive(p: Plugin) {
if (!Settings.plugins[dep].enabled) { if (!Settings.plugins[dep].enabled) {
startDependenciesRecursive(Plugins[dep]); startDependenciesRecursive(Plugins[dep]);
// If the plugin has patches, don't start the plugin, just enable it. // If the plugin has patches, don't start the plugin, just enable it.
Settings.plugins[dep].enabled = true;
if (Plugins[dep].patches) { if (Plugins[dep].patches) {
logger.warn(`Enabling dependency ${dep} requires restart.`); logger.warn(`Enabling dependency ${dep} requires restart.`);
Settings.plugins[dep].enabled = true;
restartNeeded = true; restartNeeded = true;
return; return;
} }

View File

@ -17,7 +17,7 @@
*/ */
import { addButton, removeButton } from "@api/MessagePopover"; import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getStegCloak } from "@utils/dependencies"; import { getStegCloak } from "@utils/dependencies";
@ -64,7 +64,13 @@ function Indicator() {
} }
function ChatBarIcon() { function ChatBarIcon(chatBoxProps: {
type: {
analyticsName: string;
};
}) {
if (chatBoxProps.type.analyticsName !== "normal") return null;
return ( return (
<Tooltip text="Encrypt Message"> <Tooltip text="Encrypt Message">
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
@ -85,7 +91,7 @@ function ChatBarIcon() {
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
innerClassName={ButtonWrapperClasses.button} innerClassName={ButtonWrapperClasses.button}
onClick={() => buildEncModal()} onClick={() => buildEncModal()}
style={{ padding: "0 4px" }} style={{ padding: "0 2px", scale: "0.9" }}
> >
<div className={ButtonWrapperClasses.buttonWrapper}> <div className={ButtonWrapperClasses.buttonWrapper}>
<svg <svg
@ -133,7 +139,7 @@ export default definePlugin({
find: ".activeCommandOption", find: ".activeCommandOption",
replacement: { replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}", replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
} }
}, },
], ],

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms } from "@webpack/common"; import { FluxDispatcher, Forms } from "@webpack/common";

View File

@ -20,31 +20,34 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { useForceUpdater } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { Tooltip } from "@webpack/common"; import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
import { FluxStore } from "@webpack/types";
const counts = {} as Record<string, [number, number]>; const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
let forceUpdate: () => void; const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore"); };
function MemberCount() { function MemberCount() {
const guildId = getCurrentChannel().guild_id; const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const c = counts[guildId]; const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, channelId)
);
const total = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
forceUpdate = useForceUpdater(); if (total == null)
return null;
if (!c) return null; const online =
(groups.length === 1 && groups[0].id === "unknown")
let total = c[0].toLocaleString(); ? 0
if (total === "0" && c[1] > 0) { : groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
const approx = GuildMemberCountStore.getMemberCount(guildId);
total = approx ? approx.toLocaleString() : "Loading...";
}
const online = c[1].toLocaleString();
return ( return (
<Flex id="vc-membercount" style={{ <Flex id="vc-membercount" style={{
@ -55,7 +58,7 @@ function MemberCount() {
alignContent: "center", alignContent: "center",
gap: 0 gap: 0
}}> }}>
<Tooltip text={`${online} Online`} position="bottom"> <Tooltip text={`${online} Online in this Channel`} position="bottom">
{props => ( {props => (
<div {...props}> <div {...props}>
<span <span
@ -72,7 +75,7 @@ function MemberCount() {
</div> </div>
)} )}
</Tooltip> </Tooltip>
<Tooltip text={`${total} Total Members`} position="bottom"> <Tooltip text={`${total} Total Server Members`} position="bottom">
{props => ( {props => (
<div {...props}> <div {...props}>
<span <span
@ -102,31 +105,10 @@ export default definePlugin({
patches: [{ patches: [{
find: ".isSidebarVisible,", find: ".isSidebarVisible,",
replacement: { replacement: {
match: /(var (.)=.\.className.+?children):\[(.\.useMemo[^}]+"aria-multiselectable")/, match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: "$1:[$2.startsWith('members')?$self.render():null,$3" replace: "$1:[$2.startsWith('members')?$self.render():null,$3"
} }
}], }],
flux: { render: ErrorBoundary.wrap(MemberCount, { noop: true })
GUILD_MEMBER_LIST_UPDATE({ guildId, groups, memberCount, id }) {
// eeeeeh - sometimes it has really wrong counts??? like 10 times less than actual
// but if we only listen to everyone updates, sometimes we never get the count?
// this seems to work but isn't optional
if (id !== "everyone" && counts[guildId]) return;
let count = 0;
for (const group of groups) {
if (group.id !== "offline")
count += group.count;
}
counts[guildId] = [memberCount, count];
forceUpdate?.();
}
},
render: () => (
<ErrorBoundary noop>
<MemberCount />
</ErrorBoundary>
)
}); });

View File

@ -17,10 +17,11 @@
*/ */
import { addClickListener, removeClickListener } from "@api/MessageEvents"; import { addClickListener, removeClickListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { PermissionStore, UserStore } from "@webpack/common"; import { FluxDispatcher, PermissionStore, UserStore } from "@webpack/common";
let isDeletePressed = false; let isDeletePressed = false;
const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true); const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true);
@ -28,24 +29,36 @@ const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed =
const MANAGE_CHANNELS = 1n << 4n; const MANAGE_CHANNELS = 1n << 4n;
const settings = definePluginSettings({
enableDeleteOnClick: {
type: OptionType.BOOLEAN,
description: "Enable delete on click",
default: true
},
enableDoubleClickToEdit: {
type: OptionType.BOOLEAN,
description: "Enable double click to edit",
default: true
},
enableDoubleClickToReply: {
type: OptionType.BOOLEAN,
description: "Enable double click to reply",
default: true
},
requireModifier: {
type: OptionType.BOOLEAN,
description: "Only do double click actions when shift/ctrl is held",
default: false
}
});
export default definePlugin({ export default definePlugin({
name: "MessageClickActions", name: "MessageClickActions",
description: "Hold Backspace and click to delete, double click to edit", description: "Hold Backspace and click to delete, double click to edit/reply",
authors: [Devs.Ven], authors: [Devs.Ven],
dependencies: ["MessageEventsAPI"], dependencies: ["MessageEventsAPI"],
options: { settings,
enableDeleteOnClick: {
type: OptionType.BOOLEAN,
description: "Enable delete on click",
default: true
},
enableDoubleClickToEdit: {
type: OptionType.BOOLEAN,
description: "Enable double click to edit",
default: true
}
},
start() { start() {
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
@ -54,15 +67,30 @@ export default definePlugin({
document.addEventListener("keydown", keydown); document.addEventListener("keydown", keydown);
document.addEventListener("keyup", keyup); document.addEventListener("keyup", keyup);
this.onClick = addClickListener((msg, chan, event) => { this.onClick = addClickListener((msg, channel, event) => {
const isMe = msg.author.id === UserStore.getCurrentUser().id; const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) { if (!isDeletePressed) {
if (Vencord.Settings.plugins.MessageClickActions.enableDoubleClickToEdit && (isMe && event.detail >= 2 && !EditStore.isEditing(chan.id, msg.id))) { if (event.detail < 2) return;
MessageActions.startEditMessage(chan.id, msg.id, msg.content); if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
if (isMe) {
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault(); event.preventDefault();
} else {
if (!settings.store.enableDoubleClickToReply) return;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention: !Settings.plugins.NoReplyMention.enabled,
showMentionToggle: channel.guild_id !== null
});
} }
} else if (Vencord.Settings.plugins.MessageClickActions.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, chan))) { } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
MessageActions.deleteMessage(chan.id, msg.id); MessageActions.deleteMessage(channel.id, msg.id);
event.preventDefault(); event.preventDefault();
} }
}); });

View File

@ -17,11 +17,12 @@
*/ */
import { addAccessory } from "@api/MessageAccessories"; import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js"; import { Devs } from "@utils/constants.js";
import { classes, LazyComponent } from "@utils/misc"; import { classes } from "@utils/misc";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { LazyComponent } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { find, findByCode, findByPropsLazy } from "@webpack"; import { find, findByCode, findByPropsLazy } from "@webpack";
import { import {

View File

@ -19,11 +19,11 @@
import "./messageLogger.css"; import "./messageLogger.css";
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common"; import { FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";

View File

@ -18,7 +18,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/proxyLazy.js"; import { proxyLazy } from "@utils/lazy.js";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { find, findByPropsLazy } from "@webpack"; import { find, findByPropsLazy } from "@webpack";
import { ChannelStore, GuildStore } from "@webpack/common"; import { ChannelStore, GuildStore } from "@webpack/common";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { sleep } from "@utils/misc"; import { sleep } from "@utils/misc";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";

View File

@ -1,59 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Samu and Linnea Gräf
*
* 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 { addPreSendListener, MessageObject, removePreSendListener } from "@api/MessageEvents";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
export default definePlugin({
name: "NoCanaryMessageLinks",
description: "Allows you to change/remove the subdomain of discord message and channel links",
authors: [Devs.Samu, Devs.nea],
options: {
linkPrefix: {
description: "The subdomain for your discord message links",
type: OptionType.STRING,
default: "",
restartNeeded: false,
},
alwaysUseDiscordHost: {
description: "Always use discord.com host (replace discordapp.com)",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: false,
},
},
dependencies: ["MessageEventsAPI"],
removeBetas(msg: MessageObject) {
const settings = Settings.plugins.NoCanaryMessageLinks;
msg.content = msg.content.replace(
/https:\/\/(?:canary\.|ptb\.)?(discord(?:app)?\.com)(\/channels\/(?:\d{17,20}|@me)\/\d{17,20}(?:\/\d{17,20})?)/g,
(_, host, path) => "https://" + (settings.linkPrefix ? settings.linkPrefix + "." : "") + (settings.alwaysUseDiscordHost ? "discord.com" : host) + path
);
},
start() {
this.preSend = addPreSendListener((_, msg) => this.removeBetas(msg));
},
stop() {
removePreSendListener(this.preSend);
}
});

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";

View File

@ -19,7 +19,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getGifEncoder } from "@utils/dependencies"; import { getGifEncoder } from "@utils/dependencies";
import { makeLazy } from "@utils/misc"; import { makeLazy } from "@utils/lazy";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings, Settings, useSettings } from "@api/settings"; import { definePluginSettings, Settings, useSettings } from "@api/Settings";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";

View File

@ -19,7 +19,7 @@
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
import { addDecoration, removeDecoration } from "@api/MessageDecorations"; import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -66,7 +66,7 @@ const PlatformIcon = ({ platform, status }: { platform: Platform, status: string
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id]; const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
const PlatformIndicator = ({ user, inline = false, marginLeft = "4px" }: { user: User; inline?: boolean; marginLeft?: string; }) => { const PlatformIndicator = ({ user, wantMargin = true }: { user: User; wantMargin?: boolean; }) => {
if (!user || user.bot) return null; if (!user || user.bot) return null;
if (user.id === UserStore.getCurrentUser().id) { if (user.id === UserStore.getCurrentUser().id) {
@ -105,23 +105,27 @@ const PlatformIndicator = ({ user, inline = false, marginLeft = "4px" }: { user:
if (!icons.length) return null; if (!icons.length) return null;
return ( return (
<div <span
className="vc-platform-indicator" className="vc-platform-indicator"
style={{ style={{
marginLeft, display: "inline-flex",
gap: "4px", justifyContent: "center",
display: inline ? "inline-flex" : "flex", marginLeft: wantMargin ? 4 : 0,
alignItems: "center", verticalAlign: "top",
translate: inline ? "0 3px 0" : undefined position: "relative",
top: wantMargin ? 1 : 0,
padding: !wantMargin ? 2 : 0,
gap: 4
}} }}
> >
{icons} {icons}
</div> </span>
); );
}; };
const badge: ProfileBadge = { const badge: ProfileBadge = {
component: p => <PlatformIndicator {...p} marginLeft="" />, component: p => <PlatformIndicator {...p} wantMargin={false} />,
position: BadgePosition.START, position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length, shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
key: "indicator" key: "indicator"
@ -146,7 +150,7 @@ const indicatorLocations = {
description: "Inside messages", description: "Inside messages",
onEnable: () => addDecoration("platform-indicator", props => onEnable: () => addDecoration("platform-indicator", props =>
<ErrorBoundary noop> <ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} inline /> <PlatformIndicator user={props.message?.author} />
</ErrorBoundary> </ErrorBoundary>
), ),
onDisable: () => removeDecoration("platform-indicator") onDisable: () => removeDecoration("platform-indicator")

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { VENCORD_USER_AGENT } from "@utils/constants"; import { VENCORD_USER_AGENT } from "@utils/constants";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { useAwaiter } from "@utils/misc"; import { useAwaiter } from "@utils/react";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { settings } from "./settings"; import { settings } from "./settings";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
import { PronounsFormat } from "./pronoundbUtils"; import { PronounsFormat } from "./pronoundbUtils";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings, Settings } from "@api/settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
export default definePluginSettings({ export default definePluginSettings({

View File

@ -18,7 +18,7 @@
import { DataStore, Notices } from "@api/index"; import { DataStore, Notices } from "@api/index";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { ChannelStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
import settings from "./settings"; import settings from "./settings";
import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
@ -106,12 +106,16 @@ export function deleteGuild(id: string) {
} }
export async function syncGuilds() { export async function syncGuilds() {
guilds.clear();
const me = UserStore.getCurrentUser().id;
for (const [id, { name, icon }] of Object.entries(GuildStore.getGuilds())) { for (const [id, { name, icon }] of Object.entries(GuildStore.getGuilds())) {
guilds.set(id, { if (GuildMemberStore.isMember(id, me))
id, guilds.set(id, {
name, id,
iconURL: icon && `https://cdn.discordapp.com/icons/${id}/${icon}.png` name,
}); iconURL: icon && `https://cdn.discordapp.com/icons/${id}/${icon}.png`
});
} }
await DataStore.set(guildsKey(), guilds); await DataStore.set(guildsKey(), guilds);
} }
@ -126,6 +130,8 @@ export function deleteGroup(id: string) {
} }
export async function syncGroups() { export async function syncGroups() {
groups.clear();
for (const { type, id, name, rawRecipients, icon } of ChannelStore.getSortedPrivateChannels()) { for (const { type, id, name, rawRecipients, icon } of ChannelStore.getSortedPrivateChannels()) {
if (type === ChannelType.GROUP_DM) if (type === ChannelType.GROUP_DM)
groups.set(id, { groups.set(id, {

View File

@ -76,6 +76,8 @@ export default definePlugin({
name: "ReverseImageSearch", name: "ReverseImageSearch",
description: "Adds ImageSearch to image context menus", description: "Adds ImageSearch to image context menus",
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz],
tags: ["ImageUtilities"],
patches: [ patches: [
{ {
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { Review } from "../entities/Review"; import { Review } from "../entities/Review";
import { ReviewDBUser } from "../entities/User"; import { ReviewDBUser } from "../entities/User";

View File

@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { FluxDispatcher, React, SelectedChannelStore, Toasts, UserUtils } from "@webpack/common"; import { FluxDispatcher, React, SelectedChannelStore, Toasts, UserUtils } from "@webpack/common";

View File

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { classes, LazyComponent } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
export default LazyComponent(() => { export default LazyComponent(() => {
const { button, dangerous } = findByProps("button", "wrapper", "disabled","separator"); const { button, dangerous } = findByProps("button", "wrapper", "disabled", "separator");
return function MessageButton(props) { return function MessageButton(props) {
return props.type === "delete" return props.type === "delete"

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { classes, LazyComponent } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react";
import { filters, findBulk } from "@webpack"; import { filters, findBulk } from "@webpack";
import { Alerts, moment, Timestamp, UserStore } from "@webpack/common"; import { Alerts, moment, Timestamp, UserStore } from "@webpack/common";

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import { classes, useAwaiter } from "@utils/misc"; import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import { findLazy } from "@webpack"; import { findLazy } from "@webpack";
import { Forms, React, Text, UserStore } from "@webpack/common"; import { Forms, React, Text, UserStore } from "@webpack/common";
import type { KeyboardEvent } from "react"; import type { KeyboardEvent } from "react";

View File

@ -18,7 +18,7 @@
import "./style.css"; import "./style.css";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";

View File

@ -18,7 +18,7 @@
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/misc"; import { LazyComponent } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCode, findByCodeLazy } from "@webpack"; import { findByCode, findByCodeLazy } from "@webpack";
import { ChannelStore, i18n, Menu, SelectedChannelStore } from "@webpack/common"; import { ChannelStore, i18n, Menu, SelectedChannelStore } from "@webpack/common";

View File

@ -43,7 +43,7 @@
} }
.vc-st-button { .vc-st-button {
padding: 0 8px; padding: 0 6px;
} }
.vc-st-button svg { .vc-st-button svg {

View File

@ -17,10 +17,10 @@
*/ */
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList"; import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/misc"; import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common"; import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";

View File

@ -17,18 +17,14 @@
*/ */
import { addContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/settings"; import { Settings } from "@api/Settings";
import PatchHelper from "@components/PatchHelper";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { LazyComponent } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { SettingsRouter } from "@webpack/common"; import { React, SettingsRouter } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
const SettingsComponent = LazyComponent(() => require("../components/VencordSettings").default);
export default definePlugin({ export default definePlugin({
name: "Settings", name: "Settings",
description: "Adds Settings UI and debug info", description: "Adds Settings UI and debug info",
@ -95,37 +91,37 @@ export default definePlugin({
{ {
section: "VencordSettings", section: "VencordSettings",
label: "Vencord", label: "Vencord",
element: () => <SettingsComponent tab="VencordSettings" /> element: require("@components/VencordSettings/VencordTab").default
}, },
{ {
section: "VencordPlugins", section: "VencordPlugins",
label: "Plugins", label: "Plugins",
element: () => <SettingsComponent tab="VencordPlugins" />, element: require("@components/VencordSettings/PluginsTab").default,
}, },
{ {
section: "VencordThemes", section: "VencordThemes",
label: "Themes", label: "Themes",
element: () => <SettingsComponent tab="VencordThemes" />, element: require("@components/VencordSettings/ThemesTab").default,
}, },
!IS_WEB && { !IS_WEB && {
section: "VencordUpdater", section: "VencordUpdater",
label: "Updater", label: "Updater",
element: () => <SettingsComponent tab="VencordUpdater" />, element: require("@components/VencordSettings/UpdaterTab").default,
}, },
{ {
section: "VencordCloud", section: "VencordCloud",
label: "Cloud", label: "Cloud",
element: () => <SettingsComponent tab="VencordCloud" />, element: require("@components/VencordSettings/CloudTab").default,
}, },
{ {
section: "VencordSettingsSync", section: "VencordSettingsSync",
label: "Backup & Restore", label: "Backup & Restore",
element: () => <SettingsComponent tab="VencordSettingsSync" />, element: require("@components/VencordSettings/BackupAndRestoreTab").default,
}, },
IS_DEV && { IS_DEV && {
section: "VencordPatchHelper", section: "VencordPatchHelper",
label: "Patch Helper", label: "Patch Helper",
element: PatchHelper!, element: require("@components/VencordSettings/PatchHelperTab").default,
}, },
IS_VENCORD_DESKTOP && { IS_VENCORD_DESKTOP && {
section: "VencordDesktop", section: "VencordDesktop",
@ -155,12 +151,12 @@ export default definePlugin({
}, },
get electronVersion() { get electronVersion() {
return VencordNative.getVersions().electron || window.armcord?.electron || null; return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
}, },
get chromiumVersion() { get chromiumVersion() {
try { try {
return VencordNative.getVersions().chrome return VencordNative.native.getVersions().chrome
// @ts-ignore Typescript will add userAgentData IMMEDIATELY // @ts-ignore Typescript will add userAgentData IMMEDIATELY
|| navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version || navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version
|| null; || null;

View File

@ -17,8 +17,7 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { useAwaiter } from "@utils/misc"; import { useAwaiter, useIntersection } from "@utils/react";
import { useIntersection } from "@utils/react";
import { hljs, React } from "@webpack/common"; import { hljs, React } from "@webpack/common";
import { resolveLang } from "../api/languages"; import { resolveLang } from "../api/languages";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { parseUrl } from "@utils/misc"; import { parseUrl } from "@utils/misc";
import { wordsFromPascal, wordsToTitle } from "@utils/text"; import { wordsFromPascal, wordsToTitle } from "@utils/text";

View File

@ -61,10 +61,7 @@
display: flex; display: flex;
position: absolute; position: absolute;
justify-content: center; justify-content: center;
top: 0; inset: 0;
right: 0;
bottom: 0;
left: 0;
} }
.shiki-preview { .shiki-preview {

View File

@ -1,6 +1,6 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Samu * Copyright (c) 2023 Vendicated and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,27 +16,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { addPreSendListener, MessageObject, removePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
const re = /https?:\/\/twitter\.com(?=\/\w+?\/status\/)/g;
export default definePlugin({ export default definePlugin({
name: "FxTwitter", name: "ShowAllMessageButtons",
description: "Uses FxTwitter to improve embeds from twitter on send", description: "Always show all message buttons no matter if you are holding the shift key or not.",
authors: [Devs.Samu], authors: [Devs.Nuckyz],
dependencies: ["MessageEventsAPI"],
addPrefix(msg: MessageObject) { patches: [
msg.content = msg.content.replace(re, "https://fxtwitter.com"); {
}, find: ".Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: {
start() { // isExpanded: V, (?<=,V = shiftKeyDown && !H...;)
this.preSend = addPreSendListener((_, msg) => this.addPrefix(msg)); match: /isExpanded:(\i),(?<=,\1=\i&&(!.+);.+?)/,
}, replace: "isExpanded:$2,"
}
stop() { }
removePreSendListener(this.preSend); ]
}
}); });

Some files were not shown because too many files have changed in this diff Show More