diff --git a/package.json b/package.json index 6953adb1b..f09752508 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "typescript-transform-paths": "^3.5.5", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@10.29.2", + "packageManager": "pnpm@10.30.3", "pnpm": { "patchedDependencies": { "@types/less@3.0.8": "patches/@types__less@3.0.8.patch" diff --git a/src/equicordplugins/betterActivities/index.tsx b/src/equicordplugins/betterActivities/index.tsx index 049f0ab0c..e7cafbe12 100644 --- a/src/equicordplugins/betterActivities/index.tsx +++ b/src/equicordplugins/betterActivities/index.tsx @@ -44,7 +44,7 @@ export default definePlugin({ }, { // Show all activities in the user popout/sidebar - find: /\.POPOUT,onClose:\i}\),nicknameIcons:.+?\.isProvisional/, + find: /onOpenUserProfileModal:\i,onClose:\i\}\),null/, replacement: { match: /((\i)=.{0,10}(\i)\.id\).*?,onInteraction:\i\}\),).{0,250}onClose:\i\}\)/, replace: "$1$self.showAllActivitiesComponent({ activity: $2, user: $3 })" diff --git a/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx b/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx index df4e4c777..5968cf5b4 100644 --- a/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx +++ b/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx @@ -99,7 +99,7 @@ export default definePlugin({ }, // enables clips { - find: "2022-11_clips_experiment", + find: "2026-03-clips-experiment", replacement: { match: /defaultConfig:\{enableClips:!\d,ignorePlatformRestriction:!\d,showClipsHeaderEntrypoint:!\d,enableScreenshotKeybind:!\d,enableVoiceOnlyClips:!\d,enableAdvancedSignals:!\d\}/, replace: "defaultConfig:{enableClips:!0,ignorePlatformRestriction:$self.settings.store.ignorePlatformRestriction,showClipsHeaderEntrypoint:!0,enableScreenshotKeybind:$self.settings.store.enableScreenshotKeybind,enableVoiceOnlyClips:$self.settings.store.enableVoiceOnlyClips,enableAdvancedSignals:$self.settings.store.enableAdvancedSignals}" diff --git a/src/equicordplugins/declutter/index.tsx b/src/equicordplugins/declutter/index.tsx index 70ebc4d45..ab0b8e503 100644 --- a/src/equicordplugins/declutter/index.tsx +++ b/src/equicordplugins/declutter/index.tsx @@ -22,7 +22,7 @@ const migrationsAnammox = [ ]; for (const [oldKey, newKey] of migrationsAnammox) { - migratePluginSetting("Anammox", newKey, oldKey); + migratePluginSetting("Declutter", newKey, oldKey); } export const settings = definePluginSettings({ diff --git a/src/equicordplugins/fullVcPfp/index.tsx b/src/equicordplugins/fullVcPfp/index.tsx index 3c9a85e02..fa1694f5a 100644 --- a/src/equicordplugins/fullVcPfp/index.tsx +++ b/src/equicordplugins/fullVcPfp/index.tsx @@ -4,18 +4,28 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { EquicordDevs } from "@utils/constants"; import { t } from "@utils/translation"; -import definePlugin from "@utils/types"; -import { ChannelRTCStore, IconUtils, UserStore, VoiceStateStore } from "@webpack/common"; +import definePlugin, { OptionType } from "@utils/types"; +import { ChannelRTCStore, ChannelStore, GuildMemberStore, IconUtils, UserStore, VoiceStateStore } from "@webpack/common"; import style from "./style.css?managed"; +const settings = definePluginSettings({ + useServerProfileAvatars: { + type: OptionType.BOOLEAN, + description: "Use server profile avatars in guild voice channels when available.", + default: false + } +}); + export default definePlugin({ name: "FullVCPFP", description: t("fullVcPfp.description"), authors: [EquicordDevs.mochienya], + settings, patches: [ { find: "\"data-selenium-video-tile\":", @@ -30,15 +40,37 @@ export default definePlugin({ if (!className.includes("tile") || !participantUserId) return; const user = UserStore.getUser(participantUserId); - const channelId = VoiceStateStore.getVoiceStateForUser(participantUserId)?.channelId!; + if (!user) return; + + const channelId = VoiceStateStore.getVoiceStateForUser(participantUserId)?.channelId; + if (!channelId) return; + const isSpeaking = ChannelRTCStore.getSpeakingParticipants(channelId).some(p => p.user.id === participantUserId && p.speaking); - const avatarUrl = IconUtils.getUserAvatarURL(user, isSpeaking, 1024); + const avatarUrl = settings.store.useServerProfileAvatars + ? this.getServerAvatarUrl(participantUserId, channelId, isSpeaking) ?? IconUtils.getUserAvatarURL(user, isSpeaking, 1024) + : IconUtils.getUserAvatarURL(user, isSpeaking, 1024); return { "--full-res-avatar": `url(${avatarUrl})` }; }, + getServerAvatarUrl(userId: string, channelId: string, canAnimate: boolean) { + const guildId = ChannelStore.getChannel(channelId)?.guild_id; + if (!guildId) return; + + const guildAvatar = GuildMemberStore.getMember(guildId, userId)?.avatar; + if (!guildAvatar) return; + + return IconUtils.getGuildMemberAvatarURLSimple({ + userId, + guildId, + avatar: guildAvatar, + canAnimate, + size: 1024 + }); + }, + start() { enableStyle(style); }, diff --git a/src/equicordplugins/richMagnetLinks/index.tsx b/src/equicordplugins/richMagnetLinks/index.tsx index 3a30850d3..bafbf15cb 100644 --- a/src/equicordplugins/richMagnetLinks/index.tsx +++ b/src/equicordplugins/richMagnetLinks/index.tsx @@ -23,7 +23,7 @@ export default definePlugin({ } }, { - find: '"flattenMarkdown"', + find: 'before:"@silent"', replacement: { match: /mention:{type:/, replace: "magnet:{type:\"inlineObject\"},$&", diff --git a/src/equicordplugins/songSpotlight.desktop/index.tsx b/src/equicordplugins/songSpotlight.desktop/index.tsx index 360dd6c7e..83ecdf203 100644 --- a/src/equicordplugins/songSpotlight.desktop/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/index.tsx @@ -13,7 +13,7 @@ import definePlugin, { OptionType } from "@utils/types"; import { useAuthorizationStore } from "./lib/stores/AuthorizationStore"; import { useSongStore } from "./lib/stores/SongStore"; -import { clearCache } from "./service"; +import { Native } from "./service"; import Settings from "./ui/settings"; import ProfileSongs from "./ui/songs/ProfileSongs"; import WidgetSongs from "./ui/songs/WidgetSongs"; @@ -49,7 +49,7 @@ export default definePlugin({ { find: ".SIDEBAR}),nicknameIcons:", replacement: { - match: /{userId:(\i)\.id}\)}\)]}\)/, + match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/, replace: "$&,$self.renderProfileSongs({userId:$1.id})", }, }, @@ -80,7 +80,7 @@ export default definePlugin({ // only gets cleared on full restart. we don't want that since // audio preview URLs expire very fast, so we just clear it on // plugin restart instead - clearCache(); + Native.clearCache(); useSongStore.getState().$refresh(); useAuthorizationStore.persist.rehydrate(); diff --git a/src/equicordplugins/songSpotlight.desktop/service.ts b/src/equicordplugins/songSpotlight.desktop/service.ts index e7334d52a..7dd16eab5 100644 --- a/src/equicordplugins/songSpotlight.desktop/service.ts +++ b/src/equicordplugins/songSpotlight.desktop/service.ts @@ -17,7 +17,7 @@ export function useRender(song: Song) { useEffect(() => { setFailed(false); setRender(null); - renderSong(song) + Native.renderSong(song) .catch(() => null) .then(info => info ? setRender(info) : setFailed(true)); }, [sid(song)]); @@ -25,5 +25,4 @@ export function useRender(song: Song) { return { failed, render }; } -export const { parseLink, renderSong, validateSong, clearCache } = VencordNative.pluginHelpers - .SongSpotlight as PluginNative; +export const Native = VencordNative.pluginHelpers.SongSpotlight as PluginNative; diff --git a/src/equicordplugins/songSpotlight.desktop/ui/settings/AddSong.tsx b/src/equicordplugins/songSpotlight.desktop/ui/settings/AddSong.tsx index 084ed2534..9b0767daa 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/settings/AddSong.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/settings/AddSong.tsx @@ -10,7 +10,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { PlusIcon } from "@components/Icons"; import { cl, logger } from "@equicordplugins/songSpotlight.desktop/lib/utils"; -import { parseLink } from "@equicordplugins/songSpotlight.desktop/service"; +import { Native } from "@equicordplugins/songSpotlight.desktop/service"; import { parsers } from "@song-spotlight/api/handlers"; import { Song } from "@song-spotlight/api/structs"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; @@ -60,7 +60,7 @@ function AddSongModal({ modalProps, close, onAdd }: AddSongModalProps) { onClick={async () => { setPending(true); try { - const parsed = await parseLink(url); + const parsed = await Native.parseLink(url); if (!parsed) { setError("Invalid link"); return setPending(false); diff --git a/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx b/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx index 8dd5a166f..ef42841de 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx @@ -13,7 +13,7 @@ import { presentOAuth2Modal } from "@equicordplugins/songSpotlight.desktop/lib/o import { useAuthorizationStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore"; import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore"; import { cl } from "@equicordplugins/songSpotlight.desktop/lib/utils"; -import { validateSong } from "@equicordplugins/songSpotlight.desktop/service"; +import { Native } from "@equicordplugins/songSpotlight.desktop/service"; import { Spinner } from "@equicordplugins/songSpotlight.desktop/ui/common"; import SongList from "@equicordplugins/songSpotlight.desktop/ui/settings/SongList"; import { UserData, UserDataSchema } from "@song-spotlight/api/structs"; @@ -48,7 +48,7 @@ function ImportButton({ overwrite, pending, setPending, onImport }: ImportButton return showToast("Invalid Song Spotlight data in clipboard!", Toasts.Type.FAILURE); } - const validated = await Promise.allSettled(data.map(song => validateSong(song))); + const validated = await Promise.allSettled(data.map(song => Native.validateSong(song))); if (!validated.every(x => x.status === "fulfilled" && x.value)) { setPending(false); return showToast("One or more imported songs were invalid.", Toasts.Type.FAILURE); diff --git a/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx b/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx index f78c71819..ee42b87d6 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx @@ -11,7 +11,7 @@ import { Link } from "@components/Link"; import { apiConstants } from "@equicordplugins/songSpotlight.desktop/lib/api"; import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore"; import { cl, formatCoverTooltip, formatDurationMs } from "@equicordplugins/songSpotlight.desktop/lib/utils"; -import { parseLink, useRender } from "@equicordplugins/songSpotlight.desktop/service"; +import { Native, useRender } from "@equicordplugins/songSpotlight.desktop/service"; import { CardClasses, ExplicitTag, @@ -84,7 +84,7 @@ function SongEntry({ entry, number, isLoaded, isPlaying, big, onClick }: SongEnt return showToast("You don't have enough space!"); } - const song = await parseLink(entry.link); + const song = await Native.parseLink(entry.link); if (!song) { return showToast("Uh oh, this song doesn't exist!", Toasts.Type.FAILURE); } diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index 854bedd09..3954ea784 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -31,12 +31,20 @@ export default definePlugin({ { find: '["VIDEO","CLIP","AUDIO"]', replacement: { - match: /(\[\i>0&&\i\.length>0.{0,150}?children:)(\i.slice\(\i\))(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/, - replace: (_, rest, origChildren, showDownload, isVisualMediaType) => `${rest}[${showDownload}&&${isVisualMediaType}&&$self.PictureInPictureButton(),...${origChildren}]` + match: /(\[\i>0&&\i\.length>0.{0,150}?children:)(\i.slice\(\i\))(?<=mimeType:(\i),downloadURL:(\i).+?showDownload:(\i).+?isVisualMediaType:(\i).+?)/, + replace: (_, rest, origChildren, mimeType, downloadURL, showDownload, isVisualMediaType) => + `${rest}[${showDownload}&&${isVisualMediaType}&&$self.shouldShowButton(${mimeType},${downloadURL})&&$self.PictureInPictureButton(),...${origChildren}]` } } ], + shouldShowButton(mimeType: string[] = [], downloadURL?: string) { + const normalizedMimeType = mimeType.join("/"); + if (normalizedMimeType.startsWith("video/")) return true; + if (!downloadURL) return false; + return /\.(mp4|webm|mov|m4v|ogv|avi)(?:$|[?#])/i.test(downloadURL); + }, + PictureInPictureButton: ErrorBoundary.wrap(() => { return (