From 9ffb2108e856f194f0081cc99f54e5b8bf64e6ff Mon Sep 17 00:00:00 2001 From: Angel Wang Date: Wed, 15 Jul 2026 10:03:39 -0600 Subject: [PATCH 1/6] LastFmRPC ~> MusicRPC: add ListenBrainz support (#4077) Co-authored-by: V --- src/main/csp/index.ts | 5 + .../index.tsx | 158 +++++++++--------- src/plugins/musicRichPresence/lastfm.ts | 63 +++++++ src/plugins/musicRichPresence/listenbrainz.ts | 105 ++++++++++++ src/utils/constants.ts | 4 + 5 files changed, 258 insertions(+), 77 deletions(-) rename src/plugins/{lastfmRichPresence => musicRichPresence}/index.tsx (74%) create mode 100644 src/plugins/musicRichPresence/lastfm.ts create mode 100644 src/plugins/musicRichPresence/listenbrainz.ts diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index bc5eb153c..adf1c73ba 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -54,6 +54,11 @@ export const CspPolicies: PolicyMap = { // Function Specific "api.github.com": ConnectSrc, // used for updating Vencord itself "ws.audioscrobbler.com": ConnectSrc, // Last.fm API + "musicbrainz.org": ConnectSrc, + "*.listenbrainz.org": ConnectSrc, + "coverartarchive.org": ConnectSrc, + "archive.org": ConnectSrc, + "*.archive.org": ConnectSrc, "translate-pa.googleapis.com": ConnectSrc, // Google Translate API "*.vencord.dev": ImageSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) "manti.vendicated.dev": ImageSrc, // ReviewDB API diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/musicRichPresence/index.tsx similarity index 74% rename from src/plugins/lastfmRichPresence/index.tsx rename to src/plugins/musicRichPresence/index.tsx index 916243485..59fcd58c6 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/musicRichPresence/index.tsx @@ -16,25 +16,38 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, migratePluginSetting, migratePluginSettings } from "@api/Settings"; import { LinkButton } from "@components/Button"; import { Card } from "@components/Card"; import { Heading } from "@components/Heading"; import { Margins } from "@components/margins"; import { Paragraph } from "@components/Paragraph"; import { Devs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types"; import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums"; import { ApplicationAssetUtils, AuthenticationStore, FluxDispatcher, PresenceStore } from "@webpack/common"; -interface TrackData { +import { LastFMScrobbler } from "./lastfm"; +import { ListenBrainzScrobbler } from "./listenbrainz"; + +export interface TrackData { name: string; album: string; artist: string; - url: string; - imageUrl?: string; + trackURL?: string; + artistURL?: string; + albumURL?: string; + imageURL?: string; + serviceName?: string; +} + +export interface ScrobblerBackend { + name: string, + id: string, + + fetchTrackData(username: string, apiKey?: string): Promise; + getUserURL(username: string): string; } const enum NameFormat { @@ -43,16 +56,15 @@ const enum NameFormat { SongFirst = "song-first", ArtistOnly = "artist", SongOnly = "song", - AlbumName = "album" + AlbumName = "album", + ServiceName = "service-name" } // Last.fm API keys are essentially public information and have no access to your account, so including one here is fine. -const API_KEY = "790c37d90400163a5a5fe00d6ca32ef0"; +const LASTFM_API_KEY = "790c37d90400163a5a5fe00d6ca32ef0"; const DISCORD_APP_ID = "1108588077900898414"; const LASTFM_PLACEHOLDER_IMAGE_HASH = "2a96cbd8b46e442fc41c2b86b821562f"; -const logger = new Logger("LastFMRichPresence"); - async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(DISCORD_APP_ID, [key]))[0]; } @@ -66,17 +78,32 @@ function setActivity(activity: Activity | null) { } const settings = definePluginSettings({ + scrobblerBackend: { + description: "The scrobbler backend to use.", + type: OptionType.SELECT, + options: [ + { + "label": "Last.FM", + "value": "lastfm", + "default": true + }, + { + "label": "ListenBrainz", + "value": "listenbrainz" + } + ] as const + }, apiKey: { displayName: "API Key", description: "Custom Last.fm API key. Not required but highly recommended to avoid rate limiting with our shared key", type: OptionType.STRING, }, username: { - description: "Last.fm username", + description: "Username", type: OptionType.STRING, }, shareUsername: { - description: "Show link to Last.fm profile", + description: "Show link to scrobbler profile", type: OptionType.BOOLEAN, default: false, }, @@ -86,12 +113,12 @@ const settings = definePluginSettings({ default: true, }, hideWithSpotify: { - description: "Hide Last.fm presence if spotify is running", + description: "Hide presence if Spotify is running", type: OptionType.BOOLEAN, default: true, }, hideWithActivity: { - description: "Hide Last.fm presence if you have any other presence", + description: "Hide presence if you have any other presence", type: OptionType.BOOLEAN, default: false, }, @@ -128,6 +155,10 @@ const settings = definePluginSettings({ value: NameFormat.StatusName, default: true }, + { + label: "Use music service name (falls back to custom status text)", + value: NameFormat.ServiceName + }, { label: "Use format 'artist - song'", value: NameFormat.ArtistFirst @@ -160,8 +191,8 @@ const settings = definePluginSettings({ type: OptionType.SELECT, options: [ { - label: "Use large Last.fm logo", - value: "lastfmLogo", + label: "Use large scrobbler logo", + value: "logo", default: true }, { @@ -170,30 +201,34 @@ const settings = definePluginSettings({ } ], }, - showLastFmLogo: { - displayName: "Show Last.fm Logo", - description: "Show the Last.fm logo by the album cover", + showLogo: { + displayName: "Show Scrobbler Logo", + description: "Show the scrobbler service logo by the album cover", type: OptionType.BOOLEAN, default: true, }, showAlbumCover: { - description: "Show album cover. Disabling this will display a placeholder. Useful if your Music has inappropriate art", + description: "Show album cover. Disabling this will display a placeholder. Useful if your music has inappropriate art", type: OptionType.BOOLEAN, default: true, } }); +migratePluginSettings("MusicRichPresence", "LastFMRichPresence"); +migratePluginSetting("MusicRichPresence", "showLastFmLogo", "showLogo"); export default definePlugin({ - name: "LastFMRichPresence", - description: "Little plugin for Last.fm rich presence", + name: "MusicRichPresence", + description: "Rich Presence for Last.FM/Listenbrainz", tags: ["Activity", "Media"], - authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu], + searchTerms: ["lastfm", "LastFMRichPresence"], + authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu, Devs.angelcube], settings, settingsAboutComponent() { return ( + Last.FM How to create an API key Set Application name and Application description to anything and leave the rest blank. Create API Key @@ -210,61 +245,22 @@ export default definePlugin({ clearInterval(this.updateInterval); }, - async fetchTrackData(): Promise { - if (!settings.store.username) - return null; - - try { - const params = new URLSearchParams({ - method: "user.getrecenttracks", - api_key: settings.store.apiKey || API_KEY, - user: settings.store.username, - limit: "1", - format: "json" - }); - - const res = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); - if (!res.ok) throw `${res.status} ${res.statusText}`; - - const json = await res.json(); - if (json.error) { - logger.error("Error from Last.fm API", `${json.error}: ${json.message}`); - return null; - } - - const trackData = json.recenttracks?.track[0]; - - if (!trackData?.["@attr"]?.nowplaying) - return null; - - // why does the json api have xml structure - return { - name: trackData.name || "Unknown", - album: trackData.album["#text"], - artist: trackData.artist["#text"] || "Unknown", - url: trackData.url, - imageUrl: trackData.image?.find((x: any) => x.size === "large")?.["#text"] - }; - } catch (e) { - logger.error("Failed to query Last.fm API", e); - // will clear the rich presence if API fails - return null; - } - }, - async updatePresence() { setActivity(await this.getActivity()); }, getLargeImage(track: TrackData): string | undefined { - if (settings.store.showAlbumCover && track.imageUrl && !track.imageUrl.includes(LASTFM_PLACEHOLDER_IMAGE_HASH)) - return track.imageUrl; + if (settings.store.showAlbumCover && track.imageURL && !track.imageURL.includes(LASTFM_PLACEHOLDER_IMAGE_HASH)) + return track.imageURL; if (settings.store.missingArt === "placeholder") return "placeholder"; }, async getActivity(): Promise { + if (!settings.store.username) + return null; + if (settings.store.hideWithActivity) { if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID && a.type !== ActivityType.CUSTOM_STATUS)) { return null; @@ -278,7 +274,9 @@ export default definePlugin({ } } - const trackData = await this.fetchTrackData(); + const scrobbler = settings.store.scrobblerBackend === "lastfm" ? LastFMScrobbler : ListenBrainzScrobbler; + + const trackData = await scrobbler.fetchTrackData(settings.store.username, settings.store.apiKey || LASTFM_API_KEY); if (!trackData) return null; const largeImage = this.getLargeImage(trackData); @@ -286,22 +284,23 @@ export default definePlugin({ { large_image: await getApplicationAsset(largeImage), large_text: trackData.album || undefined, - ...(settings.store.showLastFmLogo && { - small_image: await getApplicationAsset("lastfm-small"), - small_text: "Last.fm" + ...(settings.store.showLogo && { + small_image: await getApplicationAsset(`${scrobbler.id}-small`), + small_text: scrobbler.id }), } : { - large_image: await getApplicationAsset("lastfm-large"), + large_image: await getApplicationAsset(`${scrobbler.id}-large`), large_text: trackData.album || undefined, }; const buttons: ActivityButton[] = []; - if (settings.store.shareUsername) + if (settings.store.shareUsername) { buttons.push({ - label: "Last.fm Profile", - url: `https://www.last.fm/user/${settings.store.username}`, + label: `${scrobbler.name} Profile`, + url: scrobbler.getUserURL(settings.store.username!) }); + } const statusName = (() => { switch (settings.store.nameFormat) { @@ -318,6 +317,11 @@ export default definePlugin({ .replaceAll("{artist}", trackData.artist || "") .replaceAll("{album}", trackData.album || "") .replaceAll("{title}", trackData.name || ""); + case NameFormat.ServiceName: + return trackData.serviceName || settings.store.statusName + .replaceAll("{artist}", trackData.artist || "") + .replaceAll("{album}", trackData.album || "") + .replaceAll("{title}", trackData.name || ""); default: return settings.store.statusName .replaceAll("{artist}", trackData.artist || "") @@ -350,11 +354,11 @@ export default definePlugin({ }; if (settings.store.clickableLinks) { - activity.details_url = trackData.url; - activity.state_url = `https://www.last.fm/music/${encodeURIComponent(trackData.artist)}`; + activity.details_url = trackData.trackURL; + activity.state_url = trackData.artistURL; if (trackData.album) { - activity.assets!.large_url = `https://www.last.fm/music/${encodeURIComponent(trackData.artist)}/${encodeURIComponent(trackData.album)}`; + activity.assets!.large_url = trackData.albumURL; } } diff --git a/src/plugins/musicRichPresence/lastfm.ts b/src/plugins/musicRichPresence/lastfm.ts new file mode 100644 index 000000000..dc58a1de6 --- /dev/null +++ b/src/plugins/musicRichPresence/lastfm.ts @@ -0,0 +1,63 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Logger } from "@utils/Logger"; + +import { ScrobblerBackend, TrackData } from "."; + +const logger = new Logger("MusicRichPresence/LastFM"); + +const url = (path: string) => `https://www.last.fm${path}`; + +export const LastFMScrobbler: ScrobblerBackend = { + name: "Last.FM", + id: "lastfm", + + async fetchTrackData(username: string, apiKey: string): Promise { + try { + const params = new URLSearchParams({ + method: "user.getrecenttracks", + api_key: apiKey, + user: username, + limit: "1", + format: "json" + }); + + const res = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); + if (!res.ok) throw `${res.status} ${res.statusText}`; + + const json = await res.json(); + if (json.error) { + logger.error("Error from Last.fm API", `${json.error}: ${json.message}`); + return null; + } + + const trackData = json.recenttracks?.track[0]; + + if (!trackData?.["@attr"]?.nowplaying) + return null; + + // why does the json api have xml structure + return { + name: trackData.name || "Unknown", + album: trackData.album["#text"], + artist: trackData.artist["#text"] || "Unknown", + trackURL: trackData.url, + artistURL: trackData.artist["#text"] ? url(`/music/${encodeURIComponent(trackData.artist["#text"])}`) : undefined, + albumURL: url(`/music/${encodeURIComponent(trackData.artist["#text"])}/${encodeURIComponent(trackData.album["#text"])}`), + imageURL: trackData.image?.find((x: any) => x.size === "large")?.["#text"] + } as TrackData; + } catch (e) { + logger.error("Failed to query Last.FM API", e); + // will clear the rich presence if API fails + return null; + } + }, + + getUserURL(username: string): string { + return url(`/user/${username}`); + } +}; diff --git a/src/plugins/musicRichPresence/listenbrainz.ts b/src/plugins/musicRichPresence/listenbrainz.ts new file mode 100644 index 000000000..6844b378e --- /dev/null +++ b/src/plugins/musicRichPresence/listenbrainz.ts @@ -0,0 +1,105 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; +import { Logger } from "@utils/Logger"; + +import { ScrobblerBackend, TrackData } from "."; + +const logger = new Logger("AudioScrobblerRichPresence/ListenBrainz"); + +const url = (path: string) => `https://listenbrainz.org${path}`; + +async function fetchCoverArt(releaseGroupMBID: string) { + const res = await fetch(`https://coverartarchive.org/release-group/${releaseGroupMBID}`); + if (!res.ok) return null; + return res.json().then(json => json.images[0].thumbnails.large); +} + +async function getUrls(additionalInfo: Record | undefined, trackName: string, artistName: string, releaseName: string): Promise> { + // Well tagged music will have MBIDs which we can use directly. These are optional but highly recommended in ListenBrainz scrobbles. + // If your music doesn't have these, it's highly recommended to use https://picard.musicbrainz.org/ to automatically add them + if (additionalInfo?.recording_mbid) { + const { release_group_mbid, release_mbid, recording_mbid, artist_mbids } = additionalInfo; + + return { + imageURL: release_group_mbid ? await fetchCoverArt(release_group_mbid) : undefined, + trackURL: recording_mbid ? url(`/track/${recording_mbid}`) : undefined, + albumURL: release_group_mbid + ? url(`/release-group/${release_group_mbid}`) + : release_mbid + ? url(`/release/${release_mbid}`) + : undefined, + artistURL: artist_mbids?.length ? url(`/artist/${artist_mbids[0]}`) : undefined, + }; + } + + // If no MBIDs are present, try to search for the track on MusicBrainz + + // this needs to be encoded separately—URLSearchParams encodes spaces as "+" + const query = encodeURIComponent(`artist:"${artistName}" AND recording:"${trackName}" AND album:${releaseName}`); + + const params = new URLSearchParams({ + fmt: "json", + limit: "1" + }); + + const metadata = await fetch("https://musicbrainz.org/ws/2/recording/?" + params + "&query=" + query, { + headers: { "User-Agent": VENCORD_USER_AGENT } + }) + .then(res => res.ok ? res.json() : Promise.reject(new Error(`${res.status} ${res.statusText}`))) + .then(json => json.recordings?.[0]); + + if (!metadata) { + return {}; + } + + const artist = metadata["artist-credit"]?.[0]?.artist; + const release = metadata.releases?.[0]; + + return { + imageURL: release?.["release-group"] ? await fetchCoverArt(release["release-group"].id) : undefined, + trackURL: url(`/track/${metadata.id}/`), + albumURL: release?.id ? url(`/release/${release.id}/`) : release?.["release-group"]?.id ? url(`/release-group/${release["release-group"].id}/`) : undefined, + artistURL: artist?.id ? url(`/artist/${artist.id}/`) : undefined, + }; +} + +export const ListenBrainzScrobbler: ScrobblerBackend = { + name: "ListenBrainz", + id: "listenbrainz", + + async fetchTrackData(username: string, _apiKey?: string): Promise { + try { + const res = await fetch(`https://api.listenbrainz.org/1/user/${username}/playing-now`); + if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); + + const data = await res.json().then(json => json.payload?.listens[0]); + if (!data?.playing_now || !data?.track_metadata) + return null; + + const { track_name, artist_name, release_name, additional_info } = data.track_metadata; + + const trackData = { + name: track_name || "Unknown", + artist: artist_name, + album: release_name || "Unknown", + serviceName: additional_info?.music_service_name || additional_info?.submission_client, + ...await getUrls(additional_info, track_name, artist_name, release_name) + } as TrackData; + + return trackData; + } catch (e) { + logger.error("Failed to query ListenBrainz API", e); + // will clear the rich presence if API fails + return null; + } + }, + + getUserURL(username: string): string { + return url(`/user/${username}`); + } +}; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 85901da7f..b2255bb0f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -646,6 +646,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "NightmareSan", id: 304239816466235392n }, + angelcube: { + name: "angelcube", + id: 958505257288208446n + }, Lunascape: { name: "Lunascape", id: 383365021415243776n From c4d8e8624b36866134b09b2092ecc9a62127c261 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 15 Jul 2026 18:14:51 +0200 Subject: [PATCH 2/6] chore: update some Dev IDs --- src/plugins/musicRichPresence/index.tsx | 2 +- src/plugins/noUnblockToJump/index.ts | 2 +- src/plugins/serverListIndicators/index.tsx | 2 +- src/plugins/showHiddenChannels/index.tsx | 2 +- src/utils/constants.ts | 10 +++------- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/plugins/musicRichPresence/index.tsx b/src/plugins/musicRichPresence/index.tsx index 59fcd58c6..9ba35e29a 100644 --- a/src/plugins/musicRichPresence/index.tsx +++ b/src/plugins/musicRichPresence/index.tsx @@ -221,7 +221,7 @@ export default definePlugin({ description: "Rich Presence for Last.FM/Listenbrainz", tags: ["Activity", "Media"], searchTerms: ["lastfm", "LastFMRichPresence"], - authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu, Devs.angelcube], + authors: [Devs.Rini, Devs.Ven, Devs.angelcube, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu], settings, diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index 6c1f504b6..a9071dcdb 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -23,7 +23,7 @@ export default definePlugin({ name: "NoUnblockToJump", description: "Allows you to jump to messages of blocked or ignored users and likely spammers without unblocking them", tags: ["Utility"], - authors: [Devs.dzshn], + authors: [Devs.Rini], patches: [ { find: "#{intl::UNIGNORE_TO_JUMP_BODY}", diff --git a/src/plugins/serverListIndicators/index.tsx b/src/plugins/serverListIndicators/index.tsx index 6dddce19f..998a0add9 100644 --- a/src/plugins/serverListIndicators/index.tsx +++ b/src/plugins/serverListIndicators/index.tsx @@ -104,7 +104,7 @@ export default definePlugin({ name: "ServerListIndicators", description: "Add online friend count or server count in the server list", tags: ["Servers", "Appearance"], - authors: [Devs.dzshn], + authors: [Devs.Rini], dependencies: ["ServerListAPI"], settings, diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 4e2ca8cae..0061576cf 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -72,7 +72,7 @@ export default definePlugin({ name: "ShowHiddenChannels", description: "Show channels that you do not have access to view.", tags: ["Servers", "Utility"], - authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux, Devs.dzshn], + authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux, Devs.Rini], settings, patches: [ diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b2255bb0f..b685039fd 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -172,10 +172,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "kemo", id: 715746190813298788n }, - dzshn: { - name: "dzshn", - id: 310449948011528192n - }, Ducko: { name: "Ducko", id: 506482395269169153n @@ -354,8 +350,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({ id: 354191516979429376n }, Rini: { - name: "Rini", - id: 1079479184478441643n + name: "rini", + id: 0n }, castdrian: { name: "castdrian", @@ -495,7 +491,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ }, Byeoon: { name: "byeoon", - id: 1167275288036655133n + id: 495683688474869781n }, Kaitlyn: { name: "kaitlyn", From de0b57eb19f54cdca0dbe82f80a5855eabbd810b Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:36:07 -0300 Subject: [PATCH 3/6] MessageLinkEmbed: Fix erroring with lazily loaded module --- src/plugins/messageLinkEmbeds/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index b2e5e85ba..65f563e0e 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -21,7 +21,6 @@ import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import { Devs } from "@utils/constants.js"; -import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin, { OptionType } from "@utils/types"; import { Channel, Message } from "@vencord/discord-types"; @@ -51,7 +50,6 @@ const Embed = findComponentLazy(m => m.prototype?.renderSuppressButton); const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hideAccessories"); let AutoModEmbed: ComponentType = () => null; -const SearchResultClasses = findCssClassesLazy("message", "searchResult"); const EmbedClasses = findCssClassesLazy("embedAuthorIcon", "embedAuthor", "embedAuthor", "embedMargin"); const MessageDisplayCompact = getUserSettingLazy("textAndImages", "messageDisplayCompact")!; @@ -306,7 +304,12 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): } }} renderDescription={() => ( -
+
Date: Wed, 15 Jul 2026 18:36:18 -0300 Subject: [PATCH 4/6] Decor: Remove obsolete patch --- src/plugins/decor/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 506553163..29f2b4c96 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -98,7 +98,6 @@ export default definePlugin({ ] }, ...[ - "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", // Messages "#{intl::COLLECTIBLES_NAMEPLATE_PREVIEW_A11Y}", // Nameplate preview "#{intl::COLLECTIBLES_PROFILE_PREVIEW_A11Y}", // Avatar preview ].map(find => ({ From 0a5dfaa1caa0799899b4d14e3862b70c665d8223 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:33:40 -0300 Subject: [PATCH 5/6] Fix broken patches --- src/plugins/betterFolders/index.tsx | 2 +- src/plugins/decor/index.tsx | 4 ++-- src/plugins/superReactionTweaks/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 33cfcfbb3..f385ddcdb 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -268,7 +268,7 @@ export default definePlugin({ predicate: () => settings.store.closeAllHomeButton, replacement: { // Close all folders when clicking the home button - match: /(?<=onClick:\(\)=>{)(?=.{0,300}"discodo")/, + match: /(?<=onClick:(?:function)?\(\)(?:=>)?{)(?=.{0,300}"discodo")/, replace: "$self.closeFolders();" } } diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 29f2b4c96..18a7dd648 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -55,8 +55,8 @@ export default definePlugin({ replace: "$self.DecorationGridItem=$1;", }, { - match: /(?<==)\i=>{let{user:\i,avatarDecoration/, - replace: "$self.DecorationGridDecoration=$&", + match: /(?<=(?:(\i)=)?)(?:\i=>|function (\i)\(\i\)){let{user:\i,avatarDecoration/, + replace: (m, arrowFunctionName, functionName) => `$self.DecorationGridDecoration=${arrowFunctionName ?? functionName}${arrowFunctionName ? "" : ";"}${m}`, }, // Remove NEW label from decor avatar decorations { diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 8bdf37d47..8bd86e0d6 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -45,7 +45,7 @@ export default definePlugin({ replacement: [ { // if (inlinedCalculatePlayingCount(a,b) >= limit) return; - match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=5+?(?=\))/, + match: /(BURST_REACTION_EFFECT_PLAY:(?:\i=>|function\(\i\)){.+?if\()(\(?(?:function)?\(\i,\i\)(?:=>)?{.+?\(\i,\i\))>=5+?(?=\))/, replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})` } ] From 8d85cb56d211703c7e3c46bf63c720fb41a4a462 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:24:09 -0300 Subject: [PATCH 6/6] MessagePopoverAPI: Fix broken patch --- src/plugins/_api/messagePopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index ef49d5119..7f6c5b938 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=\]\}\)),(.{0,40}togglePopout:.+?\}\))\]\}\):null,(?<=\((\i\.\i),\{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=\]\}\)),(.{0,40}togglePopout:.+?\}\))\]\}\):null,(?<=\((\i),\{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,` }