+ {!updatesLoaded &&
+ "and possibly more, as update checking is not finished. "}
+ You can update plugins below.
+
+
+ )}
+
+
+
+ Install Plugin
+
+
+ You can install a plugin from GitHub, GitLab, Codeberg,
+ git.nin0.dev, or plugins.nin0.dev by pasting its clone URL
+ here.
+
+
+ >;
+}
diff --git a/src/equicordplugins/userpluginInstaller.dev/index.tsx b/src/equicordplugins/userpluginInstaller.dev/index.tsx
new file mode 100644
index 000000000..98e884661
--- /dev/null
+++ b/src/equicordplugins/userpluginInstaller.dev/index.tsx
@@ -0,0 +1,145 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 nin0
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./misc/style.css";
+
+import { showNotification } from "@api/Notifications";
+import { definePluginSettings } from "@api/Settings";
+import { Button } from "@components/Button";
+import { Notice } from "@components/Notice";
+import plSettings from "@plugins/_core/settings";
+import { Devs } from "@utils/constants";
+import { relaunch } from "@utils/native";
+import definePlugin, { OptionType, PluginNative } from "@utils/types";
+import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
+import { Alerts } from "@webpack/common";
+
+import SettingsTab from "./components/SettingsTab";
+import UserpluginInstallButton from "./components/UserpluginInstallButton";
+import { VariableWithCallbacks } from "./VariableWithCallbacks";
+
+// @ts-ignore
+export const Native = VencordNative.pluginHelpers.UserpluginInstaller as PluginNative;
+export const OpenSettingsModule = findByPropsLazy("openUserSettings");
+const AppsIcon = findComponentByCodeLazy("2.95H20a2 2 0");
+
+export const settings = definePluginSettings({
+ allowlistedChannels: {
+ type: OptionType.STRING,
+ description: "Comma separated list of channels where the Install Plugin button should be displayed. It is always displayed in the Vencord Userplugin channels"
+ },
+ notifyIfUpdate: {
+ type: OptionType.BOOLEAN,
+ description: "Show a Vencord notification if UserPlugins need to be updated",
+ default: true
+ },
+ neverNotifyForPlugins: {
+ type: OptionType.STRING,
+ description: "Never show update notifications for these plugins (you can still update them from the UserPlugins tab)",
+ default: ""
+ },
+ setGitPath: {
+ type: OptionType.COMPONENT,
+ component: () =>
+ }
+});
+
+export default definePlugin({
+ name: "UserpluginInstaller",
+ description: "Install userplugins with a simple button click",
+ settingsAboutComponent: () => (
+
+ Equicord does not moderate userplugins and takes no responsibility for anything that may result from installing them.
+ Only install userplugins from developers you trust. Doing so is entirely at your own risk.
+
+ ),
+ async checkPluginUpdates() {
+ for (const p of this.plugins.value()) {
+ if (await Native.isUpdateAvailableForPlugin(p.directory!)) {
+ const t = this.pluginsWithUpdates.value().plugins;
+ t.push(p.directory!);
+ this.pluginsWithUpdates.value({
+ finished: false,
+ plugins: t
+ });
+ }
+ }
+ const t = this.pluginsWithUpdates.value().plugins;
+ this.pluginsWithUpdates.value({
+ finished: true,
+ plugins: t
+ });
+ },
+ section: {
+ key: "vencord_userplugins",
+ title: "UserPlugins",
+ panelTitle: "UserPlugins",
+ Component: SettingsTab,
+ Icon: AppsIcon
+ },
+ async start() {
+ if (!VencordNative.pluginHelpers.UserpluginInstaller) return void Alerts.show({
+ title: "UserpluginInstaller not fully loaded",
+ body: "You need to restart to allow the native to be loaded :)",
+ confirmText: "Restart now",
+ onConfirm() {
+ relaunch();
+ },
+ cancelText: "Later"
+ });
+
+ plSettings.customEntries.push(this.section);
+
+ this.pluginsWithUpdates.registerCallback((value, id) => {
+ if (value.plugins.length === 0) return;
+ if (settings.store.neverNotifyForPlugins.split(",").map(t => t.trim().toLowerCase()).includes(value.plugins[value.plugins.length - 1].toLowerCase()))
+ return;
+ this.pluginsWithUpdates.deregisterCallback(id);
+ if (settings.store.notifyIfUpdate)
+ showNotification({
+ title: "Some UserPlugins are out of date!",
+ body: "Click to open the UserPlugin Updater",
+ noPersist: true,
+ permanent: true,
+ onClick() {
+ OpenSettingsModule.openUserSettings("vencord_userplugins_panel");
+ },
+ });
+ });
+ const pls = await Native.getUserplugins();
+ // @ts-ignore :trolley:
+ this.plugins.value(pls);
+ await this.checkPluginUpdates();
+ },
+ stop() {
+ // @ts-ignore
+ plSettings.customEntries.splice(plSettings.customEntries.indexOf(this.section), 1);
+ },
+ plugins: new VariableWithCallbacks<{
+ name: string;
+ description: string;
+ usesPreSend: boolean;
+ usesNative: boolean;
+ directory: string;
+ remote: string;
+ }[]>([]),
+ pluginsWithUpdates: new VariableWithCallbacks<{
+ finished: boolean;
+ plugins: string[];
+ }>({
+ finished: false,
+ plugins: []
+ }),
+ settings,
+ authors: [Devs.nin0dev],
+ renderMessageAccessory: props => {
+ return ;
+ }
+});
diff --git a/src/equicordplugins/userpluginInstaller.dev/misc/constants.ts b/src/equicordplugins/userpluginInstaller.dev/misc/constants.ts
new file mode 100644
index 000000000..b860a82c3
--- /dev/null
+++ b/src/equicordplugins/userpluginInstaller.dev/misc/constants.ts
@@ -0,0 +1,28 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 nin0
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { classNameFactory } from "@api/Styles";
+import { relaunch } from "@utils/native";
+import { Alerts } from "@webpack/common";
+
+// if edited, also edit in native.ts!!!
+export const CLONE_LINK_REGEX = /https:\/\/(?:((?:git(?:hub|lab)\.com|git\.(?:[a-zA-Z0-9]|\.)+|codeberg\.org))\/(?!user-attachments)((?:[a-zA-Z0-9]|-)+)\/((?:[a-zA-Z0-9]|-|\.)+)(?:\.git)?|(plugins\.(nin0)\.dev)\/((?:[a-zA-Z0-9]|-|\.)+))(?:\/)?/;
+export const WHITELISTED_SHARE_CHANNELS = ["1256395889354997771", "1032200195582197831", "1301947896601509900", "1322935137591365683"];
+export const cl = classNameFactory("vc-userplugininstaller-");
+
+export function showInstallFinishedAlert(pluginToEnable: string, native: boolean) {
+ Alerts.show({
+ title: "Done!",
+ body: `${pluginToEnable} has been successfully installed.${native ? " However, as it makes use of native functions, a client restart is required." : ""} What now?`,
+ confirmText: `Enable & ${native ? "restart" : "refresh"}`,
+ cancelText: native ? "Restart" : "Refresh",
+ onConfirm() {
+ !Vencord.Plugins.plugins[pluginToEnable] ? Vencord.Settings.plugins[pluginToEnable] = { enabled: true } : Vencord.Settings.plugins[pluginToEnable].enabled = true;
+ native ? relaunch() : window.location.reload();
+ },
+ onCancel: () => native ? relaunch() : window.location.reload()
+ });
+}
diff --git a/src/equicordplugins/userpluginInstaller.dev/misc/pluginValidate.txt b/src/equicordplugins/userpluginInstaller.dev/misc/pluginValidate.txt
new file mode 100644
index 000000000..9486605c9
--- /dev/null
+++ b/src/equicordplugins/userpluginInstaller.dev/misc/pluginValidate.txt
@@ -0,0 +1,158 @@
+
+
+
+
+
+ Review userplugin
+
+
+
+ %WARNINGHIDER% %NATIVETSHIDER% %PRESENDHIDER%
+
+
+
+
Plugin info
+
+
%PLUGINNAME%
+
%PLUGINDESC%
+
+
+
Warnings
+
+
Uses a native.ts file
+
+ Use of this file allows the plugin to escape the browser sandbox
+ and potentially do anything to your system and data.
+ ONLY INSTALL THIS PLUGIN AFTER REVIEWING THE CODE AND BEING
+ 100% SURE THAT NOTHING BAD CAN BE DONE!
+
+
+ Acknowledge warning (required to allow
+ install)
+
+
+
Has pre-send listeners
+
+ This allows the plugin to edit your messages before they are
+ sent.
+
+
+
+ Reminder: installing a userplugin can be a destructive action. Make
+ sure that you know and trust the developer before installing any.
+
+ If you're having issues downloading userplugins because Git isn't properly detected, you can manually set a Git
+ path
+ that'll be used instead of
+ simply looking in PATH.
+
+ If you do not specify anything, Git will be called from the PATH variable. Do not set this setting unless you
+ have issues.
+
+
+ On Linux, Flatpak still isn't supported.
+