Plugin

ShowHiddenChannels

Show channels that you do not have access to view.

Servers Utility
index.tsx
Download

Source

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