aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/apiNotices.ts4
-rw-r--r--src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx269
-rw-r--r--src/plugins/showHiddenChannels/index.tsx118
-rw-r--r--src/plugins/showHiddenChannels/style.css34
-rw-r--r--src/plugins/typingTweaks.tsx12
-rw-r--r--src/utils/text.ts67
-rw-r--r--src/webpack/patchWebpack.ts6
7 files changed, 365 insertions, 145 deletions
diff --git a/src/plugins/apiNotices.ts b/src/plugins/apiNotices.ts
index c362f76..8922ace 100644
--- a/src/plugins/apiNotices.ts
+++ b/src/plugins/apiNotices.ts
@@ -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);'
}
]
}
diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx
index e5c5ee2..7e66a6a 100644
--- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx
+++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx
@@ -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;
- return (
- <div className={ChatClasses.chat + " " + "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>
+ const membersToFetch: Array<string> = [];
- <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 >
+ const guildOwnerId = GuildStore.getGuild(channel.guild_id).ownerId;
+ if (!GuildMemberStore.getMember(channel.guild_id, guildOwnerId)) membersToFetch.push(guildOwnerId);
- {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! * 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
- }} />
+ 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={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>
- }
- {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.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 >
+ )}
+
+ {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>
);
}
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index 9a448d5..a4c7dec 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -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,
@@ -90,13 +91,28 @@ export default definePlugin({
]
},
{
- find: "VoiceChannel.renderPopout: There must always be something to render",
+ find: "VoiceChannel, transitionTo: Channel does not have a guildId",
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;"
+ // 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: [
// 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} />,
diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css
index 73957ef..0f85b50 100644
--- a/src/plugins/showHiddenChannels/style.css
+++ b/src/plugins/showHiddenChannels/style.css
@@ -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;
+}
diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx
index 96c04b9..f03779c 100644
--- a/src/plugins/typingTweaks.tsx
+++ b/src/plugins/typingTweaks.tsx
@@ -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 })
});
diff --git a/src/utils/text.ts b/src/utils/text.ts
index fae3343..115b3e2 100644
--- a/src/utils/text.ts
+++ b/src/utils/text.ts
@@ -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 " : ", ";
-
- const unitStr = short
- ? unit[0]
- : x === 1 ? unit.slice(0, -1) : unit;
-
- res += `${x} ${unitStr}`;
+export function formatDuration(time: number, unit: Units, short: boolean = false) {
+ const dur = moment.duration(time, unit);
+
+ let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit }));
+
+ 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)}`;
}
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 7b318b2..19ca951 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -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);
}