ShowHiddenChannels: More improvements (#454)
- Remove buttons like the invite button when hovering hidden channels (as they do not work correctly) - Make hideUnreads false work with HiddenIconWithMutedStyle - migrate to definePluginSettings - Change hardcoded constants to webpack gathering - Clean up some patches - Other minor things - Make all patches use lookbehind for cleaner replacements (and better performance too lmao) - Handle trying to connect to hidden channels
This commit is contained in:
parent
f19504f828
commit
d628924b59
|
@ -17,57 +17,56 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Settings } from "@api/settings";
|
import { definePluginSettings } from "@api/settings";
|
||||||
import { Badge } from "@components/Badge";
|
import { Badge } from "@components/Badge";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
|
import { proxyLazy } from "@utils/proxyLazy";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
||||||
|
import { Channel } from "discord-types/general";
|
||||||
|
|
||||||
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
|
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
|
||||||
|
const Permissions = findByPropsLazy("VIEW_CHANNEL", "ADMINISTRATOR");
|
||||||
|
const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM");
|
||||||
|
|
||||||
const VIEW_CHANNEL = 1024n;
|
const ChannelTypesToChannelName = proxyLazy(() => ({
|
||||||
|
|
||||||
enum ChannelTypes {
|
|
||||||
GUILD_TEXT = 0,
|
|
||||||
GUILD_ANNOUNCEMENT = 5,
|
|
||||||
GUILD_FORUM = 15
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChannelTypesToChannelName = {
|
|
||||||
[ChannelTypes.GUILD_TEXT]: "TEXT",
|
[ChannelTypes.GUILD_TEXT]: "TEXT",
|
||||||
[ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT",
|
[ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT",
|
||||||
[ChannelTypes.GUILD_FORUM]: "FORUM"
|
[ChannelTypes.GUILD_FORUM]: "FORUM"
|
||||||
};
|
}));
|
||||||
|
|
||||||
enum ShowMode {
|
enum ShowMode {
|
||||||
LockIcon,
|
LockIcon,
|
||||||
HiddenIconWithMutedStyle
|
HiddenIconWithMutedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
hideUnreads: {
|
||||||
|
description: "Hide Unreads",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
showMode: {
|
||||||
|
description: "The mode used to display hidden channels.",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{ label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true },
|
||||||
|
{ label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle },
|
||||||
|
],
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ShowHiddenChannels",
|
name: "ShowHiddenChannels",
|
||||||
description: "Show channels that you do not have access to view.",
|
description: "Show channels that you do not have access to view.",
|
||||||
authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux],
|
authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux],
|
||||||
options: {
|
settings,
|
||||||
hideUnreads: {
|
|
||||||
description: "Hide Unreads",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: true,
|
|
||||||
restartNeeded: true
|
|
||||||
},
|
|
||||||
showMode: {
|
|
||||||
description: "The mode used to display hidden channels.",
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
options: [
|
|
||||||
{ label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true },
|
|
||||||
{ label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle },
|
|
||||||
],
|
|
||||||
restartNeeded: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
|
// RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
|
||||||
|
@ -75,96 +74,125 @@ export default definePlugin({
|
||||||
// These replacements only change the necessary CannotShow's
|
// These replacements only change the necessary CannotShow's
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?,/,
|
match: /(?<=isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\..+?(?=,)/,
|
||||||
replace: "$<restOfFunction>$<renderLevelExpression>,"
|
replace: "this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show"
|
||||||
|
},
|
||||||
|
// Move isChannelGatedAndVisible renderLevel logic to the bottom to not show hidden channels in case they are muted
|
||||||
|
{
|
||||||
|
match: /(?<=(?<permissionCheck>if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{)if\(this\.id===\i\).+?};)(?<isChannelGatedAndVisibleCondition>if\(!\i\.\i\.isChannelGatedAndVisible\(.+?})(?<restOfFunction>.+?)(?=return{renderLevel:\i\.Show.{1,40}return \i)/,
|
||||||
|
replace: "$<restOfFunction>$<permissionCheck>$<isChannelGatedAndVisibleCondition>}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>activeJoinedRelevantThreads.{1,100}renderLevel:(?<RenderLevels>\i)\.Show.+?renderLevel:).+?,/,
|
match: /(?<=renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/,
|
||||||
replace: "$<restOfFunction>$<RenderLevels>.Show,"
|
replace: "$<renderLevelExpression>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\.CannotShow/,
|
match: /(?<=activeJoinedRelevantThreads.+?renderLevel:.+?,threadIds:\i\(this.record.+?renderLevel:)(?<RenderLevels>\i)\..+?(?=,)/,
|
||||||
replace: "$<restOfFunction>this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show"
|
replace: "$<RenderLevels>.Show"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>getRenderLevel=function.+?return).+?\?(?<renderLevelExpression>.+?):\i\.CannotShow}/,
|
match: /(?<=getRenderLevel=function.+?return ).+?\?(?<renderLevelExpressionWithoutPermCheck>.+?):\i\.CannotShow(?=})/,
|
||||||
replace: "$<restOfFunction> $<renderLevelExpression>}"
|
replace: "$<renderLevelExpressionWithoutPermCheck>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// inside the onMouseClick handler, we check if the channel is hidden and open the modal if it is
|
// inside the onMouseDown handler, we check if the channel is hidden and open the modal if it is
|
||||||
find: ".handleThreadsPopoutClose();",
|
find: "VoiceChannel.renderPopout: There must always be something to render",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(?<this>\i)\.handleThreadsPopoutClose\(\);/,
|
{
|
||||||
replace: "if(arguments[0].button===0&&$self.channelSelected($<this>?.props?.channel))return;$&"
|
match: /(?=(?<this>\i)\.handleThreadsPopoutClose\(\))/,
|
||||||
}
|
replace: "if($self.isHiddenChannel($<this>.props.channel)&&arguments[0].button===0){"
|
||||||
},
|
+ "$self.onHiddenChannelSelected($<this>.props.channel);"
|
||||||
{
|
+ "return;"
|
||||||
find: ".UNREAD_HIGHLIGHT",
|
+ "}"
|
||||||
predicate: () => Settings.plugins.ShowHiddenChannels.hideUnreads === true,
|
},
|
||||||
replacement: [{
|
// Do nothing when trying to join a voice channel if the channel is hidden
|
||||||
// Hide unreads
|
{
|
||||||
match: /(?<restOfFunction>\i\.connected,)(?<hasUnread>\i)=(?<props>\i).unread/,
|
match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/,
|
||||||
replace: "$<restOfFunction>$<hasUnread>=$self.isHiddenChannel($<props>.channel)?false:$<props>.unread"
|
replace: "if($self.isHiddenChannel($<this>.props.channel))return;"
|
||||||
}]
|
},
|
||||||
|
// Render null instead of the buttons if the channel is hidden
|
||||||
|
...[
|
||||||
|
"renderEditButton",
|
||||||
|
"renderInviteButton",
|
||||||
|
"renderOpenChatButton"
|
||||||
|
].map(func => ({
|
||||||
|
match: new RegExp(`(?<=\\i\\.${func}=function\\(\\){)`, "g"), // Global because Discord has multiple declarations of the same functions
|
||||||
|
replace: "if($self.isHiddenChannel(this.props.channel))return null;"
|
||||||
|
}))
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY",
|
find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY",
|
||||||
predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.LockIcon,
|
predicate: () => settings.store.showMode === ShowMode.LockIcon,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Lock Icon
|
// Lock Icon
|
||||||
match: /switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\)\(\i\)/,
|
match: /(?=switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\))/,
|
||||||
replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;$&"
|
replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".UNREAD_HIGHLIGHT",
|
find: ".UNREAD_HIGHLIGHT",
|
||||||
predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.HiddenIconWithMutedStyle,
|
predicate: () => settings.store.hideUnreads === true,
|
||||||
|
replacement: [{
|
||||||
|
// Hide unreads
|
||||||
|
match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/,
|
||||||
|
replace: "$self.isHiddenChannel($<props>.channel)?false:"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".UNREAD_HIGHLIGHT",
|
||||||
|
predicate: () => settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
|
||||||
replacement: [
|
replacement: [
|
||||||
// Make the channel appear as muted if it's hidden
|
// Make the channel appear as muted if it's hidden
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>\i\.name,)(?<isMuted>\i)=(?<props>\i).muted/,
|
match: /(?<=\i\.name,\i=)(?=(?<props>\i)\.muted)/,
|
||||||
replace: "$<restOfFunction>$<isMuted>=$self.isHiddenChannel($<props>.channel)?true:$<props>.muted"
|
replace: "$self.isHiddenChannel($<props>.channel)?true:"
|
||||||
},
|
},
|
||||||
// Add the hidden eye icon if the channel is hidden
|
// Add the hidden eye icon if the channel is hidden
|
||||||
{
|
{
|
||||||
match: /channel:(?<channel>\i),.+?\.channelName.+?\.children.+?:null/,
|
match: /(?<=(?<channel>\i)=\i\.channel,.+?\(\)\.children.+?:null)/,
|
||||||
replace: "$&,$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null"
|
replace: ",$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null"
|
||||||
},
|
},
|
||||||
// Make voice channels also appear as muted if they are muted
|
// Make voice channels also appear as muted if they are muted
|
||||||
{
|
{
|
||||||
match: /(?<restOfFunction>.wrapper:\i\(\).notInteractive,)(?<secondRestOfFunction>.+?)(?<isMutedClassExpression>(?<isMuted>\i)\?\i\.MUTED:)/,
|
match: /(?<=\i\(\)\.wrapper:\i\(\)\.notInteractive,)(?<otherClasses>.+?)(?<mutedClassExpression>(?<isMuted>\i)\?\i\.MUTED)/,
|
||||||
replace: "$<restOfFunction>$<isMutedClassExpression>\"\",$<secondRestOfFunction>$<isMuted>?\"\":"
|
replace: "$<mutedClassExpression>:\"\",$<otherClasses>$<isMuted>?\"\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
|
||||||
|
{
|
||||||
|
find: ".UNREAD_HIGHLIGHT",
|
||||||
|
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=(?<channel>\i)=\i\.channel,.+?\.LOCKED:\i)/,
|
||||||
|
replace: "&&!($self.settings.store.hideUnreads===false&&$self.isHiddenChannel($<channel>))"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Hide New unreads box for hidden channels
|
// Hide New unreads box for hidden channels
|
||||||
find: '.displayName="ChannelListUnreadsStore"',
|
find: '.displayName="ChannelListUnreadsStore"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<restOfFunction>return null!=(?<channel>\i))(?<secondRestOfFunction>&&null!=\i\.getGuildId\(\).{1,120}hasRelevantUnread\(\i\)\))/,
|
match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/,
|
||||||
replace: "$<restOfFunction>&&!$self.isHiddenChannel($<channel>)$<secondRestOfFunction>"
|
replace: "&&!$self.isHiddenChannel($<channel>)"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
isHiddenChannel(channel) {
|
isHiddenChannel(channel: Channel & { channelId?: string; }) {
|
||||||
if (!channel) return false;
|
if (!channel) return false;
|
||||||
|
|
||||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||||
|
|
||||||
return !PermissionStore.can(VIEW_CHANNEL, channel);
|
return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel);
|
||||||
},
|
},
|
||||||
|
|
||||||
channelSelected(channel) {
|
onHiddenChannelSelected(channel: Channel) {
|
||||||
if (!channel) return false;
|
|
||||||
|
|
||||||
const isHidden = this.isHiddenChannel(channel);
|
|
||||||
|
|
||||||
// Check for type, otherwise it would attempt to show the modal for stage channels
|
// Check for type, otherwise it would attempt to show the modal for stage channels
|
||||||
if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type) && isHidden) {
|
if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type)) {
|
||||||
openModal(modalProps => (
|
openModal(modalProps => (
|
||||||
<ModalRoot size={ModalSize.SMALL} {...modalProps}>
|
<ModalRoot size={ModalSize.SMALL} {...modalProps}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
|
@ -174,7 +202,7 @@ export default definePlugin({
|
||||||
{channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />}
|
{channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent style={{ marginBottom: 10, marginTop: 10, marginRight: 8, marginLeft: 8 }}>
|
<ModalContent style={{ margin: "10px 8px" }}>
|
||||||
<Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text>
|
<Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text>
|
||||||
{(channel.topic ?? "").length > 0 && (
|
{(channel.topic ?? "").length > 0 && (
|
||||||
<>
|
<>
|
||||||
|
@ -211,7 +239,6 @@ export default definePlugin({
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return isHidden;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
LockIcon: () => (
|
LockIcon: () => (
|
||||||
|
|
Loading…
Reference in a new issue