add universalMention (#471)

* hopefully the first and last commit

* this took too long to make smh

* Fix Patches

* Update find

* CustomRPC: add detail/state and image URL fields (#3758)

Co-authored-by: Vendicated <vendicated@riseup.net>

* fix missing Toolbox

* fix Decor & NoTrack (#3752)

Co-authored-by: Vendicated <vendicated@riseup.net>

* bump to v1.13.5

* DM recipients fix and DM filtering

* hopefully the first and last commit

* this took too long to make smh

* Fix Patches

* Update find

* DM recipients fix and DM filtering

* probably fine

* 🍤

* Fix Memory Leak

* Experiments: Fix Patches

---------

Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
Co-authored-by: hich4t <hich4t@enchoral.me>
This commit is contained in:
Jake 2025-11-03 16:09:16 +00:00 • committed by GitHub
parent ba7b42ad5f
commit 94f35e7e5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 126 additions and 21 deletions

View file

@ -1,7 +1,7 @@
{
"name": "equicord",
"private": "true",
"version": "1.13.4",
"version": "1.13.5",
"description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": {
@ -121,4 +121,4 @@
"engines": {
"node": ">=18"
}
}
}

View file

@ -3,8 +3,10 @@ import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../en
export interface ActivityAssets {
large_image?: string;
large_text?: string;
large_url?: string;
small_image?: string;
small_text?: string;
small_url?: string;
}
export interface ActivityButton {

View file

@ -25,7 +25,7 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { Menu, Popout, useRef, useState } from "@webpack/common";
import type { ReactNode } from "react";
import type { PropsWithChildren, ReactNode } from "react";
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
@ -154,16 +154,20 @@ export default definePlugin({
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
// TODO: (?:\.button) is for stable compat and should be removed soon:tm:
match: /focusSectionProps:"HELP".{0,20},className:(\i(?:\.button)?)\}\),/,
replace: "$& $self.renderVencordPopoutButton($1),"
match: /(?<=trailing:.{0,50})\i\.Fragment,\{(?=.+?className:(\i))/,
replace: "$self.TrailingWrapper,{className:$1,"
}
}
],
renderVencordPopoutButton: (buttonClass: string) => (
<ErrorBoundary noop>
<VencordPopoutButton buttonClass={buttonClass} />
</ErrorBoundary>
)
TrailingWrapper({ children, className }: PropsWithChildren<{ className: string; }>) {
return (
<>
{children}
<ErrorBoundary noop>
<VencordPopoutButton buttonClass={className} />
</ErrorBoundary>
</>
);
},
});

View file

@ -0,0 +1,65 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { ChannelStore, UserStore } from "@webpack/common";
const settings = definePluginSettings({
globalMention: {
type: OptionType.BOOLEAN,
description: "Mention users from any server, not just the current one",
default: false,
restartNeeded: true
},
onlyDMUsers: {
type: OptionType.BOOLEAN,
description: "Only show users you've had DMs with",
default: false,
restartNeeded: true
}
});
export default definePlugin({
name: "UniversalMention",
authors: [EquicordDevs.justjxke],
description: "Mention any user, regardless of channel access.",
settings,
settingsAboutComponent: () => (
<Paragraph className="plugin-warning">
Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete.
</Paragraph>
),
patches: [
{
find: ",queryMentionResults(",
replacement: [
{
match: /(filter:\i).{0,75}context:\i\}\)(?=,allowSnowflake)/,
replace: "$1=>true",
},
{
match: /(?<=\i=)\i\.\i\.getMembers\(.{0,25}\)\.filter\(\i\)/g,
replace: "$self.useFilter()",
predicate: () => settings.store.globalMention || settings.store.onlyDMUsers,
},
{
match: /(?<=\i=)\i\.recipients\.map\(.{0,100}:null\}\}\)/,
replace: "$self.useFilter(true)",
}
],
},
],
useFilter(map: boolean = false) {
const foundUsers = Object.values(UserStore.getUsers()) as User[];
const users = settings.store.onlyDMUsers ? foundUsers.filter(user => ChannelStore.getDMFromUserId(user.id)) : foundUsers;
return map ? users.map(user => ({ userId: user.id, nick: null })) : users;
}
});

View file

