Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen1234 2024-09-20 17:09:15 -04:00
commit ad61525ae6
6 changed files with 104 additions and 48 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "equicord", "name": "equicord",
"private": "true", "private": "true",
"version": "1.10.1", "version": "1.10.2",
"description": "The other cutest Discord client mod", "description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme", "homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": { "bugs": {

View file

@ -158,4 +158,5 @@ export const defaultRules = [
"igshid", "igshid",
"igsh", "igsh",
"share_id@reddit.com", "share_id@reddit.com",
"si@soundcloud.com",
]; ];

View file

@ -112,12 +112,12 @@ export default definePlugin({
}, },
// patch request that queries if term is allowed // patch request that queries if term is allowed
{ {
find: ".GUILD_DISCOVERY_VALID_TERM", find: ".GUILD_DISCOVERY_VALID_TERM,query:",
predicate: () => settings.store.disableDisallowedDiscoveryFilters, predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true, all: true,
replacement: { replacement: {
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g, match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\)/g,
replace: "Promise.resolve({ body: { valid: true } });" replace: "Promise.resolve({ body: { valid: true } })"
} }
} }
], ],

View file

@ -7,17 +7,24 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack";
import { ChannelStore, GuildStore, IconUtils, NavigationRouter, PermissionsBits, PermissionStore, showToast, Text, Toasts, Tooltip, useCallback, useMemo, UserStore, useStateFromStores } from "@webpack/common"; import { ChannelStore, GuildStore, IconUtils, match, NavigationRouter, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
const cl = classNameFactory("vc-uvs-"); const cl = classNameFactory("vc-uvs-");
const { selectVoiceChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel"); const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChannel");
const { useChannelName } = mapMangledModuleLazy(".Messages.GROUP_DM_ALONE", {
useChannelName: filters.byCode("()=>null==")
});
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
const VoiceStateStore = findStoreLazy("VoiceStateStore"); const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
interface IconProps extends React.HTMLAttributes<HTMLDivElement> { const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const Avatar = findComponentByCodeLazy(".AVATAR_STATUS_TYPING_16;");
const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL");
interface IconProps extends React.ComponentPropsWithoutRef<"div"> {
size?: number; size?: number;
} }
@ -28,7 +35,7 @@ function SpeakerIcon(props: IconProps) {
<div <div
{...props} {...props}
role={props.onClick != null ? "button" : undefined} role={props.onClick != null ? "button" : undefined}
className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined)} className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)}
> >
<svg <svg
width={props.size} width={props.size}
@ -50,7 +57,7 @@ function LockedSpeakerIcon(props: IconProps) {
<div <div
{...props} {...props}
role={props.onClick != null ? "button" : undefined} role={props.onClick != null ? "button" : undefined}
className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined)} className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)}
> >
<svg <svg
width={props.size} width={props.size}
@ -71,39 +78,46 @@ interface VoiceChannelTooltipProps {
function VoiceChannelTooltip({ channel }: VoiceChannelTooltipProps) { function VoiceChannelTooltip({ channel }: VoiceChannelTooltipProps) {
const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id)); const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id));
const users = useMemo( const users = useMemo(
() => Object.values<any>(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null), () => Object.values<any>(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null),
[voiceStates] [voiceStates]
); );
const guild = useMemo( const guild = channel.getGuildId() == null ? undefined : GuildStore.getGuild(channel.getGuildId());
() => channel.getGuildId() == null ? undefined : GuildStore.getGuild(channel.getGuildId()), const guildIcon = guild?.icon == null ? undefined : IconUtils.getGuildIconURL({
[channel] id: guild.id,
); icon: guild.icon,
size: 30
});
const guildIcon = useMemo(() => { const channelIcon = match(channel.type)
return guild?.icon == null ? undefined : IconUtils.getGuildIconURL({ .with(P.union(1, 3), () => {
id: guild.id, return channel.recipients.length >= 2 && channel.icon == null
icon: guild.icon, ? <GroupDMAvatars recipients={channel.recipients} size="SIZE_32" />
size: 30 : <Avatar src={getDMChannelIcon(channel)} size="SIZE_32" />;
}); })
}, [guild]); .otherwise(() => null);
const channelName = useChannelName(channel);
return ( return (
<> <>
{guild != null && ( {guild != null && (
<div className={cl("guild-name")}> <div className={cl("name")}>
{guildIcon != null && <img className={cl("guild-icon")} src={guildIcon} alt="" />} {guildIcon != null && <img className={cl("guild-icon")} src={guildIcon} alt="" />}
<Text variant="text-sm/bold">{guild.name}</Text> <Text variant="text-sm/bold">{guild.name}</Text>
</div> </div>
)} )}
<Text variant="text-sm/semibold">{channel.name}</Text> <div className={cl("name")}>
{channelIcon}
<Text variant="text-sm/semibold">{channelName}</Text>
</div>
<div className={cl("vc-members")}> <div className={cl("vc-members")}>
<SpeakerIcon size={18} /> <SpeakerIcon size={18} />
<UserSummaryItem <UserSummaryItem
users={users} users={users}
renderIcon={false} renderIcon={false}
max={14} max={13}
size={18} size={18}
/> />
</div> </div>
@ -113,20 +127,28 @@ function VoiceChannelTooltip({ channel }: VoiceChannelTooltipProps) {
interface VoiceChannelIndicatorProps { interface VoiceChannelIndicatorProps {
userId: string; userId: string;
isActionButton?: boolean;
isMessageIndicator?: boolean;
} }
const clickTimers = {} as Record<string, any>; const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChannelIndicatorProps) => { export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, isMessageIndicator }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = useMemo(() => channelId == null ? undefined : ChannelStore.getChannel(channelId), [channelId]);
const onClick = useCallback((e: React.MouseEvent) => { const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
if (channel == null) return null;
const isDM = channel.isDM() || channel.isMultiUserDM();
const isLocked = !isDM && (!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || !PermissionStore.can(PermissionsBits.CONNECT, channel));
function onClick(e: React.MouseEvent) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (channel == null || channelId == null) return; if (channel == null || channelId == null) return;
if (!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel)) { if (!isDM && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel)) {
showToast("You cannot view the user's Voice Channel", Toasts.Type.FAILURE); showToast("You cannot view the user's Voice Channel", Toasts.Type.FAILURE);
return; return;
} }
@ -135,7 +157,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChanne
delete clickTimers[channelId]; delete clickTimers[channelId];
if (e.detail > 1) { if (e.detail > 1) {
if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) { if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) {
showToast("You cannot join the user's Voice Channel", Toasts.Type.FAILURE); showToast("You cannot join the user's Voice Channel", Toasts.Type.FAILURE);
return; return;
} }
@ -147,13 +169,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChanne
delete clickTimers[channelId]; delete clickTimers[channelId];
}, 250); }, 250);
} }
}, [channelId]); }
const isLocked = useMemo(() => {
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || !PermissionStore.can(PermissionsBits.CONNECT, channel);
}, [channelId]);
if (channel == null) return null;
return ( return (
<Tooltip <Tooltip
@ -161,11 +177,20 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChanne
tooltipClassName={cl("tooltip-container")} tooltipClassName={cl("tooltip-container")}
tooltipContentClassName={cl("tooltip-content")} tooltipContentClassName={cl("tooltip-content")}
> >
{props => {props => {
isLocked ? const iconProps: IconProps = {
<LockedSpeakerIcon {...props} onClick={onClick} /> ...props,
: <SpeakerIcon {...props} onClick={onClick} /> className: isActionButton ? cl("indicator-action-button") : cl("speaker-padding"),
} size: isActionButton ? 20 : undefined,
onClick
};
return <div className={isMessageIndicator ? cl("message-indicator") : undefined}>
{isLocked ?
<LockedSpeakerIcon {...iconProps} />
: <SpeakerIcon {...iconProps} />}
</div>;
}}
</Tooltip> </Tooltip>
); );
}, { noop: true }); }, { noop: true });

