From 586b26d2d4736e401beba41a29cf03d27e69ca7f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 21 Mar 2023 03:07:16 -0300 Subject: Fixes and ShowHiddenChannels improvements (#634) ~ Fixes #630 ~ Fixes #618 --- src/plugins/crashHandler.ts | 20 ++-- src/plugins/devCompanion.dev.tsx | 14 ++- src/plugins/emoteCloner.tsx | 2 +- src/plugins/fakeNitro.ts | 24 ++--- src/plugins/reverseImageSearch.tsx | 2 +- .../components/HiddenChannelLockScreen.tsx | 8 +- src/plugins/showHiddenChannels/index.tsx | 107 ++++++++++++++++++--- src/plugins/showHiddenChannels/style.css | 1 + src/plugins/volumeBooster.desktop.ts | 86 +++++++++++++++++ src/plugins/volumeBooster.ts | 86 ----------------- 10 files changed, 223 insertions(+), 127 deletions(-) create mode 100644 src/plugins/volumeBooster.desktop.ts delete mode 100644 src/plugins/volumeBooster.ts (limited to 'src/plugins') diff --git a/src/plugins/crashHandler.ts b/src/plugins/crashHandler.ts index e35dfed..6457e09 100644 --- a/src/plugins/crashHandler.ts +++ b/src/plugins/crashHandler.ts @@ -42,6 +42,7 @@ const settings = definePluginSettings({ }); let crashCount: number = 0; +let lastCrashTimestamp: number = 0; export default definePlugin({ name: "CrashHandler", @@ -80,6 +81,7 @@ export default definePlugin({ }); } catch { } + lastCrashTimestamp = Date.now(); return false; } @@ -97,17 +99,21 @@ export default definePlugin({ } catch (err) { CrashHandlerLogger.error("Failed to handle crash", err); return false; + } finally { + lastCrashTimestamp = Date.now(); } }, handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Attempting to recover...", - }); - } catch { } + if (Date.now() - lastCrashTimestamp >= 1_000) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Attempting to recover...", + }); + } catch { } + } try { FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" }); diff --git a/src/plugins/devCompanion.dev.tsx b/src/plugins/devCompanion.dev.tsx index c3d4d6a..8dbf59e 100644 --- a/src/plugins/devCompanion.dev.tsx +++ b/src/plugins/devCompanion.dev.tsx @@ -18,10 +18,11 @@ import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { showNotification } from "@api/Notifications"; +import { definePluginSettings } from "@api/settings"; import { Devs } from "@utils/constants"; import Logger from "@utils/Logger"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { filters, findAll, search } from "@webpack"; import { Menu } from "@webpack/common"; @@ -65,6 +66,14 @@ interface FindData { args: Array; } +const settings = definePluginSettings({ + notifyOnAutoConnect: { + description: "Wheter to notify when Dev Companion has automatically connected.", + type: OptionType.BOOLEAN, + default: true + } +}); + function parseNode(node: Node) { switch (node.type) { case "string": @@ -91,7 +100,7 @@ function initWs(isManual = false) { logger.info("Connected to WebSocket"); - showNotification({ + (settings.store.notifyOnAutoConnect || isManual) && showNotification({ title: "Dev Companion Connected", body: "Connected to WebSocket" }); @@ -241,6 +250,7 @@ export default definePlugin({ description: "Dev Companion Plugin", authors: [Devs.Ven], dependencies: ["ContextMenuAPI"], + settings, start() { initWs(); diff --git a/src/plugins/emoteCloner.tsx b/src/plugins/emoteCloner.tsx index 609ef08..afbb398 100644 --- a/src/plugins/emoteCloner.tsx +++ b/src/plugins/emoteCloner.tsx @@ -188,7 +188,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) = const src = itemHref ?? itemSrc; const isAnimated = new URL(src).pathname.endsWith(".gif"); - const group = findGroupChildrenByChildId("save-image", children); + const group = findGroupChildrenByChildId("copy-link", children); if (group && !group.some(child => child?.props?.id === "emote-cloner")) { group.push(( `,fakeNitroIntention=${intention}` }, { - match: /(?<=\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g, - replace: ',typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' + match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, + replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' }, { - match: /(?<=&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, - replace: (_, canUseExternal) => `(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` + match: /(&&!\i&&!)(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, + replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` } ] }, @@ -100,16 +100,16 @@ export default definePlugin({ find: "canUseAnimatedEmojis:function", predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true, replacement: { - match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g, - replace: (_, premiumCheck) => `,fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` + match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g, + replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` } }, { find: "canUseStickersEverywhere:function", predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true, replacement: { - match: /(?<=canUseStickersEverywhere:function\(\i\){)/, - replace: "return true;" + match: /canUseStickersEverywhere:function\(\i\){/, + replace: "$&return true;" }, }, { @@ -129,8 +129,8 @@ export default definePlugin({ "canStreamMidQuality" ].map(func => { return { - match: new RegExp(`(?<=${func}:function\\(\\i\\){)`), - replace: "return true;" + match: new RegExp(`${func}:function\\(\\i\\){`), + replace: "$&return true;" }; }) }, @@ -145,8 +145,8 @@ export default definePlugin({ { find: "canUseClientThemes:function", replacement: { - match: /(?<=canUseClientThemes:function\(\i\){)/, - replace: "return true;" + match: /canUseClientThemes:function\(\i\){/, + replace: "$&return true;" } } ], diff --git a/src/plugins/reverseImageSearch.tsx b/src/plugins/reverseImageSearch.tsx index 88c0b16..7a3d6d9 100644 --- a/src/plugins/reverseImageSearch.tsx +++ b/src/plugins/reverseImageSearch.tsx @@ -42,7 +42,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => const src = itemHref ?? itemSrc; - const group = findGroupChildrenByChildId("save-image", children); + const group = findGroupChildrenByChildId("copy-link", children); if (group && !group.some(child => child?.props?.id === "search-image")) { group.push((
- This is a hidden {ChannelTypesToChannelNames[type]} channel. + This is a {!PermissionStore.can(VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel. {channel.isNSFW() && {({ onMouseLeave, onMouseEnter }) => ( diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 318bad2..38ce52d 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -21,6 +21,7 @@ import "./style.css"; import { definePluginSettings } from "@api/settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; @@ -30,7 +31,8 @@ import HiddenChannelLockScreen, { setChannelBeginHeaderComponent, setEmojiCompon const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); -const VIEW_CHANNEL = 1n << 10n; +export const VIEW_CHANNEL = 1n << 10n; +const CONNECT = 1n << 20n; enum ShowMode { LockIcon, @@ -99,14 +101,14 @@ export default definePlugin({ replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&` }, { - // Make Discord think we are connected to a voice channel so it shows us inside it + // Prevent Discord from trying to connect to hidden channels match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/, replace: (_, channel) => `||$self.isHiddenChannel(${channel})` }, { - // Make Discord think we are connected to a voice channel so it shows us inside it + // Make Discord show inside the channel if clicking on a hidden or locked channel match: /(?<=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\);!__OVERLAY__&&\()/, - replace: (_, channel) => `$self.isHiddenChannel(${channel})||` + replace: (_, channel) => `$self.isHiddenChannel(${channel},true)||` } ] }, @@ -249,9 +251,49 @@ export default definePlugin({ replace: (m, component) => `${m}$self.setChannelBeginHeaderComponent(${component});` }, { - // Patch the header to only return allowed users and roles if it's a hidden channel (Like when it's used on the HiddenChannelLockScreen) + // Change the role permission check to CONNECT if the channel is locked + match: /ADMINISTRATOR\)\|\|(?=(.+?\((\i),\i\.\i\.)VIEW_CHANNEL)/, + replace: (m, permCheck, channel) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):` + }, + { + // Change the permissionOverwrite check to CONNECT if the channel is locked + match: /permissionOverwrites\[.+?\?(\i):.+?\i=(?=(.+?)VIEW_CHANNEL)/, + replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):` + }, + { + // Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen) match: /MANAGE_ROLES.{0,60}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?]}\)))/, - replace: (m, component, channel) => `${m} $self.isHiddenChannel(${channel})?${component}:` + replace: (m, component, channel) => { + // Export the channel for the users allowed component patch + component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,channel:${channel}`); + + return `${m} $self.isHiddenChannel(${channel},true)?${component}:`; + } + } + ] + }, + { + find: "().avatars),children", + replacement: [ + { + // Create a variable for the channel prop + match: /=(\i)\.maxUsers,/, + replace: (m, props) => `${m}channel=${props}.channel,` + }, + { + // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen + match: /\i>0(?=&&.{0,60}renderPopout)/, + replace: m => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)?true:${m})` + }, + { + // Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen + match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/, + replace: (_, amount) => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?0:1)` + }, + { + // Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen + match: /(?<="\+",)(\i)\+1/, + replace: (m, amount) => `$self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?"":${m}` } ] }, @@ -261,22 +303,22 @@ export default definePlugin({ { // Remove the divider and the open chat button for the HiddenChannelLockScreen match: /"more-options-popout"\)\);if\((?<=function \i\((\i)\).+?)/, - replace: (m, props) => `${m}(!$self.isHiddenChannel(${props}.channel)||${props}.inCall)&&` + replace: (m, props) => `${m}!${props}.inCall&&$self.isHiddenChannel(${props}.channel,true)){}else if(` }, { // Render our HiddenChannelLockScreen component instead of the main voice channel component match: /this\.renderVoiceChannelEffects.+?children:(?<=renderContent=function.+?)/, - replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?$self.HiddenChannelLockScreen(this.props.channel):" + replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?$self.HiddenChannelLockScreen(this.props.channel):" }, { // Disable gradients for the HiddenChannelLockScreen of voice channels match: /this\.renderVoiceChannelEffects.+?disableGradients:(?<=renderContent=function.+?)/, - replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)||" + replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)||" }, { // Disable useless components for the HiddenChannelLockScreen of voice channels match: /(?:{|,)render(?!Header|ExternalHeader).{0,30}?:(?<=renderContent=function.+?)(?!void)/g, - replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?null:" + replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?null:" } ] }, @@ -338,10 +380,25 @@ export default definePlugin({ }, { find: '.displayName="GuildChannelStore"', + replacement: [ + { + // Make GuildChannelStore contain hidden channels + match: /isChannelGated\(.+?\)(?=\|\|)/, + replace: m => `${m}||true` + }, + { + // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise + match: /(?<=getChannels=function\(\i)\).+?(?=return (\i)})/, + replace: (rest, channels) => `,shouldIncludeHidden=false${rest}${channels}=$self.resolveGuildChannels(${channels},shouldIncludeHidden);` + } + ] + }, + { + find: ".Messages.FORM_LABEL_MUTED", replacement: { - // Make GuildChannelStore contain hidden channels for users in voice channels to appear in the guild tooltip - match: /isChannelGated\(.+?\)(?=\|\|)/, - replace: m => `${m}||true` + // Make GuildChannelStore.getChannels return hidden channels + match: /(?<=getChannels\(\i)(?=\))/, + replace: ",true" } } ], @@ -349,13 +406,33 @@ export default definePlugin({ setEmojiComponent, setChannelBeginHeaderComponent, - isHiddenChannel(channel: Channel & { channelId?: string; }) { + isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) { if (!channel) return false; if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; - return !PermissionStore.can(VIEW_CHANNEL, channel); + return !PermissionStore.can(VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(CONNECT, channel); + }, + + resolveGuildChannels(channels: Record | string | number>, shouldIncludeHidden: boolean) { + if (shouldIncludeHidden) return channels; + + const res = {}; + for (const [key, maybeObjChannels] of Object.entries(channels)) { + if (!Array.isArray(maybeObjChannels)) { + res[key] = maybeObjChannels; + continue; + } + + res[key] ??= []; + + for (const objChannel of maybeObjChannels) { + if (objChannel.channel.id === null || !this.isHiddenChannel(objChannel.channel)) res[key].push(objChannel); + } + } + + return res; }, HiddenChannelLockScreen: (channel: any) => , diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css index 0f85b50..01903ce 100644 --- a/src/plugins/showHiddenChannels/style.css +++ b/src/plugins/showHiddenChannels/style.css @@ -103,4 +103,5 @@ .shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { margin-left: 10px; flex-wrap: wrap; + justify-content: center; } diff --git a/src/plugins/volumeBooster.desktop.ts b/src/plugins/volumeBooster.desktop.ts new file mode 100644 index 0000000..b77af57 --- /dev/null +++ b/src/plugins/volumeBooster.desktop.ts @@ -0,0 +1,86 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { definePluginSettings } from "@api/settings"; +import { makeRange } from "@components/PluginSettings/components"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + multiplier: { + description: "Volume Multiplier", + type: OptionType.SLIDER, + markers: makeRange(1, 5, 1), + default: 2, + stickToMarkers: true, + } +}); + +export default definePlugin({ + name: "VolumeBooster", + authors: [Devs.Nuckyz], + description: "Allows you to set the user and stream volume above the default maximum.", + settings, + + patches: [ + // Change the max volume for sliders to allow for values above 200 + ...[ + ".Messages.USER_VOLUME", + "currentVolume:" + ].map(find => ({ + find, + replacement: { + match: /(?<=maxValue:\i\.\i)\?(\d+?):(\d+?)(?=,)/, + replace: (_, higherMaxVolume, minorMaxVolume) => "" + + `?${higherMaxVolume}*$self.settings.store.multiplier` + + `:${minorMaxVolume}*$self.settings.store.multiplier` + } + })), + // Prevent Audio Context Settings sync from trying to sync with values above 200, changing them to 200 before we send to Discord + { + find: "AudioContextSettingsMigrated", + replacement: [ + { + match: /(?<=updateAsync\("audioContextSettings".{0,350}return \i\.volume=)\i(?=})/, + replace: "$&>200?200:$&" + }, + { + match: /(?<=Object\.entries\(\i\.localMutes\).+?volume:).+?(?=,)/, + replace: "$&>200?200:$&" + }, + { + match: /(?<=Object\.entries\(\i\.localVolumes\).+?volume:).+?(?=})/, + replace: "$&>200?200:$&" + } + ] + }, + // Prevent the MediaEngineStore from overwriting our LocalVolumes above 200 with the ones the Discord Audio Context Settings sync sends + { + find: '.displayName="MediaEngineStore"', + replacement: [ + { + match: /(\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/, + replace: (_, rest1, localVolume, syncVolume, rest2) => rest1 + + `(${localVolume}>200?void 0:${localVolume}=${syncVolume})` + + rest2 + + `${localVolume}??${syncVolume})` + } + ] + } + ], +}); diff --git a/src/plugins/volumeBooster.ts b/src/plugins/volumeBooster.ts deleted file mode 100644 index 3f692c7..0000000 --- a/src/plugins/volumeBooster.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/settings"; -import { makeRange } from "@components/PluginSettings/components"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; - -const settings = definePluginSettings({ - multiplier: { - description: "Volume Multiplier", - type: OptionType.SLIDER, - markers: makeRange(1, 5, 1), - default: 2, - stickToMarkers: true, - } -}); - -export default definePlugin({ - name: "VolumeBooster", - authors: [Devs.Nuckyz], - description: "Allows you to set the user and stream volume above the default maximum.", - settings, - - patches: [ - // Change the max volume for sliders to allow for values above 200 - ...[ - ".Messages.USER_VOLUME", - "currentVolume:" - ].map(find => ({ - find, - replacement: { - match: /(?<=maxValue:\i\.\i)\?(\d+?):(\d+?)(?=,)/, - replace: (_, higherMaxVolume, minorMaxVolume) => "" - + `?${higherMaxVolume}*$self.settings.store.multiplier` - + `:${minorMaxVolume}*$self.settings.store.multiplier` - } - })), - // Prevent Audio Context Settings sync from trying to sync with values above 200, changing them to 200 before we send to Discord - { - find: "AudioContextSettingsMigrated", - replacement: [ - { - match: /(?<=updateAsync\("audioContextSettings".{0,350}return \i\.volume=)\i(?=})/, - replace: "$&>200?200:$&" - }, - { - match: /(?<=Object\.entries\(\i\.localMutes\).+?volume:).+?(?=,)/, - replace: "$&>200?200:$&" - }, - { - match: /(?<=Object\.entries\(\i\.localVolumes\).+?volume:).+?(?=})/, - replace: "$&>200?200:$&" - } - ] - }, - // Prevent the MediaEngineStore from overwriting our LocalVolumes above 200 with the ones the Discord Audio Context Settings sync sends - { - find: '.displayName="MediaEngineStore"', - replacement: [ - { - match: /(?<=\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/, - replace: (_, localVolume, syncVolume, rest) => "" - + `(${localVolume}>200?void 0:${localVolume}=${syncVolume})` - + rest - + `${localVolume}??${syncVolume})` - } - ] - } - ], -}); -- cgit