ShowHiddenChannels: Stage and voice channels support (#469)
Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
parent
291f38115c
commit
992a77e76c
|
@ -34,8 +34,8 @@ export default definePlugin({
|
||||||
";if(Vencord.Api.Notices.currentNotice)return false$&"
|
";if(Vencord.Api.Notices.currentNotice)return false$&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/,
|
match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/,
|
||||||
replace: '{if($1?.id=="VencordNotice")return ($1=null,Vencord.Api.Notices.nextNotice(),true);'
|
replace: 'if($1?.id=="VencordNotice")return($1=null,Vencord.Api.Notices.nextNotice(),true);'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,17 @@
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { LazyComponent } from "@utils/misc";
|
import { LazyComponent } from "@utils/misc";
|
||||||
import { proxyLazy } from "@utils/proxyLazy";
|
|
||||||
import { formatDuration } from "@utils/text";
|
import { formatDuration } from "@utils/text";
|
||||||
import { find, findByCode, findByPropsLazy, findLazy } from "@webpack";
|
import { find, findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
|
|
||||||
enum SortOrderTypesTyping {
|
enum SortOrderTypes {
|
||||||
LATEST_ACTIVITY = 0,
|
LATEST_ACTIVITY = 0,
|
||||||
CREATION_DATE = 1
|
CREATION_DATE = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ForumLayoutTypesTyping {
|
enum ForumLayoutTypes {
|
||||||
DEFAULT = 0,
|
DEFAULT = 0,
|
||||||
LIST = 1,
|
LIST = 1,
|
||||||
GRID = 2
|
GRID = 2
|
||||||
|
@ -50,18 +49,31 @@ interface Tag {
|
||||||
|
|
||||||
interface ExtendedChannel extends Channel {
|
interface ExtendedChannel extends Channel {
|
||||||
defaultThreadRateLimitPerUser?: number;
|
defaultThreadRateLimitPerUser?: number;
|
||||||
defaultSortOrder?: SortOrderTypesTyping | null;
|
defaultSortOrder?: SortOrderTypes | null;
|
||||||
defaultForumLayout?: ForumLayoutTypesTyping;
|
defaultForumLayout?: ForumLayoutTypes;
|
||||||
defaultReactionEmoji?: DefaultReaction | null;
|
defaultReactionEmoji?: DefaultReaction | null;
|
||||||
availableTags?: Array<Tag>;
|
availableTags?: Array<Tag>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatClasses = findByPropsLazy("chat", "chatContent");
|
enum ChannelTypes {
|
||||||
const TagClasses = findLazy(m => typeof m.tags === "string" && Object.entries(m).length === 1); // Object exported with a single key called tags
|
GUILD_TEXT = 0,
|
||||||
const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM");
|
GUILD_VOICE = 2,
|
||||||
const SortOrderTypes = findLazy(m => typeof m.LATEST_ACTIVITY === "number");
|
GUILD_ANNOUNCEMENT = 5,
|
||||||
const ForumLayoutTypes = findLazy(m => typeof m.LIST === "number");
|
GUILD_STAGE_VOICE = 13,
|
||||||
const ChannelFlags = findLazy(m => typeof m.REQUIRE_TAG === "number");
|
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 => {
|
const TagComponent = LazyComponent(() => find(m => {
|
||||||
if (typeof m !== "function") return false;
|
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");
|
return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill");
|
||||||
}));
|
}));
|
||||||
const EmojiComponent = LazyComponent(() => findByCode('.jumboable?"jumbo":"default"'));
|
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_TEXT]: "text",
|
||||||
[ChannelTypes.GUILD_ANNOUNCEMENT]: "announcement",
|
[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.LATEST_ACTIVITY]: "Latest activity",
|
||||||
[SortOrderTypes.CREATION_DATE]: "Creation date"
|
[SortOrderTypes.CREATION_DATE]: "Creation date"
|
||||||
}));
|
};
|
||||||
|
|
||||||
const ForumLayoutTypesToNames = proxyLazy(() => ({
|
const ForumLayoutTypesToNames = {
|
||||||
[ForumLayoutTypes.DEFAULT]: "Not set",
|
[ForumLayoutTypes.DEFAULT]: "Not set",
|
||||||
[ForumLayoutTypes.LIST]: "List view",
|
[ForumLayoutTypes.LIST]: "List view",
|
||||||
[ForumLayoutTypes.GRID]: "Gallery 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
|
// Icon from the modal when clicking a message link you don't have access to view
|
||||||
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
||||||
|
@ -104,97 +125,137 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
rateLimitPerUser,
|
rateLimitPerUser,
|
||||||
defaultThreadRateLimitPerUser,
|
defaultThreadRateLimitPerUser,
|
||||||
defaultSortOrder,
|
defaultSortOrder,
|
||||||
defaultReactionEmoji
|
defaultReactionEmoji,
|
||||||
|
bitrate,
|
||||||
|
rtcRegion,
|
||||||
|
videoQualityMode,
|
||||||
|
permissionOverwrites
|
||||||
} = channel;
|
} = 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 (
|
return (
|
||||||
<div className={ChatClasses.chat + " " + "shc-lock-screen-container"}>
|
<div className={ChatScrollClasses.auto + " " + "shc-lock-screen-outer-container"}>
|
||||||
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} />
|
<div className="shc-lock-screen-container">
|
||||||
|
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} />
|
||||||
|
|
||||||
<div className="shc-lock-screen-heading-container">
|
<div className="shc-lock-screen-heading-container">
|
||||||
<Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text>
|
<Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text>
|
||||||
{channel.isNSFW() &&
|
{channel.isNSFW() &&
|
||||||
<Tooltip text="NSFW">
|
<Tooltip text="NSFW">
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<svg
|
<svg
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
className="shc-lock-screen-heading-nsfw-icon"
|
className="shc-lock-screen-heading-nsfw-icon"
|
||||||
width="32"
|
width="32"
|
||||||
height="32"
|
height="32"
|
||||||
viewBox="0 0 48 48"
|
viewBox="0 0 48 48"
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
role="img"
|
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" />
|
<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>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</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>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{lastMessageId &&
|
{(!channel.isGuildVoice() && !channel.isGuildStageVoice()) && (
|
||||||
<Text variant="text-md/normal">
|
<Text variant="text-lg/normal">
|
||||||
Last {channel.isForumChannel() ? "post" : "message"} created:
|
You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel.
|
||||||
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
{channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"}
|
||||||
</Text>
|
</Text >
|
||||||
}
|
)}
|
||||||
|
|
||||||
{lastPinTimestamp &&
|
{channel.isForumChannel() && topic && topic.length > 0 && (
|
||||||
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text>
|
<div className="shc-lock-screen-topic-container">
|
||||||
}
|
{Parser.parseTopic(topic, false, { channelId })}
|
||||||
{(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} />)}
|
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,15 @@ import { definePluginSettings } from "@api/settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common";
|
import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
|
|
||||||
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
||||||
|
|
||||||
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
|
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
|
||||||
const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint");
|
|
||||||
|
const VIEW_CHANNEL = 1n << 10n;
|
||||||
|
|
||||||
enum ShowMode {
|
enum ShowMode {
|
||||||
LockIcon,
|
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",
|
find: "VoiceChannel.renderPopout: There must always be something to render",
|
||||||
replacement: [
|
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
|
// Render null instead of the buttons if the channel is hidden
|
||||||
...[
|
...[
|
||||||
"renderEditButton",
|
"renderEditButton",
|
||||||
|
@ -120,11 +136,11 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".UNREAD_HIGHLIGHT",
|
find: ".UNREAD_HIGHLIGHT",
|
||||||
predicate: () => settings.store.hideUnreads === true,
|
predicate: () => settings.store.hideUnreads === true,
|
||||||
replacement: [{
|
replacement: {
|
||||||
// Hide unreads
|
// Hide unreads
|
||||||
match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/,
|
match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/,
|
||||||
replace: "$self.isHiddenChannel($<props>.channel)?false:"
|
replace: "$self.isHiddenChannel($<props>.channel)?false:"
|
||||||
}]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".UNREAD_HIGHLIGHT",
|
find: ".UNREAD_HIGHLIGHT",
|
||||||
|
@ -160,7 +176,7 @@ export default definePlugin({
|
||||||
// Hide New unreads box for hidden channels
|
// Hide New unreads box for hidden channels
|
||||||
find: '.displayName="ChannelListUnreadsStore"',
|
find: '.displayName="ChannelListUnreadsStore"',
|
||||||
replacement: {
|
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>)"
|
replace: "&&!$self.isHiddenChannel($<channel>)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -191,7 +207,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
match: /(?<=renderChat=function\(\){)/,
|
match: /(?<=renderChat=function\(\){)/,
|
||||||
replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);"
|
replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);"
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Avoid trying to fetch messages from hidden channels
|
// 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)/,
|
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:
|
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.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||||
|
|
||||||
return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel);
|
return !PermissionStore.can(VIEW_CHANNEL, channel);
|
||||||
},
|
},
|
||||||
|
|
||||||
HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen 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 {
|
.shc-lock-screen-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shc-lock-screen-container > * {
|
.shc-lock-screen-container > * {
|
||||||
|
@ -34,14 +43,14 @@
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 10px;
|
||||||
max-width: 70vw;
|
max-width: 70vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shc-lock-screen-tags-container {
|
.shc-lock-screen-tags-container {
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 10px;
|
||||||
max-width: 70vw;
|
max-width: 70vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +58,12 @@
|
||||||
margin: inherit;
|
margin: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shc-lock-screen-tags-container > [class^="tags"] {
|
.shc-lock-screen-tags {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shc-evenodd-fill-current-color {
|
.shc-evenodd-fill-current-color {
|
||||||
|
@ -76,3 +89,18 @@
|
||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
margin-left: 5px;
|
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 { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { GuildMemberStore, React } from "@webpack/common";
|
import { GuildMemberStore, React, RelationshipStore } from "@webpack/common";
|
||||||
|
|
||||||
const Avatar = findByCodeLazy(".Positions.TOP,spacing:");
|
const Avatar = findByCodeLazy(".Positions.TOP,spacing:");
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ",\"SEVERAL_USERS_TYPING\",\"",
|
find: ",\"SEVERAL_USERS_TYPING\",\"",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\((\i),("SEVERAL_USERS_TYPING"),".+?"\)/,
|
match: /(?<="SEVERAL_USERS_TYPING",)".+?"/,
|
||||||
replace: "$1($2,$3,\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")"
|
replace: '"**!!{a}!!**, **!!{b}!!**, and {c} others are typing..."'
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.alternativeFormatting
|
predicate: () => settings.store.alternativeFormatting
|
||||||
},
|
},
|
||||||
|
@ -98,7 +98,7 @@ export default definePlugin({
|
||||||
|
|
||||||
let element = 0;
|
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 }) => {
|
TypingUser: ErrorBoundary.wrap(({ user, guildId }) => {
|
||||||
|
@ -111,9 +111,9 @@ export default definePlugin({
|
||||||
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size={Avatar.Sizes.SIZE_16}
|
size={Avatar.Sizes.SIZE_16}
|
||||||
src={user.getAvatarURL(guildId, 128)}/>
|
src={user.getAvatarURL(guildId, 128)} />
|
||||||
</div>}
|
</div>}
|
||||||
{GuildMemberStore.getNick(guildId!, user.id) || user.username}
|
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
|
||||||
</strong>;
|
</strong>;
|
||||||
}, { noop: true })
|
}, { noop: true })
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,25 +37,58 @@ export const wordsToPascal = (words: string[]) =>
|
||||||
export const wordsToTitle = (words: string[]) =>
|
export const wordsToTitle = (words: string[]) =>
|
||||||
words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
|
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"
|
* Forms time into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds"
|
||||||
* @param ms Milliseconds
|
* @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"
|
* @param short Whether to use short units like "d" instead of "days"
|
||||||
*/
|
*/
|
||||||
export function formatDuration(ms: number, short: boolean = false) {
|
export function formatDuration(time: number, unit: Units, short: boolean = false) {
|
||||||
const dur = moment.duration(ms);
|
const dur = moment.duration(time, unit);
|
||||||
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
|
let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit }));
|
||||||
? unit[0]
|
|
||||||
: x === 1 ? unit.slice(0, -1) : 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 { canonicalizeReplacement } from "@utils/patches";
|
||||||
import { PatchReplacement } from "@utils/types";
|
import { PatchReplacement } from "@utils/types";
|
||||||
|
|
||||||
|
import { traceFunction } from "../debug/Tracer";
|
||||||
import { _initWebpack } from ".";
|
import { _initWebpack } from ".";
|
||||||
|
|
||||||
let webpackChunk: any[];
|
let webpackChunk: any[];
|
||||||
|
@ -132,6 +133,7 @@ function patchPush() {
|
||||||
|
|
||||||
for (let i = 0; i < patches.length; i++) {
|
for (let i = 0; i < patches.length; i++) {
|
||||||
const patch = patches[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 (patch.predicate && !patch.predicate()) continue;
|
||||||
|
|
||||||
if (code.includes(patch.find)) {
|
if (code.includes(patch.find)) {
|
||||||
|
@ -146,7 +148,7 @@ function patchPush() {
|
||||||
canonicalizeReplacement(replacement, patch.plugin);
|
canonicalizeReplacement(replacement, patch.plugin);
|
||||||
|
|
||||||
try {
|
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) {
|
if (newCode === code && !patch.noWarn) {
|
||||||
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
|
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
|
@ -187,7 +189,7 @@ function patchPush() {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context);
|
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");
|
const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff");
|
||||||
logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements);
|
logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue