ShowHiddenChannels: Stage and voice channels support (#469)
Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
		
							parent
							
								
									291f38115c
								
							
						
					
					
						commit
						992a77e76c
					
				
					 7 changed files with 362 additions and 142 deletions
				
			
		|  | @ -34,8 +34,8 @@ export default definePlugin({ | |||
|                         ";if(Vencord.Api.Notices.currentNotice)return false$&" | ||||
|                 }, | ||||
|                 { | ||||
|                     match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/, | ||||
|                     replace: '{if($1?.id=="VencordNotice")return ($1=null,Vencord.Api.Notices.nextNotice(),true);' | ||||
|                     match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/, | ||||
|                     replace: 'if($1?.id=="VencordNotice")return($1=null,Vencord.Api.Notices.nextNotice(),true);' | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|  |  | |||
|  | @ -18,18 +18,17 @@ | |||
| 
 | ||||
| import ErrorBoundary from "@components/ErrorBoundary"; | ||||
| import { LazyComponent } from "@utils/misc"; | ||||
| import { proxyLazy } from "@utils/proxyLazy"; | ||||
| import { formatDuration } from "@utils/text"; | ||||
| import { find, findByCode, findByPropsLazy, findLazy } from "@webpack"; | ||||
| import { moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; | ||||
| import { find, findByCode, findByPropsLazy } from "@webpack"; | ||||
| import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; | ||||
| import { Channel } from "discord-types/general"; | ||||
| 
 | ||||
| enum SortOrderTypesTyping { | ||||
| enum SortOrderTypes { | ||||
|     LATEST_ACTIVITY = 0, | ||||
|     CREATION_DATE = 1 | ||||
| } | ||||
| 
 | ||||
| enum ForumLayoutTypesTyping { | ||||
| enum ForumLayoutTypes { | ||||
|     DEFAULT = 0, | ||||
|     LIST = 1, | ||||
|     GRID = 2 | ||||
|  | @ -50,18 +49,31 @@ interface Tag { | |||
| 
 | ||||
| interface ExtendedChannel extends Channel { | ||||
|     defaultThreadRateLimitPerUser?: number; | ||||
|     defaultSortOrder?: SortOrderTypesTyping | null; | ||||
|     defaultForumLayout?: ForumLayoutTypesTyping; | ||||
|     defaultSortOrder?: SortOrderTypes | null; | ||||
|     defaultForumLayout?: ForumLayoutTypes; | ||||
|     defaultReactionEmoji?: DefaultReaction | null; | ||||
|     availableTags?: Array<Tag>; | ||||
| } | ||||
| 
 | ||||
| const ChatClasses = findByPropsLazy("chat", "chatContent"); | ||||
| const TagClasses = findLazy(m => typeof m.tags === "string" && Object.entries(m).length === 1); // Object exported with a single key called tags
 | ||||
| const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); | ||||
| const SortOrderTypes = findLazy(m => typeof m.LATEST_ACTIVITY === "number"); | ||||
| const ForumLayoutTypes = findLazy(m => typeof m.LIST === "number"); | ||||
| const ChannelFlags = findLazy(m => typeof m.REQUIRE_TAG === "number"); | ||||
| enum ChannelTypes { | ||||
|     GUILD_TEXT = 0, | ||||
|     GUILD_VOICE = 2, | ||||
|     GUILD_ANNOUNCEMENT = 5, | ||||
|     GUILD_STAGE_VOICE = 13, | ||||
|     GUILD_FORUM = 15 | ||||
| } | ||||
| 
 | ||||
| enum VideoQualityModes { | ||||
|     AUTO = 1, | ||||
|     FULL = 2 | ||||
| } | ||||
| 
 | ||||
| enum ChannelFlags { | ||||
|     PINNED = 1 << 1, | ||||
|     REQUIRE_TAG = 1 << 4 | ||||
| } | ||||
| 
 | ||||
| const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); | ||||
| const TagComponent = LazyComponent(() => find(m => { | ||||
|     if (typeof m !== "function") return false; | ||||
| 
 | ||||
|  | @ -70,23 +82,32 @@ const TagComponent = LazyComponent(() => find(m => { | |||
|     return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); | ||||
| })); | ||||
| const EmojiComponent = LazyComponent(() => findByCode('.jumboable?"jumbo":"default"')); | ||||
| // The component for the beggining of a channel, but we patched it so it only returns the allowed users and roles components for hidden channels
 | ||||
| const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); | ||||
| 
 | ||||
| const ChannelTypesToChannelNames = proxyLazy(() => ({ | ||||
| const ChannelTypesToChannelNames = { | ||||
|     [ChannelTypes.GUILD_TEXT]: "text", | ||||
|     [ChannelTypes.GUILD_ANNOUNCEMENT]: "announcement", | ||||
|     [ChannelTypes.GUILD_FORUM]: "forum" | ||||
| })); | ||||
|     [ChannelTypes.GUILD_FORUM]: "forum", | ||||
|     [ChannelTypes.GUILD_VOICE]: "voice", | ||||
|     [ChannelTypes.GUILD_STAGE_VOICE]: "stage" | ||||
| }; | ||||
| 
 | ||||
| const SortOrderTypesToNames = proxyLazy(() => ({ | ||||
| const SortOrderTypesToNames = { | ||||
|     [SortOrderTypes.LATEST_ACTIVITY]: "Latest activity", | ||||
|     [SortOrderTypes.CREATION_DATE]: "Creation date" | ||||
| })); | ||||
| }; | ||||
| 
 | ||||
| const ForumLayoutTypesToNames = proxyLazy(() => ({ | ||||
| const ForumLayoutTypesToNames = { | ||||
|     [ForumLayoutTypes.DEFAULT]: "Not set", | ||||
|     [ForumLayoutTypes.LIST]: "List view", | ||||
|     [ForumLayoutTypes.GRID]: "Gallery view" | ||||
| })); | ||||
| }; | ||||
| 
 | ||||
| const VideoQualityModesToNames = { | ||||
|     [VideoQualityModes.AUTO]: "Automatic", | ||||
|     [VideoQualityModes.FULL]: "720p" | ||||
| }; | ||||
| 
 | ||||
| // Icon from the modal when clicking a message link you don't have access to view
 | ||||
| const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg"; | ||||
|  | @ -104,97 +125,137 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { | |||
|         rateLimitPerUser, | ||||
|         defaultThreadRateLimitPerUser, | ||||
|         defaultSortOrder, | ||||
|         defaultReactionEmoji | ||||
|         defaultReactionEmoji, | ||||
|         bitrate, | ||||
|         rtcRegion, | ||||
|         videoQualityMode, | ||||
|         permissionOverwrites | ||||
|     } = channel; | ||||
| 
 | ||||
|     const membersToFetch: Array<string> = []; | ||||
| 
 | ||||
|     const guildOwnerId = GuildStore.getGuild(channel.guild_id).ownerId; | ||||
|     if (!GuildMemberStore.getMember(channel.guild_id, guildOwnerId)) membersToFetch.push(guildOwnerId); | ||||
| 
 | ||||
|     Object.values(permissionOverwrites).forEach(({ type, id: userId }) => { | ||||
|         if (type === 1) { | ||||
|             if (!GuildMemberStore.getMember(channel.guild_id, userId)) membersToFetch.push(userId); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     if (membersToFetch.length > 0) { | ||||
|         FluxDispatcher.dispatch({ | ||||
|             type: "GUILD_MEMBERS_REQUEST", | ||||
|             guildIds: [channel.guild_id], | ||||
|             userIds: membersToFetch | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={ChatClasses.chat + " " + "shc-lock-screen-container"}> | ||||
|             <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> | ||||
|         <div className={ChatScrollClasses.auto + " " + "shc-lock-screen-outer-container"}> | ||||
|             <div className="shc-lock-screen-container"> | ||||
|                 <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> | ||||
| 
 | ||||
|             <div className="shc-lock-screen-heading-container"> | ||||
|                 <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> | ||||
|                 {channel.isNSFW() && | ||||
|                     <Tooltip text="NSFW"> | ||||
|                         {({ onMouseLeave, onMouseEnter }) => ( | ||||
|                             <svg | ||||
|                                 onMouseLeave={onMouseLeave} | ||||
|                                 onMouseEnter={onMouseEnter} | ||||
|                                 className="shc-lock-screen-heading-nsfw-icon" | ||||
|                                 width="32" | ||||
|                                 height="32" | ||||
|                                 viewBox="0 0 48 48" | ||||
|                                 aria-hidden={true} | ||||
|                                 role="img" | ||||
|                             > | ||||
|                                 <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> | ||||
|                             </svg> | ||||
|                         )} | ||||
|                     </Tooltip> | ||||
|                 } | ||||
|             </div> | ||||
| 
 | ||||
|             <Text variant="text-lg/normal"> | ||||
|                 You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. | ||||
|                 {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} | ||||
|             </Text > | ||||
| 
 | ||||
|             {channel.isForumChannel() && topic && topic.length > 0 && ( | ||||
|                 <div className="shc-lock-screen-topic-container"> | ||||
|                     {Parser.parseTopic(topic, false, { channelId })} | ||||
|                 <div className="shc-lock-screen-heading-container"> | ||||
|                     <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> | ||||
|                     {channel.isNSFW() && | ||||
|                         <Tooltip text="NSFW"> | ||||
|                             {({ onMouseLeave, onMouseEnter }) => ( | ||||
|                                 <svg | ||||
|                                     onMouseLeave={onMouseLeave} | ||||
|                                     onMouseEnter={onMouseEnter} | ||||
|                                     className="shc-lock-screen-heading-nsfw-icon" | ||||
|                                     width="32" | ||||
|                                     height="32" | ||||
|                                     viewBox="0 0 48 48" | ||||
|                                     aria-hidden={true} | ||||
|                                     role="img" | ||||
|                                 > | ||||
|                                     <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> | ||||
|                                 </svg> | ||||
|                             )} | ||||
|                         </Tooltip> | ||||
|                     } | ||||
|                 </div> | ||||
|             )} | ||||
| 
 | ||||
|             {lastMessageId && | ||||
|                 <Text variant="text-md/normal"> | ||||
|                     Last {channel.isForumChannel() ? "post" : "message"} created: | ||||
|                     <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> | ||||
|                 </Text> | ||||
|             } | ||||
|                 {(!channel.isGuildVoice() && !channel.isGuildStageVoice()) && ( | ||||
|                     <Text variant="text-lg/normal"> | ||||
|                         You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. | ||||
|                         {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} | ||||
|                     </Text > | ||||
|                 )} | ||||
| 
 | ||||
|             {lastPinTimestamp && | ||||
|                 <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> | ||||
|             } | ||||
|             {(rateLimitPerUser ?? 0) > 0 && | ||||
|                 <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser! * 1000)}</Text> | ||||
|             } | ||||
|             {(defaultThreadRateLimitPerUser ?? 0) > 0 && | ||||
|                 <Text variant="text-md/normal"> | ||||
|                     Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser! * 1000)} | ||||
|                 </Text> | ||||
|             } | ||||
|             {(defaultAutoArchiveDuration ?? 0) > 0 && | ||||
|                 <Text variant="text-md/normal"> | ||||
|                     Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: | ||||
|                     {formatDuration(defaultAutoArchiveDuration! * 1000 * 60)} | ||||
|                 </Text> | ||||
|             } | ||||
|             {defaultForumLayout != null && | ||||
|                 <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> | ||||
|             } | ||||
|             {defaultSortOrder != null && | ||||
|                 <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> | ||||
|             } | ||||
|             {defaultReactionEmoji != null && | ||||
|                 <div className="shc-lock-screen-default-emoji-container"> | ||||
|                     <Text variant="text-md/normal">Default reaction emoji:</Text> | ||||
|                     <EmojiComponent node={{ | ||||
|                         type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", | ||||
|                         name: defaultReactionEmoji.emojiName ?? "", | ||||
|                         emojiId: defaultReactionEmoji.emojiId | ||||
|                     }} /> | ||||
|                 </div> | ||||
|             } | ||||
|             {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && | ||||
|                 <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> | ||||
|             } | ||||
|             {availableTags && availableTags.length > 0 && | ||||
|                 <div className="shc-lock-screen-tags-container"> | ||||
|                     <Text variant="text-lg/bold">Available tags:</Text> | ||||
|                     <div className={TagClasses.tags}> | ||||
|                         {availableTags.map(tag => <TagComponent tag={tag} />)} | ||||
|                 {channel.isForumChannel() && topic && topic.length > 0 && ( | ||||
|                     <div className="shc-lock-screen-topic-container"> | ||||
|                         {Parser.parseTopic(topic, false, { channelId })} | ||||
|                     </div> | ||||
|                 )} | ||||
| 
 | ||||
|                 {lastMessageId && | ||||
|                     <Text variant="text-md/normal"> | ||||
|                         Last {channel.isForumChannel() ? "post" : "message"} created: | ||||
|                         <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> | ||||
|                     </Text> | ||||
|                 } | ||||
| 
 | ||||
|                 {lastPinTimestamp && | ||||
|                     <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> | ||||
|                 } | ||||
|                 {(rateLimitPerUser ?? 0) > 0 && | ||||
|                     <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text> | ||||
|                 } | ||||
|                 {(defaultThreadRateLimitPerUser ?? 0) > 0 && | ||||
|                     <Text variant="text-md/normal"> | ||||
|                         Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser!, "seconds")} | ||||
|                     </Text> | ||||
|                 } | ||||
|                 {((channel.isGuildVoice() || channel.isGuildStageVoice()) && bitrate != null) && | ||||
|                     <Text variant="text-md/normal">Bitrate: {bitrate} bits</Text> | ||||
|                 } | ||||
|                 {rtcRegion !== undefined && | ||||
|                     <Text variant="text-md/normal">Region: {rtcRegion ?? "Automatic"}</Text> | ||||
|                 } | ||||
|                 {(channel.isGuildVoice() || channel.isGuildStageVoice()) && | ||||
|                     <Text variant="text-md/normal">Video quality mode: {VideoQualityModesToNames[videoQualityMode ?? VideoQualityModes.AUTO]}</Text> | ||||
|                 } | ||||
|                 {(defaultAutoArchiveDuration ?? 0) > 0 && | ||||
|                     <Text variant="text-md/normal"> | ||||
|                         Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: | ||||
|                         {" " + formatDuration(defaultAutoArchiveDuration!, "minutes")} | ||||
|                     </Text> | ||||
|                 } | ||||
|                 {defaultForumLayout != null && | ||||
|                     <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> | ||||
|                 } | ||||
|                 {defaultSortOrder != null && | ||||
|                     <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> | ||||
|                 } | ||||
|                 {defaultReactionEmoji != null && | ||||
|                     <div className="shc-lock-screen-default-emoji-container"> | ||||
|                         <Text variant="text-md/normal">Default reaction emoji:</Text> | ||||
|                         <EmojiComponent node={{ | ||||
|                             type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", | ||||
|                             name: defaultReactionEmoji.emojiName ?? "", | ||||
|                             emojiId: defaultReactionEmoji.emojiId | ||||
|                         }} /> | ||||
|                     </div> | ||||
|                 } | ||||
|                 {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && | ||||
|                     <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> | ||||
|                 } | ||||
|                 {availableTags && availableTags.length > 0 && | ||||
|                     <div className="shc-lock-screen-tags-container"> | ||||
|                         <Text variant="text-lg/bold">Available tags:</Text> | ||||
|                         <div className="shc-lock-screen-tags"> | ||||
|                             {availableTags.map(tag => <TagComponent tag={tag} />)} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 } | ||||
|                 <div className="shc-lock-screen-allowed-users-and-roles-container"> | ||||
|                     <Text variant="text-lg/bold">Allowed users and roles:</Text> | ||||
|                     <ChannelBeginHeader channel={channel} /> | ||||
|                 </div> | ||||
|             } | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  |  | |||
|  | @ -22,14 +22,15 @@ import { definePluginSettings } from "@api/settings"; | |||
| import ErrorBoundary from "@components/ErrorBoundary"; | ||||
| import { Devs } from "@utils/constants"; | ||||
| import definePlugin, { OptionType } from "@utils/types"; | ||||
| import { findByPropsLazy, findLazy } from "@webpack"; | ||||
| import { findByPropsLazy } from "@webpack"; | ||||
| import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; | ||||
| import { Channel } from "discord-types/general"; | ||||
| 
 | ||||
| import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; | ||||
| 
 | ||||
| const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); | ||||
| const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint"); | ||||
| 
 | ||||
| const VIEW_CHANNEL = 1n << 10n; | ||||
| 
 | ||||
| enum ShowMode { | ||||
|     LockIcon, | ||||
|  | @ -89,14 +90,29 @@ export default definePlugin({ | |||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             find: "VoiceChannel, transitionTo: Channel does not have a guildId", | ||||
|             replacement: [ | ||||
|                 { | ||||
|                     // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel
 | ||||
|                     match: /(?<=getCurrentClientVoiceChannelId\(\i\.guild_id\);if\()(?=.+?\((?<channel>\i)\))/, | ||||
|                     replace: "!$self.isHiddenChannel($<channel>)&&" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Make Discord think we are connected to a voice channel so it shows us inside it
 | ||||
|                     match: /(?=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\))/, | ||||
|                     replace: "||$self.isHiddenChannel($<channel>)" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Make Discord think we are connected to a voice channel so it shows us inside it
 | ||||
|                     match: /(?<=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\);!__OVERLAY__&&\()/, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)||" | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             find: "VoiceChannel.renderPopout: There must always be something to render", | ||||
|             replacement: [ | ||||
|                 // Do nothing when trying to join a voice channel if the channel is hidden
 | ||||
|                 { | ||||
|                     match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/, | ||||
|                     replace: "if($self.isHiddenChannel($<this>.props.channel))return;" | ||||
|                 }, | ||||
|                 // Render null instead of the buttons if the channel is hidden
 | ||||
|                 ...[ | ||||
|                     "renderEditButton", | ||||
|  | @ -120,11 +136,11 @@ export default definePlugin({ | |||
|         { | ||||
|             find: ".UNREAD_HIGHLIGHT", | ||||
|             predicate: () => settings.store.hideUnreads === true, | ||||
|             replacement: [{ | ||||
|             replacement: { | ||||
|                 // Hide unreads
 | ||||
|                 match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/, | ||||
|                 replace: "$self.isHiddenChannel($<props>.channel)?false:" | ||||
|             }] | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             find: ".UNREAD_HIGHLIGHT", | ||||
|  | @ -160,7 +176,7 @@ export default definePlugin({ | |||
|             // Hide New unreads box for hidden channels
 | ||||
|             find: '.displayName="ChannelListUnreadsStore"', | ||||
|             replacement: { | ||||
|                 match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, | ||||
|                 match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module
 | ||||
|                 replace: "&&!$self.isHiddenChannel($<channel>)" | ||||
|             } | ||||
|         }, | ||||
|  | @ -191,7 +207,7 @@ export default definePlugin({ | |||
|                 { | ||||
|                     match: /(?<=renderChat=function\(\){)/, | ||||
|                     replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);" | ||||
|                 }, | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         // Avoid trying to fetch messages from hidden channels
 | ||||
|  | @ -226,6 +242,86 @@ export default definePlugin({ | |||
|                 match: /(?<=\i:\(\)=>\i)(?=}.+?(?<component>\i)=function.{1,20}node,\i=\i.isInteracting)/, | ||||
|                 replace: ",hc1:()=>$<component>" // Blame Ven length check for the small name :pensive_cry:
 | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE", | ||||
|             replacement: [ | ||||
|                 { | ||||
|                     // Export the channel beggining header
 | ||||
|                     match: /(?<=\i:\(\)=>\i)(?=}.+?function (?<component>\i).{1,600}computePermissionsForRoles)/, | ||||
|                     replace: ",hc2:()=>$<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)
 | ||||
|                     match: /(?<=MANAGE_ROLES.{1,60}return)(?=\(.+?(?<component>\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(?<channel>\i)\.guild_id.+?roleColor.+?]}\)))/, | ||||
|                     replace: " $self.isHiddenChannel($<channel>)?$<component>:" | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             find: ".Messages.SHOW_CHAT", | ||||
|             replacement: [ | ||||
|                 { | ||||
|                     // Remove the divider and the open chat button for the HiddenChannelLockScreen
 | ||||
|                     match: /(?<=function \i\((?<props>\i)\).{1,1800}"more-options-popout"\)\);if\()/, | ||||
|                     replace: "(!$self.isHiddenChannel($<props>.channel)||$<props>.inCall)&&" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Render our HiddenChannelLockScreen component instead of the main voice channel component
 | ||||
|                     match: /(?<=renderContent=function.{1,1700}children:)/, | ||||
|                     replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?$self.HiddenChannelLockScreen(this.props.channel):" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Disable gradients for the HiddenChannelLockScreen of voice channels
 | ||||
|                     match: /(?<=renderContent=function.{1,1600}disableGradients:)/, | ||||
|                     replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)||" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Disable useless components for the HiddenChannelLockScreen of voice channels
 | ||||
|                     match: /(?<=renderContent=function.{1,800}render(?!Header).{0,30}:)(?!void)/g, | ||||
|                     replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?null:" | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             find: "Guild voice channel without guild id.", | ||||
|             replacement: [ | ||||
|                 { | ||||
|                     // Render our HiddenChannelLockScreen component instead of the main stage channel component
 | ||||
|                     match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1400}children:)(?=.{1,20}}\)}function)/, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)?$self.HiddenChannelLockScreen($<channel>):" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Disable useless components for the HiddenChannelLockScreen of stage channels
 | ||||
|                     match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1000}render(?!Header).{0,30}:)/g, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)?null:" | ||||
|                 }, | ||||
|                 // Prevent Discord from replacing our route if we aren't connected to the stage channel
 | ||||
|                 { | ||||
|                     match: /(?<=if\()(?=!\i&&!\i&&!\i.{1,80}(?<channel>\i)\.getGuildId\(\).{1,50}Guild voice channel without guild id\.)/, | ||||
|                     replace: "!$self.isHiddenChannel($<channel>)&&" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Disable gradients for the HiddenChannelLockScreen of stage channels
 | ||||
|                     match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}disableGradients:)/, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)||" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels
 | ||||
|                     match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}style:)/, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)?undefined:" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
 | ||||
|                     match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(?<channel>\i)\.guild_id)/, | ||||
|                     replace: "$self.isHiddenChannel($<channel>)?null:($&)" | ||||
|                 }, | ||||
|                 { | ||||
|                     // Remove the open chat button for the HiddenChannelLockScreen
 | ||||
|                     match: /(?<=null,)(?=.{1,120}channelId:(?<channel>\i)\.id,.+?toggleRequestToSpeakSidebar:\i,iconClassName:\i\(\)\.buttonIcon)/, | ||||
|                     replace: "!$self.isHiddenChannel($<channel>)&&" | ||||
|                 } | ||||
|             ], | ||||
|         } | ||||
|     ], | ||||
| 
 | ||||
|  | @ -235,7 +331,7 @@ export default definePlugin({ | |||
|         if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); | ||||
|         if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; | ||||
| 
 | ||||
|         return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel); | ||||
|         return !PermissionStore.can(VIEW_CHANNEL, channel); | ||||
|     }, | ||||
| 
 | ||||
|     HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />, | ||||
|  |  | |||
|  | @ -1,9 +1,18 @@ | |||
| .shc-lock-screen-outer-container { | ||||
|     background-color: var(--background-primary); | ||||
|     overflow: hidden scroll; | ||||
|     flex: 1 1 auto; | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
|     min-height: 100%; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-container > * { | ||||
|  | @ -34,14 +43,14 @@ | |||
|     color: var(--text-normal); | ||||
|     background-color: var(--background-secondary); | ||||
|     border-radius: 5px; | ||||
|     padding: 5px; | ||||
|     padding: 10px; | ||||
|     max-width: 70vw; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-tags-container { | ||||
|     background-color: var(--background-secondary); | ||||
|     border-radius: 5px; | ||||
|     padding: 5px; | ||||
|     padding: 10px; | ||||
|     max-width: 70vw; | ||||
| } | ||||
| 
 | ||||
|  | @ -49,8 +58,12 @@ | |||
|     margin: inherit; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-tags-container > [class^="tags"] { | ||||
| .shc-lock-screen-tags { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 8px; | ||||
| } | ||||
| 
 | ||||
| .shc-evenodd-fill-current-color { | ||||
|  | @ -76,3 +89,18 @@ | |||
|     padding: 3px 4px; | ||||
|     margin-left: 5px; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-allowed-users-and-roles-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     background-color: var(--background-secondary); | ||||
|     border-radius: 5px; | ||||
|     padding: 10px; | ||||
|     max-width: 70vw; | ||||
| } | ||||
| 
 | ||||
| .shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { | ||||
|     margin-left: 10px; | ||||
|     flex-wrap: wrap; | ||||
| } | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; | |||
| import { Devs } from "@utils/constants"; | ||||
| import definePlugin, { OptionType } from "@utils/types"; | ||||
| import { findByCodeLazy } from "@webpack"; | ||||
| import { GuildMemberStore, React } from "@webpack/common"; | ||||
| import { GuildMemberStore, React, RelationshipStore } from "@webpack/common"; | ||||
| 
 | ||||
| const Avatar = findByCodeLazy(".Positions.TOP,spacing:"); | ||||
| 
 | ||||
|  | @ -76,8 +76,8 @@ export default definePlugin({ | |||
|         { | ||||
|             find: ",\"SEVERAL_USERS_TYPING\",\"", | ||||
|             replacement: { | ||||
|                 match: /(\i)\((\i),("SEVERAL_USERS_TYPING"),".+?"\)/, | ||||
|                 replace: "$1($2,$3,\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" | ||||
|                 match: /(?<="SEVERAL_USERS_TYPING",)".+?"/, | ||||
|                 replace: '"**!!{a}!!**, **!!{b}!!**, and {c} others are typing..."' | ||||
|             }, | ||||
|             predicate: () => settings.store.alternativeFormatting | ||||
|         }, | ||||
|  | @ -98,7 +98,7 @@ export default definePlugin({ | |||
| 
 | ||||
|         let element = 0; | ||||
| 
 | ||||
|         return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]}/> : c); | ||||
|         return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]} /> : c); | ||||
|     }, | ||||
| 
 | ||||
|     TypingUser: ErrorBoundary.wrap(({ user, guildId }) => { | ||||
|  | @ -111,9 +111,9 @@ export default definePlugin({ | |||
|             {settings.store.showAvatars && <div style={{ marginTop: "4px" }}> | ||||
|                 <Avatar | ||||
|                     size={Avatar.Sizes.SIZE_16} | ||||
|                     src={user.getAvatarURL(guildId, 128)}/> | ||||
|                     src={user.getAvatarURL(guildId, 128)} /> | ||||
|             </div>} | ||||
|             {GuildMemberStore.getNick(guildId!, user.id) || user.username} | ||||
|             {GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username} | ||||
|         </strong>; | ||||
|     }, { noop: true }) | ||||
| }); | ||||
|  |  | |||
|  | @ -37,25 +37,58 @@ export const wordsToPascal = (words: string[]) => | |||
| export const wordsToTitle = (words: string[]) => | ||||
|     words.map(w => w[0].toUpperCase() + w.slice(1)).join(" "); | ||||
| 
 | ||||
| const units = ["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const; | ||||
| type Units = typeof units[number]; | ||||
| 
 | ||||
| function getUnitStr(unit: Units, isOne: boolean, short: boolean) { | ||||
|     if (short === false) return isOne ? unit.slice(0, -1) : unit; | ||||
| 
 | ||||
|     return unit[0]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Forms milliseconds into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" | ||||
|  * @param ms Milliseconds | ||||
|  * Forms time into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" | ||||
|  * @param time The time on the specified unit | ||||
|  * @param unit The unit the time is on | ||||
|  * @param short Whether to use short units like "d" instead of "days" | ||||
|  */ | ||||
| export function formatDuration(ms: number, short: boolean = false) { | ||||
|     const dur = moment.duration(ms); | ||||
|     return (["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const).reduce((res, unit) => { | ||||
|         const x = dur[unit](); | ||||
|         if (x > 0 || res.length) { | ||||
|             if (res.length) | ||||
|                 res += unit === "seconds" ? " and " : ", "; | ||||
| export function formatDuration(time: number, unit: Units, short: boolean = false) { | ||||
|     const dur = moment.duration(time, unit); | ||||
| 
 | ||||
|             const unitStr = short | ||||
|                 ? unit[0] | ||||
|                 : x === 1 ? unit.slice(0, -1) : unit; | ||||
|     let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit })); | ||||
| 
 | ||||
|             res += `${x} ${unitStr}`; | ||||
|     let amountsToBeRemoved = 0; | ||||
| 
 | ||||
|     outer: | ||||
|     for (let i = 0; i < unitsAmounts.length; i++) { | ||||
|         if (unitsAmounts[i].amount === 0 || !(i + 1 < unitsAmounts.length)) continue; | ||||
|         for (let v = i + 1; v < unitsAmounts.length; v++) { | ||||
|             if (unitsAmounts[v].amount !== 0) continue outer; | ||||
|         } | ||||
|         return res; | ||||
|     }, "").replace(/((,|and) \b0 \w+)+$/, "") || "now"; | ||||
| 
 | ||||
|         amountsToBeRemoved = unitsAmounts.length - (i + 1); | ||||
|     } | ||||
|     unitsAmounts = amountsToBeRemoved === 0 ? unitsAmounts : unitsAmounts.slice(0, -amountsToBeRemoved); | ||||
| 
 | ||||
|     const daysAmountIndex = unitsAmounts.findIndex(({ unit }) => unit === "days"); | ||||
|     if (daysAmountIndex !== -1) { | ||||
|         const daysAmount = unitsAmounts[daysAmountIndex]; | ||||
| 
 | ||||
|         const daysMod = daysAmount.amount % 7; | ||||
|         if (daysMod === 0) unitsAmounts.splice(daysAmountIndex, 1); | ||||
|         else daysAmount.amount = daysMod; | ||||
|     } | ||||
| 
 | ||||
|     let res: string = ""; | ||||
|     while (unitsAmounts.length) { | ||||
|         const { amount, unit } = unitsAmounts.shift()!; | ||||
| 
 | ||||
|         if (res.length) res += unitsAmounts.length ? ", " : " and "; | ||||
| 
 | ||||
|         if (amount > 0 || res.length) { | ||||
|             res += `${amount} ${getUnitStr(unit, amount === 1, short)}`; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return res.length ? res : `0 ${getUnitStr(unit, false, short)}`; | ||||
| } | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import Logger from "@utils/Logger"; | |||
| import { canonicalizeReplacement } from "@utils/patches"; | ||||
| import { PatchReplacement } from "@utils/types"; | ||||
| 
 | ||||
| import { traceFunction } from "../debug/Tracer"; | ||||
| import { _initWebpack } from "."; | ||||
| 
 | ||||
| let webpackChunk: any[]; | ||||
|  | @ -132,6 +133,7 @@ function patchPush() { | |||
| 
 | ||||
|                 for (let i = 0; i < patches.length; i++) { | ||||
|                     const patch = patches[i]; | ||||
|                     const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace)); | ||||
|                     if (patch.predicate && !patch.predicate()) continue; | ||||
| 
 | ||||
|                     if (code.includes(patch.find)) { | ||||
|  | @ -146,7 +148,7 @@ function patchPush() { | |||
|                             canonicalizeReplacement(replacement, patch.plugin); | ||||
| 
 | ||||
|                             try { | ||||
|                                 const newCode = code.replace(replacement.match, replacement.replace as string); | ||||
|                                 const newCode = executePatch(replacement.match, replacement.replace as string); | ||||
|                                 if (newCode === code && !patch.noWarn) { | ||||
|                                     logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); | ||||
|                                     if (IS_DEV) { | ||||
|  | @ -187,7 +189,7 @@ function patchPush() { | |||
|                                     } | ||||
| 
 | ||||
|                                     logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context); | ||||
|                                     logger.errorCustomFmt(...Logger.makeTitle("white", "After"), context); | ||||
|                                     logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext); | ||||
|                                     const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); | ||||
|                                     logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); | ||||
|                                 } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue