Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen 2026-09-09 10:29:50 -04:00
commit 360c1c5a6b
No known key found for this signature in database
10 changed files with 270 additions and 72 deletions

View file

@ -53,7 +53,7 @@ export class RunningGameStore extends FluxStore {
getSystemServiceStatus(service: string): SystemServiceStatus;
getVisibleGame(): RunningGame | null;
getVisibleRunningGames(): RunningGame[];
isDetectionEnabled(type?: string): boolean;
isDetectionEnabled(game: RunningGame): boolean;
isGamesSeenLoaded(): boolean;
isObservedAppRunning(app: string): boolean;
isSystemServiceInitialized(service: string): boolean;

View file

@ -1453,3 +1453,17 @@ export function RemixIcon(props: IconProps) {
</Icon>
);
}
export function UploadIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm0-17a1 1 0 0 1 1 1v4h4a1 1 0 1 1 0 2h-4v4a1 1 0 1 1-2 0v-4H7a1 1 0 1 1 0-2h4V7a1 1 0 0 1 1-1Z"
/>
</Icon>
);
}

View file

@ -1,4 +1,4 @@
/* Works around a very strange issue where *something* (electron?) will inject body { app-region: drag } when using online themes */
body {
app-region: no-drag;
app-region: unset;
}

View file

@ -93,8 +93,6 @@ if (!IS_VANILLA) {
const isMainWindow = options.title === "Discord";
options.webPreferences.preload = join(__dirname, "preload.js");
options.webPreferences.sandbox = false;
// work around discord unloading when in background
options.webPreferences.backgroundThrottling = false;
if (mainWindowFrameless && isMainWindow) {
options.frame = false;
@ -152,27 +150,6 @@ if (!IS_VANILLA) {
});
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Equicord");
// Monkey patch commandLine to:
// - disable UseEcoQoSForBackgroundProcess: Work around Discord unloading when in background
const originalAppend = app.commandLine.appendSwitch;
app.commandLine.appendSwitch = function (...args) {
if (args[0] === "disable-features") {
const disabledFeatures = new Set((args[1] ?? "").split(","));
disabledFeatures.add("UseEcoQoSForBackgroundProcess");
args[1] += [...disabledFeatures].join(",");
}
return originalAppend.apply(this, args);
};
// disable renderer backgrounding to prevent the app from unloading when in the background
// https://github.com/electron/electron/issues/2822
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
// Work around discord unloading when in background
// Discord also recently started adding these flags but only on windows for some reason dunno why, it happens on Linux too
app.commandLine.appendSwitch("disable-renderer-backgrounding");
app.commandLine.appendSwitch("disable-background-timer-throttling");
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
} else {
console.log("[Equicord] Running in vanilla mode. Not loading Equicord");
}

View file

