From 15f0b9079199e95d04b144c5f324da8723c7e099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=B1ra=C3=A7=20Arma=C4=9Fan=20=C3=96nal?= Date: Mon, 3 Mar 2025 22:28:31 +0300 Subject: [PATCH] Implement FollowVoiceUser plugin. (Friends Only) (#170) * fix white typing indicators in servers (#166) * Drop DoNotLeak + Extras Co-Authored-By: thororen1234 Co-Authored-By: thororen <78185467+thororen1234@users.noreply.github.com> Co-Authored-By: sadan4 <117494111+sadan4@users.noreply.github.com> Co-Authored-By: ynot01 Co-Authored-By: MrDiamondDog <84212701+MrDiamondDog@users.noreply.github.com> Co-Authored-By: Crxaw <48805031+sitescript@users.noreply.github.com> * Finish Purge For Merge * Implement FollowVoiceUser plugin. FollowVoiceUser allows you to follow any user in voice chat if they are same in same guilds as you. * Make FollowBoiceUser friends only. * Update index.tsx * Fix Description For Friend Not User * Tweaks * Tweaks * Update index.tsx * Update index.tsx --------- Co-authored-by: thororen1234 Co-authored-by: mochie Co-authored-by: panbread <93918332+Panniku@users.noreply.github.com> Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> Co-authored-by: sadan4 <117494111+sadan4@users.noreply.github.com> Co-authored-by: ynot01 Co-authored-by: MrDiamondDog <84212701+MrDiamondDog@users.noreply.github.com> Co-authored-by: Crxaw <48805031+sitescript@users.noreply.github.com> --- src/equicordplugins/followVoiceUser/index.tsx | 117 ++++++++++++++++++ src/utils/constants.ts | 4 + 2 files changed, 121 insertions(+) create mode 100644 src/equicordplugins/followVoiceUser/index.tsx diff --git a/src/equicordplugins/followVoiceUser/index.tsx b/src/equicordplugins/followVoiceUser/index.tsx new file mode 100644 index 00000000..f88df7f3 --- /dev/null +++ b/src/equicordplugins/followVoiceUser/index.tsx @@ -0,0 +1,117 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { Menu, React } from "@webpack/common"; +import { VoiceState } from "@webpack/types"; +import { Channel, User } from "discord-types/general"; + +type TFollowedUserInfo = { + lastChannelId: string; + userId: string; +} | null; + +interface UserContextProps { + channel: Channel; + user: User; + guildId?: string; +} + +let followedUserInfo: TFollowedUserInfo = null; + +const voiceChannelAction = findByPropsLazy("selectVoiceChannel"); +const VoiceStateStore = findStoreLazy("VoiceStateStore"); +const UserStore = findStoreLazy("UserStore"); +const RelationshipStore = findStoreLazy("RelationshipStore"); + +const settings = definePluginSettings({ + onlyWhenInVoice: { + type: OptionType.BOOLEAN, + default: true, + description: "Only follow the user when you are in a voice channel" + }, + leaveWhenUserLeaves: { + type: OptionType.BOOLEAN, + default: false, + description: "Leave the voice channel when the user leaves. (That can cause you to sometimes enter infinite leave/join loop)" + } +}); + +const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, user }: UserContextProps) => { + if (UserStore.getCurrentUser().id === user.id || !RelationshipStore.getFriendIDs().includes(user.id)) return; + + const [checked, setChecked] = React.useState(followedUserInfo?.userId === user.id); + + children.push( + , + { + if (followedUserInfo?.userId === user.id) { + followedUserInfo = null; + setChecked(false); + return; + } + + followedUserInfo = { + lastChannelId: UserStore.getCurrentUser().id, + userId: user.id + }; + setChecked(true); + }} + > + ); +}; + + +export default definePlugin({ + name: "FollowVoiceUser", + description: "Follow a friend in voice chat.", + authors: [EquicordDevs.TheArmagan], + settings, + settingsAboutComponent: () => <> + + This Plugin is used to follow a Friend/Friends into voice chat(s). + + , + flux: { + async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { + if (!followedUserInfo) return; + if (!RelationshipStore.getFriendIDs().includes(followedUserInfo.userId)) return; + + if ( + settings.store.onlyWhenInVoice + && VoiceStateStore.getVoiceStateForUser(UserStore.getCurrentUser().id) === null + ) return; + + voiceStates.forEach(voiceState => { + if ( + voiceState.userId === followedUserInfo!.userId + && voiceState.channelId + && voiceState.channelId !== followedUserInfo!.lastChannelId + ) { + followedUserInfo!.lastChannelId = voiceState.channelId; + voiceChannelAction.selectVoiceChannel(followedUserInfo!.lastChannelId); + } else if ( + voiceState.userId === followedUserInfo!.userId + && !voiceState.channelId + && settings.store.leaveWhenUserLeaves + ) { + voiceChannelAction.selectVoiceChannel(null); + } + }); + } + }, + contextMenus: { + "user-context": UserContextMenuPatch + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a602c003..fedaf688 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1012,6 +1012,10 @@ export const EquicordDevs = Object.freeze({ name: "PhoenixAceVFX", id: 1016895892055396484n, }, + TheArmagan: { + name: "TheArmagan", + id: 707309693449535599n + } } satisfies Record); // iife so #__PURE__ works correctly