@ -73,6 +73,11 @@ function isNumberValid(value: number) {
return true;
}
function isUrlValid(value: string) {
if (value && !/^https?:\/\/.+/.test(value)) return "Must be a valid URL.";
return true;
}
function isImageKeyValid(value: string) {
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead.";
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'";
@ -180,8 +185,15 @@ export function RPCSettings() {
{ settingsKey: "appName", label: "Application Name", isValid: makeValidator(128, true) },
]} />
<SingleSetting settingsKey="details" label="Detail (line 1)" isValid={maxLength128} />
<SingleSetting settingsKey="state" label="State (line 2)" isValid={maxLength128} />
<PairSetting data={[
{ settingsKey: "details", label: "Detail (line 1)", isValid: maxLength128 },
{ settingsKey: "detailsURL", label: "Detail URL", isValid: isUrlValid },
]} />
<PairSetting data={[
{ settingsKey: "state", label: "State (line 2)", isValid: maxLength128 },
{ settingsKey: "stateURL", label: "State URL", isValid: isUrlValid },
]} />
<SingleSetting
settingsKey="streamLink"
@ -195,13 +207,15 @@ export function RPCSettings() {
settingsKey: "partySize",
label: "Party Size",
transform: parseNumber,
isValid: isNumberValid
isValid: isNumberValid,
disabled: s.type !== ActivityType.PLAYING,
},
{
settingsKey: "partyMaxSize",
label: "Maximum Party Size",
transform: parseNumber,
isValid: isNumberValid
isValid: isNumberValid,
disabled: s.type !== ActivityType.PLAYING,
},
]} />
@ -211,21 +225,23 @@ export function RPCSettings() {
{ settingsKey: "imageBig", label: "Large Image URL/Key", isValid: isImageKeyValid },
{ settingsKey: "imageBigTooltip", label: "Large Image Text", isValid: maxLength128 },
]} />
<SingleSetting settingsKey="imageBigURL" label="Large Image clickable URL" isValid={isUrlValid} />
<PairSetting data={[
{ settingsKey: "imageSmall", label: "Small Image URL/Key", isValid: isImageKeyValid },
{ settingsKey: "imageSmallTooltip", label: "Small Image Text", isValid: maxLength128 },
]} />
<SingleSetting settingsKey="imageSmallURL" label="Small Image clickable URL" isValid={isUrlValid} />
<Divider />
<PairSetting data={[
{ settingsKey: "buttonOneText", label: "Button1 Text", isValid: makeValidator(31) },
{ settingsKey: "buttonOneURL", label: "Button1 URL" },
{ settingsKey: "buttonOneURL", label: "Button1 URL", isValid: isUrlValid },
]} />
<PairSetting data={[
{ settingsKey: "buttonTwoText", label: "Button2 Text", isValid: makeValidator(31) },
{ settingsKey: "buttonTwoURL", label: "Button2 URL" },
{ settingsKey: "buttonTwoURL", label: "Button2 URL", isValid: isUrlValid },
]} />
<Divider />

View file

@ -62,15 +62,19 @@ export const settings = definePluginSettings({
appID?: string;
appName?: string;
details?: string;
detailsURL?: string;
state?: string;
stateURL?: string;
type?: ActivityType;
streamLink?: string;
timestampMode?: TimestampMode;
startTime?: number;
endTime?: number;
imageBig?: string;
imageBigURL?: string;
imageBigTooltip?: string;
imageSmall?: string;
imageSmallURL?: string;
imageSmallTooltip?: string;
buttonOneText?: string;
buttonOneURL?: string;
@ -85,14 +89,18 @@ async function createActivity(): Promise<Activity | undefined> {
appID,
appName,
details,
detailsURL,
state,
stateURL,
type,
streamLink,
startTime,
endTime,
imageBig,
imageBigURL,
imageBigTooltip,
imageSmall,
imageSmallURL,
imageSmallTooltip,
buttonOneText,
buttonOneURL,
@ -139,6 +147,14 @@ async function createActivity(): Promise<Activity | undefined> {
break;
}
if (detailsURL) {
activity.details_url = detailsURL;
}
if (stateURL) {
activity.state_url = stateURL;
}
if (buttonOneText) {
activity.buttons = [
buttonOneText,
@ -156,7 +172,8 @@ async function createActivity(): Promise<Activity | undefined> {
if (imageBig) {
activity.assets = {
large_image: await getApplicationAsset(imageBig),
large_text: imageBigTooltip || undefined
large_text: imageBigTooltip || undefined,
large_url: imageBigURL || undefined
};
}
@ -164,7 +181,8 @@ async function createActivity(): Promise<Activity | undefined> {
activity.assets = {
...activity.assets,
small_image: await getApplicationAsset(imageSmall),
small_text: imageSmallTooltip || undefined
small_text: imageSmallTooltip || undefined,
small_url: imageSmallURL || undefined
};
}

View file

@ -96,7 +96,7 @@ export default definePlugin({
},
// Enable experiment embed on sent experiment links
{
find: "dev://experiment/",
find: ".experimentOverride,children",
replacement: [
{
match: /\i\.isStaff\(\)/,
@ -104,7 +104,7 @@ export default definePlugin({
},
// Fix some tricky experiments name causing a client crash
{
match: /.getExperimentBucketName.+?if\(null==(\i)\|\|null==\i(?=\)return null;)/,
match: /if\(null==(\i)\|\|null==\i(?=\)return null;)/,
replace: "$&||({})[$1]!=null"
}
]