Equibop supportHelper (#470)

* Add support for equibop git hashes, Equicord/Equibop/commit/154ad1095b358e92a1f57f77ffcc38d691d1075a

* Add support for equibop arrpc-bun debugging

* move to an actual plugin for equibop instead of webrichp

* move plugin to core, move arrp debug out of support helper

* fix devbuild detection for equibop

* Tidy

* ARRPCBUN CHAT

* WOW

---------

Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
creations 2025-10-27 14:14:15 -04:00 • committed by GitHub
parent b503d49478
commit 57ebbe1fd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 281 additions and 42 deletions

View file

@ -0,0 +1,230 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { popNotice, showNotice } from "@api/Notices";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { isAnyPluginDev, isEquicordGuild } from "@utils/misc";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { findByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Toasts, UserStore } from "@webpack/common";
const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application");
const logger = new Logger("arRPCBun");
async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
}
const apps: any = {};
async function lookupApp(applicationId: string): Promise<string> {
const socket: any = {};
await fetchApplicationsRPC(socket, applicationId);
return socket.application;
}
let ws: WebSocket;
export const settings = definePluginSettings({
oldVerNotice: {
type: OptionType.BOOLEAN,
description: "old version notice for why arrpc bun doesnt work",
default: false,
hidden: true
},
oneTimeNotice: {
type: OptionType.BOOLEAN,
description: "One time notice for showing arrpc disabled",
default: false,
hidden: true
},
});
export default definePlugin({
name: "arRPCBun",
description: "arRPCBun integration",
authors: [EquicordDevs.creations],
reporterTestable: ReporterTestable.None,
enabledByDefault: IS_EQUIBOP,
settings,
commands: [
{
name: "arrpc-debug",
description: "Show arRPCBun debug information",
predicate: ctx => {
const result = isAnyPluginDev(UserStore.getCurrentUser()?.id) || isEquicordGuild(ctx?.guild?.id, true);
return result;
},
execute: () => {
const arrpcStatus = IS_EQUIBOP ? VesktopNative.arrpc?.getStatus?.() : null;
let content = "";
if (IS_EQUIBOP) {
const version = VesktopNative.app.getVersion();
const gitHash = VesktopNative.app.getGitHash?.();
const shortHash = gitHash?.slice(0, 7);
content += `Equibop: v${version}`;
if (shortHash) {
content += ` • [${shortHash}](<https://github.com/Equicord/Equibop/commit/${gitHash}>)`;
}
content += "\n";
}
if (arrpcStatus) {
content += `Running: ${arrpcStatus.running ? "Yes" : "No"}\n`;
content += `Enabled: ${arrpcStatus.enabled ? "Yes" : "No"}\n`;
if (arrpcStatus.running) {
content += `Port: ${arrpcStatus.port}\n`;
content += `PID: ${arrpcStatus.pid}\n`;
if (arrpcStatus.uptime) {
const seconds = Math.floor(arrpcStatus.uptime / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
content += `Uptime: ${hours}h ${minutes % 60}m ${seconds % 60}s\n`;
} else if (minutes > 0) {
content += `Uptime: ${minutes}m ${seconds % 60}s\n`;
} else {
content += `Uptime: ${seconds}s\n`;
}
}
}
const info = [
arrpcStatus.restartCount > 0 && ["Restarts", arrpcStatus.restartCount],
arrpcStatus.bunPath && ["Bun", arrpcStatus.bunPath],
arrpcStatus.warnings?.length && ["Warnings", arrpcStatus.warnings.join(", ")],
arrpcStatus.lastError && ["Last Error", arrpcStatus.lastError],
arrpcStatus.lastExitCode && arrpcStatus.lastExitCode !== 0 && ["Exit Code", arrpcStatus.lastExitCode]
].filter(Boolean);
content += info.map(([type, value]) => `${type}: ${value}`).join("\n");
} else {
if (ws) {
content += ws.readyState === WebSocket.OPEN
? "WebSocket: Connected to external arRPCBun server\n"
: `WebSocket: ${["Connecting", "Open", "Closing", "Closed"][ws.readyState]}\n`;
} else {
content += "WebSocket: Not connected\n";
}
}
return { content };
},
},
],
async handleEvent(e: MessageEvent<any>) {
const data = JSON.parse(e.data);
const { activity } = data;
const assets = activity?.assets;
if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image);
if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image);
if (activity) {
const appId = activity.application_id;
apps[appId] ||= await lookupApp(appId);
const app = apps[appId];
activity.name ||= app.name;
}
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
},
async start() {
// only works on 3.0.8+
if (IS_EQUIBOP) {
const version = VesktopNative.app.getVersion();
const [major, minor, patch] = version.split(".").map(Number);
if (major < 3 || (major === 3 && minor === 0 && patch < 8) && !settings.store.oldVerNotice) {
logger.error(`Equibop ${version} is too old. Requires 3.0.8+ for arRPCBun fix.`);
showNotice(`arRPCBun requires Equibop 3.0.8+. You have ${version}. Update Equibop to use this plugin.`, "OK", () => {
settings.store.oldVerNotice = true;
popNotice();
});
return;
}
}
// disable WebRichPresence to avoid conflicts
const webRPC = Vencord.Plugins.plugins.WebRichPresence;
if (webRPC && Vencord.Plugins.isPluginEnabled("WebRichPresence")) {
logger.info("Disabling WebRichPresence to avoid conflicts");
Vencord.Plugins.stopPlugin(webRPC);
}
// get arRPC status from Equibop if available, otherwise use defaults
const arrpcStatus = IS_EQUIBOP ? VesktopNative.arrpc?.getStatus?.() : null;
// if on Equibop and arRPC is disabled AND not running, warn user
if (IS_EQUIBOP && !arrpcStatus?.enabled && !arrpcStatus?.running && !settings.store.oneTimeNotice) {
logger.warn("Equibop's built-in arRPC is disabled and not running");
showNotice("arRPC is not running. Enable it in Equibop settings, or run your own arRPCBun server.", "OK", () => {
settings.store.oneTimeNotice = true;
popNotice();
});
return;
}
const host = arrpcStatus?.host || "127.0.0.1";
const port = arrpcStatus?.port || 1337;
const wsUrl = `ws://${host}:${port}`;
logger.info(`Connecting to arRPCBun at ${wsUrl}${arrpcStatus?.host ? "" : " (using defaults)"}`);
if (ws) ws.close();
ws = new WebSocket(wsUrl);
ws.onmessage = this.handleEvent;
ws.onerror = error => {
logger.error("WebSocket error:", error);
};
ws.onclose = () => {
logger.info("WebSocket closed");
};
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 5000));
if (!connectionSuccessful) {
logger.error("Failed to connect to arRPCBun");
showNotice("Failed to connect to arRPCBun, is it running?", "Retry", () => {
popNotice();
this.start();
});
return;
}
logger.info("Successfully connected to arRPCBun");
Toasts.show({
message: "Connected to arRPCBun",
type: Toasts.Type.SUCCESS,
id: Toasts.genId(),
options: {
duration: 1000,
position: Toasts.Position.BOTTOM
}
});
},
stop() {
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null });
ws?.close();
logger.info("Stopped arRPCBun connection");
}
});

