Plugin

ShowHiddenChannels

Show channels that you do not have access to view.

Servers Utility
index.tsx
Download

Source

src/plugins/showHiddenChannels/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import "./style.css";
8
9import { definePluginSettings } from "@api/Settings";
10import ErrorBoundary from "@components/ErrorBoundary";
11import { Devs } from "@utils/constants";
12import { classNameFactory } from "@utils/css";
13import { classes } from "@utils/misc";
14import definePlugin, { OptionType } from "@utils/types";
15import type { Channel, Role } from "@vencord/discord-types";
16import { findCssClassesLazy } from "@webpack";
17import { ChannelStore, PermissionsBits, PermissionStore, Tooltip } from "@webpack/common";
18
19import HiddenChannelLockScreen, { setChannelBeginHeader } from "./components/HiddenChannelLockScreen";
20
21export const cl = classNameFactory("vc-shc-");
22
23const ChannelListClasses = findCssClassesLazy("modeSelected", "modeMuted", "unread", "icon");
24
25const enum ShowMode {
26 LockIcon,
27 HiddenIconWithMutedStyle
28}
29
30const CONNECT = 1n << 20n;
31
32export const settings = definePluginSettings({
33 hideUnreads: {
34 description: "Hide Unreads",
35 type: OptionType.BOOLEAN,
36 default: true,
37 restartNeeded: true
38 },
39 showMode: {
40 description: "The mode used to display hidden channels.",
41 type: OptionType.SELECT,
42 options: [
43 { label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true },
44 { label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle },
45 ],
46 restartNeeded: true
47 },
48 defaultAllowedUsersAndRolesDropdownState: {
49 description: "Whether the allowed users and roles dropdown on hidden channels should be open by default",
50 type: OptionType.BOOLEAN,
51 default: true
52 }
53});
54
55function isUncategorized(objChannel: { channel: Channel; comparator: number; }) {
56 return objChannel.channel.id === "null" && objChannel.channel.name === "Uncategorized" && objChannel.comparator === -1;
57}
58
59export default definePlugin({
60 name: "ShowHiddenChannels",
61 description: "Show channels that you do not have access to view.",
62 tags: ["Servers", "Utility"],
63 authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux, Devs.dzshn],
64 settings,
65
66 patches: [
67 {
68 // RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
69 find: &#039;"placeholder-channel-id"&#039;,
70 replacement: [
71 // Remove the special logic for channels we don't have access to
72 {
73 match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\[\]}}/,
74 replace: ""
75 },
76 // Do not check for unreads when selecting the render level if the channel is hidden
77 {
78 match: /(?<=&&)(?=!\i\.\i\.hasUnread\(this\.record\.id\))/,
79 replace: "$self.isHiddenChannel(this.record)||"
80 },
81 // Make channels we dont have access to be the same level as normal ones
82 {
83 match: /(this\.record\)\?{renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
84 replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}`
85 },
86 // Remove permission checking for getRenderLevel function
87 {
88 match: /(getRenderLevel\(\i\){.+?return)!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,this\.record\)\|\|/,
89 replace: (_, rest) => `${rest} `
90 }
91 ]
92 },
93 {
94 find: "VoiceChannel, transitionTo: Channel does not have a guildId",
95 replacement: [
96 {
97 // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel
98 match: /(?<=getIgnoredUsersForVoiceChannel\((\i)\.id\)[^;]+?;return\()/,
99 replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
100 },
101 {
102 // Prevent Discord from trying to connect to hidden voice channels
103 match: /(?=\|\|\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
104 replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
105 },
106 {
107 // Make Discord show inside the channel if clicking on a hidden or locked channel
108 match: /!__OVERLAY__&&\((?<=selectVoiceChannel\((\i)\.id\).+?)/,
109 replace: (m, channel) => `${m}$self.isHiddenChannel(${channel},true)||`
110 }
111 ]
112 },
113 // Prevent Discord from trying to connect to hidden stage channels
114 {
115 find: ".AUDIENCE),{isSubscriptionGated",
116 replacement: {
117 match: /(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
118 replace: (m, channel) => `${m}||$self.isHiddenChannel(${channel})`
119 }
120 },
121 {
122 find: &#039;tutorialId:"instant-invite"&#039;,
123 replacement: [
124 // Render null instead of the buttons if the channel is hidden
125 ...[
126 "renderEditButton",
127 "renderInviteButton",
128 ].map(func => ({
129 match: new RegExp(`(?<=${func}\\(\\){)`, "g"), class="ts-cmt">// Global because Discord has multiple declarations of the same functions
130 replace: "if($self.isHiddenChannel(this?.props?.channel))return null;"
131 }))
132 ]
133 },
134 {
135 find: "VoiceChannel.renderPopout: There must always be something to render",
136 all: true,
137 // Render null instead of the buttons if the channel is hidden
138 replacement: {
139 match: /(?<=renderOpenChatButton(?:",|=)\(\)=>{)/,
140 replace: "if($self.isHiddenChannel(this?.props?.channel))return null;"
141 }
142 },
143 {
144 find: "#{intl::CHANNEL_TOOLTIP_DIRECTORY}",
145 predicate: () => settings.store.showMode === ShowMode.LockIcon,
146 replacement: {
147 // Lock Icon
148 match: /(?<=(\i)\.isNSFW\(\);)switch\(\i\.type\).{0,15}\.GUILD_ANNOUNCEMENT/,
149 replace: (m, channel) => `if($self.isHiddenChannel(${channel}))return $self.LockIcon;${m}`
150 }
151 },
152 {
153 find: "UNREAD_IMPORTANT:",
154 predicate: () => settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
155 replacement: [
156 // Make the channel appear as muted if it's hidden
157 {
158 match: /Children\.count.+?;(?=return\(0,\i\.jsxs?\)\(\i\.\i,{focusTarget:)(?<={channel:(\i),name:\i,muted:(\i).+?;)/,
159 replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
160 },
161 // Add the hidden eye icon if the channel is hidden
162 {
163 match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/,
164 replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
165 },
166 // Make voice channels also appear as muted if they are muted
167 {
168 match: /(?<=\?\i\.\i:\i\.\i,)(.{0,150}?)if\((\i)(?:\)return |\?)(\i\.MUTED)/,
169 replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
170 }
171 ]
172 },
173 {
174 find: "UNREAD_IMPORTANT:",
175 replacement: [
176 {
177 // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
178 predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
179 match: /(?<=\.LOCKED;if\()(?<={channel:(\i).+?)/,
180 replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
181 },
182 {
183 // Hide unreads
184 predicate: () => settings.store.hideUnreads === true,
185 match: /Children\.count.+?;(?=return\(0,\i\.jsxs?\)\(\i\.\i,{focusTarget:)(?<={channel:(\i),name:\i,.+?unread:(\i).+?)/,
186 replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
187 }
188 ]
189 },
190 {
191 // Hide the new version of unreads box for hidden channels
192 find: &#039;"ChannelListUnreadsStore"&#039;,
193 replacement: {
194 match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
195 replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
196 }
197 },
198 {
199 // Make the old version of unreads box not visible for hidden channels
200 find: "renderBottomUnread(){",
201 replacement: {
202 match: /(?<=!0\))(?=&&\(0,\i\.\i\)\((\i\.record)\))/,
203 replace: "&&!$self.isHiddenChannel($1)"
204 }
205 },
206 {
207 // Make the state of the old version of unreads box not include hidden channels
208 find: "GUILD_EVENT)}),[",
209 replacement: {
210 match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
211 replace: "&&!$self.isHiddenChannel($1)"
212 }
213 },
214 // Only render the channel header and buttons that work when transitioning to a hidden channel
215 {
216 find: "Missing channel in Channel.renderHeaderToolbar",
217 replacement: [
218 {
219 match: /renderHeaderToolbar(?:",|=)\(\)=>{.+?case \i\.\i\.GUILD_TEXT:(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
220 replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
221 },
222 {
223 match: /renderHeaderToolbar(?:",|=)\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
224 replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
225 },
226 {
227 match: /renderMobileToolbar(?:",|=)\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
228 replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;`
229 },
230 {
231 match: /(?<=renderHeaderBar(?:",|=)\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/,
232 replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
233 },
234 {
235 match: /(?<=renderSidebar\(\){)/,
236 replace: "if($self.isHiddenChannel(this?.props?.channel))return null;"
237 },
238 {
239 match: /(?<=renderChat\(\){)/,
240 replace: "if($self.isHiddenChannel(this?.props?.channel))return $self.HiddenChannelLockScreen(this?.props?.channel);"
241 }
242 ]
243 },
244 // Avoid trying to fetch messages from hidden channels
245 {
246 find: &#039;"MessageManager"&#039;,
247 replacement: {
248 match: /forceFetch:\i,isPreload:.+?}=\i;(?=.+?getChannel\((\i)\))/,
249 replace: (m, channelId) => `${m}if($self.isHiddenChannel({channelId:${channelId}}))return;`
250 }
251 },
252 // Patch keybind handlers so you can't accidentally jump to hidden channels
253 {
254 find: &#039;"alt+shift+down"&#039;,
255 replacement: {
256 match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,200}?>0\)&&\(0,\i\.\i\)\(\i\))/,
257 replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
258 }
259 },
260 // Patch keybind handlers so you can't accidentally jump to hidden channels
261 {
262 find: ".APPLICATION_STORE&&null!=",
263 replacement: {
264 match: /getState\(\)\.channelId.+?(?=\.map\(\i=>\i\.id)/,
265 replace: "$&.filter(e=>!$self.isHiddenChannel(e))"
266 }
267 },
268 {
269 find: "#{intl::ROLE_REQUIRED_SINGLE_USER_MESSAGE}",
270 replacement: [
271 {
272 // Change the role permission check to CONNECT if the channel is locked
273 match: /(forceRoles:.+?)(\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\))(?<=context:(\i)}.+?)/,
274 replace: (_, rest, mergedPermissions, channel) => `${rest}$self.swapViewChannelWithConnectPermission(${mergedPermissions},${channel})`
275 },
276 {
277 // Change the permissionOverwrite check to CONNECT if the channel is locked
278 match: /permissionOverwrites\[.+?\i=(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
279 replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):`
280 },
281 {
282 // Include the @everyone role in the allowed roles list for Hidden Channels
283 match: /getSortedRoles.+?\.filter\(\i=>(?=!)/,
284 replace: m => `${m}$self.isHiddenChannel(arguments[0]?.channel)?true:`
285 },
286 {
287 // If the @everyone role has the required permissions, make the array only contain it
288 match: /forceRoles:.+?.value\(\)(?<=channel:(\i).+?)/,
289 replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))`
290 },
291 {
292 // 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)
293 match: /return\(0,\i\.jsxs?\)\(\i\.\i,{channelId:(\i)\.id,children:\[(?=.{0,1000}?(\(0,\i\.jsxs?\)\("div",{className:\i\.\i,children:\[.{0,100}\i\.length>0.+?\]}\)),)/,
294 replace: (m, channel, allowedUsersAndRolesComponent) => `if($self.isHiddenChannel(${channel},true)){return${allowedUsersAndRolesComponent};}${m}`
295 },
296 {
297 // Export the channel for the users allowed component patch
298 match: /maxUsers:\d+?,users:\i(?<=channel:(\i).+?)/,
299 replace: (m, channel) => `${m},shcChannel:${channel}`
300 },
301 {
302 // Always render the component for multiple allowed users
303 match: /1!==\i\.length(?=\|\|)/,
304 replace: "true"
305 }
306 ]
307 },
308 {
309 find: &#039;="interactive-text-default",overflowCountClassName:&#039;,
310 replacement: [
311 {
312 // Create a variable for the channel prop
313 match: /let{users:\i,maxUsers:\i,/,
314 replace: "let{shcChannel}=arguments[0];$&"
315 },
316 {
317 // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
318 match: /\i>0(?=&&!\i&&!\i)/,
319 replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})`
320 },
321 {
322 // Show only the plus text without overflowed children amount
323 // if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
324 match: /(?<=`\+\$\{)\i(?=\})/,
325 replace: overflowTextAmount => "" +
326 `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&(${overflowTextAmount}-1)<=0?"":${overflowTextAmount}`
327 }
328 ]
329 },
330 {
331 find: "#{intl::CHANNEL_CALL_CURRENT_SPEAKER}",
332 replacement: [
333 {
334 // Remove the open chat button for the HiddenChannelLockScreen
335 match: /(?<=&&)\i\.push\(.{0,120}"chat-spacer"/,
336 replace: "(arguments[0]?.inCall||!$self.isHiddenChannel(arguments[0]?.channel,true))&&$&"
337 }
338 ]
339 },
340 {
341 find: "#{intl::EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY_SHELF_FETCH_ERROR}",
342 replacement: [
343 {
344 // Render our HiddenChannelLockScreen component instead of the main voice channel component
345 match: /renderContent\(\i\){.+?this\.renderVoiceChannelEffects.+?children:/,
346 replace: "$&!this?.props?.inCall&&$self.isHiddenChannel(this?.props?.channel,true)?$self.HiddenChannelLockScreen(this?.props?.channel):"
347 },
348 {
349 // Disable gradients for the HiddenChannelLockScreen of voice channels
350 match: /renderContent\(\i\){.+?disableGradients:/,
351 replace: "$&!this?.props?.inCall&&$self.isHiddenChannel(this?.props?.channel,true)||"
352 },
353 {
354 // Disable useless components for the HiddenChannelLockScreen of voice channels
355 match: /(?:{|,)render(?!Header|ExternalHeader).{0,30}?:/g,
356 replace: "$&!this?.props?.inCall&&$self.isHiddenChannel(this?.props?.channel,true)?()=>null:"
357 },
358 {
359 // Disable bad CSS class which mess up hidden voice channels styling
360 match: /(?=\i\|\|\i!==\i\.\i\.FULL_SCREEN.{0,100}?this\._callContainerRef)/,
361 replace: &#039;$&!this?.props?.inCall&&$self.isHiddenChannel(this?.props?.channel,true)?"":&#039;
362 }
363 ]
364 },
365 {
366 find: &#039;"HasBeenInStageChannel"&#039;,
367 replacement: [
368 {
369 // Render our HiddenChannelLockScreen component instead of the main stage channel component
370 match: /screenMessage:(\i)\?.+?children:(?=!\1)(?<=let \i,{channel:(\i).+?)/,
371 replace: (m, _isPopoutOpen, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):`
372 },
373 {
374 // Disable useless components for the HiddenChannelLockScreen of stage channels
375 match: /render(?:BottomLeft|BottomCenter|BottomRight|ChatToasts):\(\)=>(?<=let \i,{channel:(\i).+?)/g,
376 replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?null:`
377 },
378 {
379 // Disable gradients for the HiddenChannelLockScreen of stage channels
380 match: /"124px".+?disableGradients:(?<=let \i,{channel:(\i).+?)/,
381 replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})||`
382 },
383 {
384 // Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels
385 match: /"124px".+?style:(?<=let \i,{channel:(\i).+?)/,
386 replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?void 0:`
387 }
388 ]
389 },
390 {
391 find: "#{intl::STAGE_FULL_MODERATOR_TITLE}",
392 replacement: [
393 {
394 // Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
395 match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(\i)\.guild_id)/,
396 replace: (m, channel) => `$self.isHiddenChannel(${channel})?null:(${m})`
397 },
398 {
399 // Remove the open chat button for the HiddenChannelLockScreen
400 match: /(?<=numRequestToSpeak:\i\}\)\}\):null,!\i&&)\(0,\i\.jsxs?\).{0,280}?iconClassName:/,
401 replace: "!$self.isHiddenChannel(arguments[0]?.channel,true)&&$&"
402 }
403 ]
404 },
405 {
406 // Make the chat input bar channel list contain hidden channels
407 find: ",queryStaticRouteChannels(",
408 replacement: [
409 {
410 // Make the getChannels call to GuildChannelStore return hidden channels
411 match: /(?<=queryChannels\(\i\){.+?getChannels\(\i)(?=\))/,
412 replace: ",true"
413 },
414 {
415 // Avoid filtering out hidden channels from the channel list
416 match: /(?<=queryChannels\(\i\){.+?\)\((\i)\.type\))(?=&&!\i\.\i\.can\()/,
417 replace: "&&!$self.isHiddenChannel($1)"
418 }
419 ]
420 },
421 {
422 find: "\"^/guild-stages/(\\\\d+)(?:/)?(\\\\d+)?\"",
423 replacement: {
424 // Make mentions of hidden channels work
425 match: /\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,\i\)/,
426 replace: "true"
427 },
428 },
429 {
430 find: &#039;getConfig({location:"channel_mention"})&#039;,
431 replacement: {
432 // Show inside voice channel instead of trying to join them when clicking on a channel mention
433 match: /(?<=getChannel\(\i\);if\(null!=(\i)).{0,200}?return void (?=\i\.default\.selectVoiceChannel)/,
434 replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
435 }
436 },
437 {
438 find: &#039;"GuildChannelStore"&#039;,
439 replacement: [
440 {
441 // Make GuildChannelStore contain hidden channels
442 match: /isChannelGated\(.+?\)(?=&&)/,
443 replace: m => `${m}&&false`
444 },
445 {
446 // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise
447 match: /(?<=getChannels\(\i)(\){.*?)return (.+?)}/,
448 replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??arguments[0]==="@favorites");}`
449 },
450 ]
451 },
452 {
453 find: "GuildTooltip - ",
454 replacement: {
455 // Make GuildChannelStore.getChannels return hidden channels
456 match: /(?<=getChannels\(\i)(?=\))/,
457 replace: ",true"
458 }
459 },
460 {
461 find: &#039;"NowPlayingViewStore"&#039;,
462 replacement: {
463 // Make active now voice states on hidden channels
464 match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,
465 replace: "$1"
466 }
467 },
468 {
469 find: "#{intl::ROLE_REQUIRED_SINGLE_USER_MESSAGE}",
470 replacement: {
471 match: /(?=function (\i)\(\i\){let{channel:.{0,200}?getSortedRoles\()/,
472 replace: "$self.ChannelBeginHeader=$1;"
473 }
474 }
475 ],
476
477 set ChannelBeginHeader(value: any) {
478 setChannelBeginHeader(value);
479 },
480
481 swapViewChannelWithConnectPermission(mergedPermissions: bigint, channel: Channel) {
482 if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) {
483 mergedPermissions &= ~PermissionsBits.VIEW_CHANNEL;
484 mergedPermissions |= PermissionsBits.CONNECT;
485 }
486
487 return mergedPermissions;
488 },
489
490 isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
491 try {
492 if (channel == null || Object.hasOwn(channel, "channelId") && channel.channelId == null) return false;
493
494 if (channel.channelId != null) channel = ChannelStore.getChannel(channel.channelId);
495 if (channel == null || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
496 if (["browse", "customize", "guide"].includes(channel.id)) return false;
497
498 return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
499 } catch (e) {
500 console.error("[ViewHiddenChannels#isHiddenChannel]: ", e);
501 return false;
502 }
503 },
504
505 resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
506 if (shouldIncludeHidden) return channels;
507
508 const res = {};
509 for (const [key, maybeObjChannels] of Object.entries(channels)) {
510 if (!Array.isArray(maybeObjChannels)) {
511 res[key] = maybeObjChannels;
512 continue;
513 }
514
515 res[key] ??= [];
516
517 for (const objChannel of maybeObjChannels) {
518 if (isUncategorized(objChannel) || objChannel.channel.id === null || !this.isHiddenChannel(objChannel.channel)) res[key].push(objChannel);
519 }
520 }
521
522 return res;
523 },
524
525 makeAllowedRolesReduce(guildId: string) {
526 return [
527 (prev: Array<Role>, _: Role, index: number, originalArray: Array<Role>) => {
528 if (index !== 0) return prev;
529
530 const everyoneRole = originalArray.find(role => role.id === guildId);
531
532 if (everyoneRole) return [everyoneRole];
533 return originalArray;
534 },
535 [] as Array<Role>
536 ];
537 },
538
539 HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />,
540
541 LockIcon: ErrorBoundary.wrap(() => (
542 <svg
543 className={ChannelListClasses.icon}
544 height="18"
545 width="20"
546 viewBox="0 0 24 24"
547 aria-hidden={true}
548 role="img"
549 >
550 <path fill="currentcolor" fillRule="evenodd" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" />
551 </svg>
552 ), { noop: true }),
553
554 HiddenChannelIcon: ErrorBoundary.wrap(() => (
555 <Tooltip text="Hidden Channel">
556 {({ onMouseLeave, onMouseEnter }) => (
557 <svg
558 onMouseLeave={onMouseLeave}
559 onMouseEnter={onMouseEnter}
560 className={classes(ChannelListClasses.icon, cl("hidden-channel-icon"))}
561 width="24"
562 height="24"
563 viewBox="0 0 24 24"
564 aria-hidden={true}
565 role="img"
566 >
567 <path fill="currentcolor" fillRule="evenodd" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" />
568 </svg>
569 )}
570 </Tooltip>
571 ), { noop: true })
572});
573