Compare commits

...

22 commits

Author SHA1 Message Date
sadan4
f596e6fb8a
Merge a88db90860 into a015cf96f6 2024-09-19 18:37:14 +02:00
Nuckyz
a015cf96f6
UserVoiceShow: Fix setting name
Some checks are pending
Sync to Codeberg / codeberg (push) Waiting to run
test / test (push) Waiting to run
2024-09-19 13:33:32 -03:00
sadan4
a88db90860
Merge branch 'dev' into emoteCloner 2024-09-15 21:44:54 -04:00
sadan4
74b03c0962
Discard changes to src/utils/constants.ts 2024-09-15 21:43:14 -04:00
sadan
7d70d53083
remove hang status and add channel statuses 2024-09-15 21:42:52 -04:00
sadan4
b3bf32abd7
Merge branch 'dev' into emoteCloner 2024-09-04 20:20:42 -04:00
sadan
f8bf23db1f
fix contsatns 2024-09-02 01:16:32 -04:00
sadan4
6ac7edccec
Merge branch 'dev' into emoteCloner 2024-09-02 01:11:50 -04:00
sadan4
6366cc2e12
Merge branch 'main' into emoteCloner 2024-09-02 01:11:25 -04:00
sadan4
8bba7b7ab7
Merge branch 'main' into emoteCloner 2024-07-30 23:13:11 -04:00
sadan4
efc38a1d2e
Merge branch 'main' into emoteCloner 2024-07-20 18:20:05 -04:00
sadan
b4e4085382
fix patch modifying the user object 2024-07-15 13:18:26 -04:00
sadan4
85e3fa4cc5
Merge branch 'main' into emoteCloner 2024-07-14 23:18:50 -04:00
sadan
359d7564a5
fix wacky logic 2024-07-14 23:16:58 -04:00
sadan4
04bfbe4553
Merge branch 'main' into emoteCloner 2024-07-14 22:13:21 -04:00
sadan
bdae37ff23
fix var names, regex, and types 2024-07-11 02:20:07 -04:00
sadan4
8e649cfcc4
Update src/plugins/emoteCloner/index.tsx
Co-authored-by: Drew <84212701+MrDiamondDog@users.noreply.github.com>
2024-07-11 02:09:21 -04:00
sadan
9c819e83b3
fix regex remove fake nitro name part 2024-07-10 23:35:19 -04:00
sadan
850b940306
fix regex and remove log 2024-07-10 22:35:27 -04:00
sadan
add443e67b
inline patch 2024-07-10 22:33:36 -04:00
sadan
eed1c2b400
Merge branch 'main' into emoteCloner 2024-07-10 13:57:54 -04:00
sadan
d7366ae171
read desc
add emote cloner to
- emojis in user statuses
- hang statuses (vc statuses)
add name finding for fake nitro emojis that have &name=emoji_name
2024-07-10 02:28:18 -04:00
4 changed files with 83 additions and 35 deletions

View file