View file

@ -75,7 +75,7 @@ async function readDirectoryEntry(entry: FileSystemDirectoryEntry): Promise<Reco
const readBatch = async (): Promise<void> => {
return new Promise((resolve, reject) => {
reader.readEntries(async (entries) => {
reader.readEntries(async entries => {
if (entries.length === 0) {
resolve();
return;

View file

@ -100,3 +100,31 @@ export async function toggleEnabled(name: string) {
settings.enabled = !wasEnabled;
return await beforeReturn(settings, wasEnabled);
}
export function getWindowsName(release: string) {
const build = parseInt(release.split(".")[2]);
if (build >= 22000) return "Windows 11";
if (build >= 10240) return "Windows 10";
if (build >= 9200) return "Windows 8.1";
if (build >= 7600) return "Windows 7";
return `Windows (${release})`;
}
export function getMacOSName(release: string) {
const major = parseInt(release.split(".")[0]);
if (major === 24) return "MacOS 15 (Sequoia)";
if (major === 23) return "MacOS 14 (Sonoma)";
if (major === 22) return "MacOS 13 (Ventura)";
if (major === 21) return "MacOS 12 (Monterey)";
if (major === 20) return "MacOS 11 (Big Sur)";
if (major === 19) return "MacOS 10.15 (Catalina)";
return `MacOS (${release})`;
}
export function platformName() {
if (typeof DiscordNative === "undefined") return navigator.platform;
if (DiscordNative.process.platform === "win32") return `${getWindowsName(DiscordNative.os.release)}`;
if (DiscordNative.process.platform === "darwin") return `${getMacOSName(DiscordNative.os.release)} (${DiscordNative.process.arch === "arm64" ? "Apple Silicon" : "Intel Silicon"})`;
if (DiscordNative.process.platform === "linux") return `Linux (${DiscordNative.os.release})`;
return DiscordNative.process.platform;
}

View file

@ -20,6 +20,7 @@ interface PickerProps {
const getDesktopSources = findByCodeLazy("desktop sources");
const configModule = findByPropsLazy("getOutputVolume");
const log = new Logger("InstantScreenShare");
export const settings = definePluginSettings({
streamMedia: {
type: OptionType.COMPONENT,

View file

@ -25,6 +25,7 @@ import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
import { openUpdaterModal } from "@components/settings/tabs/updater";
import { platformName } from "@equicordplugins/equicordHelper/utils";
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_IDS, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants";
import { sendMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
@ -71,41 +72,20 @@ async function forceUpdate() {
return outdated;
}
function getWindowsName(release: string) {
const build = parseInt(release.split(".")[2]);
if (build >= 22000) return "Windows 11";
if (build >= 10240) return "Windows 10";
if (build >= 9200) return "Windows 8.1";
if (build >= 7600) return "Windows 7";
return `Windows (${release})`;
}
function getMacOSName(release: string) {
const major = parseInt(release.split(".")[0]);
if (major === 24) return "MacOS 15 (Sequoia)";
if (major === 23) return "MacOS 14 (Sonoma)";
if (major === 22) return "MacOS 13 (Ventura)";
if (major === 21) return "MacOS 12 (Monterey)";
if (major === 20) return "MacOS 11 (Big Sur)";
if (major === 19) return "MacOS 10.15 (Catalina)";
return `MacOS (${release})`;
}
function platformName() {
if (typeof DiscordNative === "undefined") return navigator.platform;
if (DiscordNative.process.platform === "win32") return `${getWindowsName(DiscordNative.os.release)}`;
if (DiscordNative.process.platform === "darwin") return `${getMacOSName(DiscordNative.os.release)} (${DiscordNative.process.arch === "arm64" ? "Apple Silicon" : "Intel Silicon"})`;
if (DiscordNative.process.platform === "linux") return `Linux (${DiscordNative.os.release})`;
return DiscordNative.process.platform;
}
async function generateDebugInfoMessage() {
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if (IS_EQUIBOP) return `Equibop v${VesktopNative.app.getVersion()}`;
if (IS_EQUIBOP) {
const equibopGitHash = tryOrElse(() => VesktopNative.app.getGitHash?.(), null);
if (equibopGitHash) {
const shortHash = equibopGitHash.slice(0, 7);
return `Equibop v${VesktopNative.app.getVersion()} • [${shortHash}](<https://github.com/Equicord/Equibop/commit/${equibopGitHash}>)`;
}
return `Equibop v${VesktopNative.app.getVersion()}`;
}
if ("legcord" in window) return `LegCord v${window.legcord.version}`;
// @ts-expect-error
@ -116,7 +96,7 @@ async function generateDebugInfoMessage() {
const info = {
Equicord:
`v${VERSION} • [${shortGitHash()}](<https://github.com/Equicord/Equicord/commit/${gitHash}>)` +
`${SettingsPlugin.getVersionInfo()} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
`${IS_EQUIBOP ? "" : SettingsPlugin.getVersionInfo()} - ${Intl.DateTimeFormat("en-US", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
Client: `${RELEASE_CHANNEL} ~ ${client}`,
Platform: platformName()
};
@ -138,6 +118,7 @@ async function generateDebugInfoMessage() {
"Activity Sharing Disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false),
"Link Embeds Disabled": tryOrElse(() => !ShowEmbeds.getSetting(), false),
"Equicord DevBuild": !IS_STANDALONE,
"Equibop DevBuild": IS_EQUIBOP && tryOrElse(() => VesktopNative.app.isDevBuild?.(), false),
"Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin),
">2 Weeks Outdated": BUILD_TIMESTAMP < Date.now() - 12096e5,
[`Potentially Problematic Plugins: ${potentiallyProblematicPlugins.join(", ")}`]: potentiallyProblematicPlugins.length

View file

@ -39,22 +39,15 @@ async function lookupApp(applicationId: string): Promise<string> {
return socket.application;
}
let hideSetting = false;
let ws: WebSocket;
if (IS_VESKTOP || IS_EQUIBOP || "legcord" in window) {
hideSetting = true;
} else if ("goofcord" in window) {
hideSetting = false;
}
migratePluginSettings("WebRichPresence", "WebRichPresence (arRPC)");
export default definePlugin({
name: "WebRichPresence",
description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)",
authors: [Devs.Ducko],
reporterTestable: ReporterTestable.None,
hidden: hideSetting,
hidden: !IS_EQUIBOP && !IS_VESKTOP && !("legcord" in window),
settingsAboutComponent: () => (
<>

View file

@ -119,18 +119,24 @@ export function tryOrElse<T>(func: () => T, fallback: T): T {
}
}
export function isEquicordGuild(id: string, isGuildId: boolean = false): boolean {
export function isEquicordGuild(id: string | null | undefined, isGuildId: boolean = false): boolean {
if (!id) return false;
if (isGuildId) return id === GUILD_ID;
const channel = ChannelStore.getChannel(id);
return channel.guild_id === GUILD_ID;
}
export function isSupportChannel(channelId: string): boolean {
export function isSupportChannel(channelId: string | null | undefined): boolean {
if (channelId) return false;
return channelId === SUPPORT_CHANNEL_ID;
}
export function isEquicordSupport(userId: string): boolean {
export function isEquicordSupport(userId: string | null | undefined): boolean {
if (!userId) return false;
const member = GuildMemberStore.getMember(GUILD_ID, userId);
return member?.roles?.includes(EQUICORD_HELPERS) || false;
}