View file

@ -19,6 +19,7 @@
import "./style.css"; import "./style.css";
import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -37,13 +38,19 @@ const settings = definePluginSettings({
description: "Show a user's Voice Channel indicator in the member and DMs list", description: "Show a user's Voice Channel indicator in the member and DMs list",
default: true, default: true,
restartNeeded: true restartNeeded: true
},
showInMessages: {
type: OptionType.BOOLEAN,
description: "Show a user's Voice Channel indicator in messages",
default: true,
restartNeeded: true
} }
}); });
export default definePlugin({ export default definePlugin({
name: "UserVoiceShow", name: "UserVoiceShow",
description: "Shows an indicator when a user is in a Voice Channel", description: "Shows an indicator when a user is in a Voice Channel",
authors: [Devs.LordElias, Devs.Nuckyz], authors: [Devs.Nuckyz, Devs.LordElias],
settings, settings,
patches: [ patches: [
@ -77,10 +84,10 @@ export default definePlugin({
}, */ }, */
// Friends List // Friends List
{ {
find: ".avatar,animate:", find: "null!=this.peopleListItemRef.current",
replacement: { replacement: {
match: /\.subtext,children:.+?}\)\]}\)(?=])/, match: /\.actions,children:\[/,
replace: "$&,$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})" replace: "$&$self.VoiceChannelIndicator({userId:this?.props?.user?.id,isActionButton:true}),"
}, },
predicate: () => settings.store.showInMemberList predicate: () => settings.store.showInMemberList
} }
@ -90,10 +97,14 @@ export default definePlugin({
if (settings.store.showInMemberList) { if (settings.store.showInMemberList) {
addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
} }
if (settings.store.showInMessages) {
addDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
}
}, },
stop() { stop() {
removeDecorator("UserVoiceShow"); removeDecorator("UserVoiceShow");
removeDecoration("UserVoiceShow");
}, },
VoiceChannelIndicator VoiceChannelIndicator

View file

@ -1,6 +1,5 @@
.vc-uvs-speaker { .vc-uvs-speaker {
color: var(--interactive-normal); color: var(--interactive-normal);
padding: 0 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -14,6 +13,26 @@
color: var(--interactive-hover); color: var(--interactive-hover);
} }
.vc-uvs-speaker-padding {
padding: 0 4px;
}
.vc-uvs-message-indicator {
display: inline-flex;
align-items: center;
justify-content: center;
top: 2.5px;
position: relative;
}
.vc-uvs-indicator-action-button {
background-color: var(--background-secondary);
border-radius: 100%;
height: 36px;
width: 36px;
margin-left: 10px;
}
.vc-uvs-tooltip-container { .vc-uvs-tooltip-container {
max-width: 300px; max-width: 300px;
} }
@ -24,7 +43,7 @@
gap: 6px; gap: 6px;
} }
.vc-uvs-guild-name { .vc-uvs-name {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;