@ -0,0 +1,7 @@
# AddAttachments
Allows you to add new attachments while editing messages.
Start editing a message and then you can either press the "Add Attachment" button or paste files from your clipboard!
![](https://github.com/user-attachments/assets/3a3a5cda-ab36-4dee-bd28-2d2a08f8f996)

View file

@ -0,0 +1,162 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ChatBarButton } from "@api/ChatButtons";
import { UploadIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { pluralise } from "@utils/misc";
import definePlugin from "@utils/types";
import { Message } from "@vencord/discord-types";
import { MessageFlags, MessageType } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack";
import { AuthenticationStore, ChannelStore, EditMessageStore, MessageStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, Toasts } from "@webpack/common";
const { uniqueId } = findByPropsLazy("uniqueId");
function chooseAttachments(channelId: string, messageId: string, existingAttachmentCount: number) {
const input = document.createElement("input");
input.type = "file";
input.accept = "*/*";
input.multiple = true;
input.click();
input.addEventListener("change", async () => {
try {
if (!input.files) return;
await addAttachments(channelId, messageId, existingAttachmentCount, input.files);
} finally {
input.remove();
}
});
}
async function addAttachments(channelId: string, messageId: string, existingAttachmentCount: number, files: FileList) {
if (!files) return;
if (files.length + existingAttachmentCount > 10) {
const remaining = 10 - existingAttachmentCount;
if (remaining <= 0) {
showToast("You cannot add more attachments to this message.", Toasts.Type.FAILURE);
return;
}
showToast(`You can only add ${pluralise(remaining, "more attachment")} to this message.`, Toasts.Type.FAILURE);
return;
}
showToast("Uploading, this can take a while...", Toasts.Type.CLOCK);
const { body: { attachments } } = await RestAPI.post({
url: `/channels/${channelId}/attachments`,
body: {
files: Array.from(files, file => ({
filename: file.name,
file_size: file.size,
id: uniqueId(),
is_clip: false
}))
}
}) as { body: { attachments: { id: string, upload_url: string, upload_filename: string; }[]; }; };
const newAttachments = await Promise.all(
attachments.map((file, i) =>
fetch(file.upload_url, {
method: "PUT",
body: files[i]
}).then(() => ({
id: file.id,
uploaded_filename: file.upload_filename,
filename: files[i].name
}))
)
);
const msg = MessageStore.getMessage(channelId, messageId);
await RestAPI.patch({
url: `/channels/${channelId}/messages/${messageId}`,
body: {
attachments: [
...msg.attachments,
...newAttachments
]
}
});
showToast(`${files.length === 1 ? "Attachment" : "Attachments"} added successfully!`, Toasts.Type.SUCCESS);
}
function canAddAttachments(msg: Message) {
return (
[MessageType.DEFAULT, MessageType.REPLY].includes(msg.type) &&
!msg.hasFlag(MessageFlags.IS_VOICE_MESSAGE) &&
AuthenticationStore.getId() === msg.author.id &&
msg.attachments.length < 10
);
}
function handlePaste(e: ClipboardEvent) {
if (!e.clipboardData?.files.length) return;
const msg = EditMessageStore.getEditingMessage(SelectedChannelStore.getChannelId());
if (!msg) return;
e.preventDefault();
e.stopImmediatePropagation();
addAttachments(msg.channel_id, msg.id, msg.attachments.length, e.clipboardData.files);
}
export default definePlugin({
name: "AddAttachments",
description: "Allows you to add new attachments while editing messages",
authors: [Devs.Lumap],
start() {
document.addEventListener("paste", handlePaste, { capture: true });
},
stop() {
document.removeEventListener("paste", handlePaste, { capture: true });
},
messagePopoverButton: {
icon: UploadIcon,
render(msg) {
if (!canAddAttachments(msg)) return null;
const channel = ChannelStore.getChannel(msg.channel_id);
if (!channel.isPrivate() && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
return {
label: "Add Attachments",
icon: UploadIcon,
message: msg,
channel: ChannelStore.getChannel(msg.channel_id),
onClick: () => chooseAttachments(msg.channel_id, msg.id, msg.attachments.length)
};
},
},
chatBarButton: {
icon: UploadIcon,
render({ channel, type }) {
if (type.analyticsName !== "edit") return null;
const msg = EditMessageStore.getEditingMessage(channel.id);
if (!msg || !canAddAttachments(msg)) return null;
return (
<ChatBarButton
tooltip="Add Attachments"
onClick={() => chooseAttachments(msg.channel_id, msg.id, msg.attachments.length)}
>
<UploadIcon height={18} width={18} />
</ChatBarButton>
);
}
}
});

View file

@ -1,3 +0,0 @@
#staff-help-popout-staff-help-bug-reporter {
display: none;
}

View file

@ -2,12 +2,14 @@
Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings.
![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82)
![](https://github.com/user-attachments/assets/5da33269-af2d-4fd5-a3fb-7176c039f812)
![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74)
![](https://github.com/user-attachments/assets/7376d230-7ca0-4468-8598-a0e5818a7e28)
![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41)
![](https://github.com/user-attachments/assets/96abf73c-bc3e-4eb1-8b15-01b914964677)
The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord:
The activity stays showing as a detected game even if ignored, but is excluded from the status, differently from the stock Toggle Detection button from Discord:
![](https://github.com/user-attachments/assets/f7b82992-279e-4449-bcad-d6ab536b9f13)
![](https://github.com/user-attachments/assets/d498bf1d-36ef-4cbe-8043-2893325176d3)
![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a)

View file

@ -4,17 +4,19 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { HeadingSecondary } from "@components/Heading";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import CustomRpcPlugin from "@plugins/customRPC";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { Button, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
import { FluxDispatcher, Menu, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
const enum ActivitiesTypes {
Game,
@ -32,16 +34,17 @@ const enum FilterMode {
Blacklist
}
const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
return (
<Tooltip text={tooltipText}>
{tooltipProps => (
<button
<div
{...tooltipProps}
role="button"
onClick={e => handleActivityToggle(e, activity)}
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
style={{ cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
>
<svg
width="24"
@ -50,24 +53,24 @@ function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string
>
<path fill={fill} d={path} />
</svg>
</button>
</div>
)}
</Tooltip>
);
}
const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
function ToggleActivityComponent(activity: IgnoredActivity) {
const s = settings.use(["ignoredActivities"]);
const { ignoredActivities } = s;
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--interactive-icon-default)");
return ToggleIconOn(activity, "var(--interactive-icon-default)");
}
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity) {
e.stopPropagation();
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
@ -76,7 +79,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
}
function recalculateActivities() {
ShowCurrentGame.updateSetting(old => old);
ShowCurrentGame.updateSetting(current => current);
}
function ImportCustomRPCComponent() {
@ -132,7 +135,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
return (
<section>
<HeadingSecondary>Filter List</HeadingSecondary>
<Heading tag="h3">Filter List</Heading>
<Paragraph className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Paragraph>
<TextArea
type="text"
@ -144,6 +147,57 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
);
}
interface RawGame {
id?: string;
exePath: string;
name: string;
}
interface ContextMenuProps {
rawGame: RawGame;
}
const registeredGameOverflowContextMenuPatch: NavContextMenuPatchCallback = (children, { rawGame }: ContextMenuProps) => {
const s = settings.use(["ignoredActivities"]);
const { ignoredActivities } = s;
const id = rawGame.id ?? rawGame.exePath;
const isCurrentlyIgnored = ignoredActivities.some(act => act.id === id);
function handleToggleActivity() {
const activity: IgnoredActivity = {
id,
name: rawGame.name,
type: ActivitiesTypes.Game
};
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
const isGettingIgnored = ignoredActivityIndex === -1;
if (isGettingIgnored) settings.store.ignoredActivities.push(activity);
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
const game = RunningGameStore.getRunningGames().find(game => game.id === rawGame.id || game.exePath === rawGame.exePath)!;
const isDetectionEnabled = RunningGameStore.isDetectionEnabled(game);
if (!isGettingIgnored && !isDetectionEnabled) {
FluxDispatcher.dispatch({
type: "RUNNING_GAME_TOGGLE_DETECTION",
game
});
}
}
children.push(
<Menu.MenuCheckboxItem
id="ignore-activities-toggle-activity"
label="Enable Activity"
checked={!isCurrentlyIgnored}
action={handleToggleActivity}
/>
);
};
const settings = definePluginSettings({
importCustomRPC: {
type: OptionType.COMPONENT,
@ -189,7 +243,7 @@ const settings = definePluginSettings({
},
ignoreListening: {
type: OptionType.BOOLEAN,
description: "Ignore all listening activities (These are usually spotify activities)",
description: "Ignore all listening activities (These are usually Spotify activities)",
default: false,
onChange: recalculateActivities
},
@ -235,7 +289,6 @@ export default definePlugin({
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below",
tags: ["Activity", "Privacy", "Customisation"],
dependencies: ["UserSettingsAPI"],
settings,
patches: [
@ -255,24 +308,21 @@ export default definePlugin({
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
}
},
{
find: "#{intl::SETTINGS_GAMES_NO_GAMES_HEADER}",
replacement: {
match: /(\i)&&!\i\|\|\i\?null(?<=(\i)\.verified&&.+?)/,
replace: "$self.renderToggleGameActivityButton($2,$1),$&"
}
},
// Activities from the apps launcher in the bottom right of the chat bar
{
find: "#{intl::EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY}",
replacement: {
match: /lineClamp:1.{0,50}?(?=!\i&&\i\?.+?application:(\i))/,
replace: "$&$self.renderToggleActivityButton($1),"
replace: "$&$self.ToggleActivityButton($1),"
}
}
],
contextMenus: {
"registered-game-overflow-menu": registeredGameOverflowContextMenuPatch
},
async start() {
if (settings.store.ignoredActivities.length !== 0) {
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
@ -302,17 +352,7 @@ export default definePlugin({
return true;
},
renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {
return (
<ErrorBoundary noop>
<div style={{ marginLeft: 12, zIndex: 0 }}>
{ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}
</div>
</ErrorBoundary>
);
},
renderToggleActivityButton(props: { id: string; name: string; }) {
ToggleActivityButton(props: { id: string; name: string; }) {
return (
<ErrorBoundary noop>
{ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}

View file

@ -103,7 +103,7 @@ function ReactionUsers({ message, users }: { message: Message, users: User[]; })
<div
onClick={handleClickAvatar}
onKeyDown={handleClickAvatar}
style={settings.store.avatarClick ? {} : { pointerEvents: "none" }}
style={!settings.store.clickableAvatars ? { pointerEvents: "none" } : {}}
>
<UserSummaryItem
users={users}
@ -119,10 +119,10 @@ function ReactionUsers({ message, users }: { message: Message, users: User[]; })
}
const settings = definePluginSettings({
avatarClick: {
description: "Toggle clicking avatars in reactions",
clickableAvatars: {
description: "While this is enabled, clicking a reacting user's avatar will open their profile instead of adding the reaction",
type: OptionType.BOOLEAN,
default: false,
default: true,
restartNeeded: true
}
});
@ -132,7 +132,6 @@ export default definePlugin({
description: "Renders the avatars of users who reacted to a message",
tags: ["Reactions", "Chat", "Appearance"],
authors: [Devs.Ven, Devs.KannaDev, Devs.newwares, Devs.paige],
isModified: true,
settings,
patches: [
{