@ -25,11 +25,16 @@ import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/moda
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Channel } from "discord-types/general";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const StickersStore = findStoreLazy("StickersStore"); const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
const ChannelStatusStore: {
getChannelStatus(c: Channel): string;
} = findStoreLazy("ChannelStatusStore");
interface Sticker { interface Sticker {
t: "Sticker"; t: "Sticker";
description: string; description: string;
@ -272,12 +277,12 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
); );
} }
function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Omit<Sticker | Emoji, "t">>) { function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Omit<Sticker | Emoji, "t">>, label?: string) {
return ( return (
<Menu.MenuItem <Menu.MenuItem
id="emote-cloner" id={`emote-cloner${label ?? ""}`}
key="emote-cloner" key="emote-cloner"
label={`Clone ${type}`} label={label ?? `Clone ${type}`}
action={() => action={() =>
openModalLazy(async () => { openModalLazy(async () => {
const res = await fetchData(); const res = await fetchData();
@ -360,13 +365,57 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t
} }
}; };
const emojiRegex = /https:\/\/cdn\.discordapp\.com\/emojis\/(\d+)\.([a-zA-Z]{3,4}).*/;
const imageContextPatch: NavContextMenuPatchCallback = (children, props: {
src: string;
}) => {
// this context menu is called on normal images, as well as stock emojis.
const matches = [...props.src.match(emojiRegex) ?? []];
if (matches.length === 0) return;
children.push(buildMenuItem("Emoji", () => ({
id: matches[1],
isAnimated: (matches[2] === "gif"),
name: "ProfileEmoji"
})));
};
const emojiMatchRegex = /<(a?):(\w+):(\d{19})>/g;
const channelContextStatusPatch: NavContextMenuPatchCallback = (children, props: {
channel: Channel;
}) => {
const status = ChannelStatusStore.getChannelStatus(props.channel);
if (!status) return;
const emojis = [...status.matchAll(emojiMatchRegex)];
if (emojis.length === 0) return;
console.log(emojis);
children.push((
<Menu.MenuItem id="vc-emoteCloner-item" label="Clone Emoji">
{
emojis.map(([_, animated, name, id]) => (
buildMenuItem("Emoji", () => ({
id,
name,
isAnimated: !!animated,
}),
`Clone ${name}`
)
))
}
</Menu.MenuItem>
));
};
export default definePlugin({ export default definePlugin({
name: "EmoteCloner", name: "EmoteCloner",
description: "Allows you to clone Emotes & Stickers to your own server (right click them)", description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
tags: ["StickerCloner"], tags: ["StickerCloner"],
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz, Devs.sadan],
contextMenus: { contextMenus: {
"message": messageContextMenuPatch, "message": messageContextMenuPatch,
"expression-picker": expressionPickerPatch "expression-picker": expressionPickerPatch,
} "image-context": imageContextPatch,
"channel-context": channelContextStatusPatch
},
}); });

View file

@ -8,7 +8,7 @@ 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 { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildStore, IconUtils, NavigationRouter, PermissionsBits, PermissionStore, showToast, Text, Toasts, Tooltip, useCallback, useMemo, UserStore, useStateFromStores } from "@webpack/common"; import { ChannelStore, GuildStore, IconUtils, NavigationRouter, 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-");
@ -17,7 +17,7 @@ const { selectVoiceChannel } = findByPropsLazy("selectChannel", "selectVoiceChan
const VoiceStateStore = findStoreLazy("VoiceStateStore"); const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
interface IconProps extends React.HTMLAttributes<HTMLDivElement> { interface IconProps extends React.ComponentPropsWithoutRef<"div"> {
size?: number; size?: number;
} }
@ -71,23 +71,18 @@ 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]
);
const guildIcon = useMemo(() => {
return guild?.icon == null ? undefined : IconUtils.getGuildIconURL({
id: guild.id, id: guild.id,
icon: guild.icon, icon: guild.icon,
size: 30 size: 30
}); });
}, [guild]);
return ( return (
<> <>
@ -103,7 +98,7 @@ function VoiceChannelTooltip({ channel }: VoiceChannelTooltipProps) {
<UserSummaryItem <UserSummaryItem
users={users} users={users}
renderIcon={false} renderIcon={false}
max={7} max={13}
size={18} size={18}
/> />
</div> </div>
@ -119,11 +114,14 @@ const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChannelIndicatorProps) => { export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: 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;
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 (!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel)) {
@ -147,18 +145,15 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId }: VoiceChanne
delete clickTimers[channelId]; delete clickTimers[channelId];
}, 250); }, 250);
} }
}, [channelId]); }
const isLocked = useMemo(() => { const isLocked = !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || !PermissionStore.can(PermissionsBits.CONNECT, channel);
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || !PermissionStore.can(PermissionsBits.CONNECT, channel);
}, [channelId]);
if (channel == null) return null;
return ( return (
<Tooltip <Tooltip
text={<VoiceChannelTooltip channel={channel} />} text={<VoiceChannelTooltip channel={channel} />}
tooltipClassName={cl("tooltip-container")} tooltipClassName={cl("tooltip-container")}
tooltipContentClassName={cl("tooltip-content")}
> >
{props => {props =>
isLocked ? isLocked ?

View file

@ -32,7 +32,7 @@ const settings = definePluginSettings({
default: true, default: true,
restartNeeded: true restartNeeded: true
}, },
showInVoiceMemberList: { showInMemberList: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
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,
@ -82,12 +82,12 @@ export default definePlugin({
match: /\.subtext,children:.+?}\)\]}\)(?=])/, match: /\.subtext,children:.+?}\)\]}\)(?=])/,
replace: "$&,$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})" replace: "$&,$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})"
}, },
predicate: () => settings.store.showInVoiceMemberList predicate: () => settings.store.showInMemberList
} }
], ],
start() { start() {
if (settings.store.showInVoiceMemberList) { if (settings.store.showInMemberList) {
addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
} }
}, },

View file

@ -15,7 +15,13 @@
} }
.vc-uvs-tooltip-container { .vc-uvs-tooltip-container {
max-width: 200px; max-width: 300px;
}
.vc-uvs-tooltip-content {
display: flex;
flex-direction: column;
gap: 6px;
} }
.vc-uvs-guild-name { .vc-uvs-guild-name {
@ -31,7 +37,5 @@
.vc-uvs-vc-members { .vc-uvs-vc-members {
display: flex; display: flex;
margin: 8px 0;
flex-direction: row;
gap: 6px; gap: 6px;
} }