From 165353321d1c6416715e54c9447360ceb305ae35 Mon Sep 17 00:00:00 2001 From: Vexify Date: Wed, 6 Nov 2024 21:12:19 +0100 Subject: [PATCH] feat(plugin): voiceMuteBlockedUsers Basically as the title describes. Normal block just restrains certain users from messaging you etc. This also voicemutes them. Option to do this automatic or with button. --- src/plugins/voiceMuteBlockedUsers/index.tsx | 223 ++++++++++++++++++++ src/utils/constants.ts | 6 +- 2 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/plugins/voiceMuteBlockedUsers/index.tsx diff --git a/src/plugins/voiceMuteBlockedUsers/index.tsx b/src/plugins/voiceMuteBlockedUsers/index.tsx new file mode 100644 index 000000000..9fa096601 --- /dev/null +++ b/src/plugins/voiceMuteBlockedUsers/index.tsx @@ -0,0 +1,223 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * I got told to create a new plugin for that! + * I had the option to add this as a new* feature for src/plugins/noBlockedMessages + * But I also didnt like the name noBlockedMessages as this does nothing about messages. Only transforms a block to a "real" block. + * I asked if I should add this as a feature and maybe rename the plugin to noBlockedUsers but this was unofficially denied (not by a mantainer). + */ + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Button, Menu, React, RelationshipStore, TooltipContainer, UserStore, } from "@webpack/common"; +import { User } from "discord-types/general"; + +const { toggleLocalMute } = findByPropsLazy("toggleLocalMute"); +const { isLocalMute } = findByPropsLazy("isLocalMute"); +const { addRelationship } = findByPropsLazy("addRelationship"); +const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner"); + + +/** + * Adds a "Mute and Block" or "Unmute and Unblock" item to user context menus. + * + * If the user is blocked, the action will unmute and unblock the user. Otherwise, + * the action will mute and block the user. + * + * @param children The menu items to add the new item to. + * @param props The props passed to the user context menu. + * @returns The modified children with the new item added. + */ +const userContextPatch: NavContextMenuPatchCallback = (children, { user }: { user?: User, onClose(): void; }) => { + if (!user) return; + + // Determine the label and action based on the user's blocked status + const isBlocked = RelationshipStore.isBlocked(user.id); + const lbl = isBlocked ? "Unmute and Unblock" : "Mute and Block"; + + /** + * Handles the logic for toggling mute and block/unblock when the user + * clicks the "Mute and Block" or "Unmute and Unblock" context menu item. + * + * If the user is currently blocked, the action will unmute and unblock. + * Otherwise, the action will mute and block. + * + * @private + */ + const action = () => { + // Toggle mute and handle block/unblock logic + if (isBlocked) { + // Logic to unblock the user + if (isLocalMute(user.id)) { + toggleLocalMute(user.id); // Unmute the user + } + unblockUser(user); + } else { + // Logic to block the user + if (!isLocalMute(user.id)) { + toggleLocalMute(user.id); // Mute the user + } + blockUser(user); + } + }; + + children.push( + + ); +}; + +/** + * Blocks a user by adding a relationship with type 2. + * + * This function updates the relationship store to mark the specified + * user as blocked. The block action is associated with the "ContextMenu" + * location. + * + * @param user The user to block. + */ +function blockUser(user: User) { + addRelationship({ + userId: user.id, type: 2, context: { + location: "ContextMenu" + } + }); +} + + +/** + * Unblocks a user by adding a relationship with type 0. + * + * This function updates the relationship store to mark the specified + * user as unblocked. The unblock action is associated with the "ContextMenu" + * location. + * + * @param user The user to unblock. + */ +function unblockUser(user: User) { + addRelationship({ + userId: user.id, type: 0, context: { + location: "ContextMenu" + } + }); +} + +const settings = definePluginSettings({ + autoMuteBlocked: { + type: OptionType.BOOLEAN, + default: true, + description: "Automatically mute blocked users.", + restartNeeded: false + } +}); + +export default definePlugin({ + name: "VoiceMuteBlockedUsers", + description: `Automatically voice-mute blocked users for better visibility and manageability in your Discord experience. Transform normal blocks into "real" ones with ease, ensuring a smoother and more organized communication environment.`, + authors: [Devs.notvexi], + settings, + patches: [], + contextMenus: { + "user-context": userContextPatch, + "user-profile-actions": userContextPatch, + "user-profile-overflow-menu": userContextPatch + }, + /** + * Attaches a listener to the RelationshipStore and runs the automatic muted user check once. + * + * The listener is needed to detect when a user is blocked and should be muted. + * The initial function call is needed to mute users that are already blocked when the plugin is started. + */ + start() { + RelationshipStore.addChangeListener(() => { + this.automaticMuteBlockedUsers(); + }); + this.automaticMuteBlockedUsers(); + }, + /** + * Automatically mutes all blocked users and unmutes all unblocked users. + * + * This function is called whenever the RelationshipStore changes. It is + * also called once when the plugin is started. It is responsible for + * updating the mute status of users based on their blocked status. + * + * @private + */ + automaticMuteBlockedUsers() { + const { autoMuteBlocked } = settings.store; + if (!autoMuteBlocked) return; + + // Get all relationships and filter for blocked users + const blockedIds = Object.entries(RelationshipStore.getRelationships()) + .filter(([_, v]) => v === 2) // 2 represents blocked + .map(([k]) => UserStore.getUser(k).id); // Get user IDs of blocked users + + // Mute blocked users + for (const ID of blockedIds) { + if (!isLocalMute(ID)) { + toggleLocalMute(ID); + } + } + + // Check for unblocked users + const allUsers = UserStore.getUsers(); // Get all users as a Record + const allUserIds = Object.keys(allUsers); + + for (const ID of allUserIds) { + if (!RelationshipStore.isBlocked(ID)) { + // Unmute the user if they are unblocked + if (isLocalMute(ID)) { + toggleLocalMute(ID); + } + } + } + }, + BlockUnblockButton: ErrorBoundary.wrap(({ user }: { user: User; }) => { + if (!user) return null; // Return null if no user is provided + + // Determine the button label based on the user's blocked status + const isBlocked = RelationshipStore.isBlocked(user.id); + const lbl = isBlocked ? "Unmute and Unblock" : "Mute and Block"; + + return ( + + + + ); + }, { noop: true }) +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 70eca56fd..31072979f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -575,10 +575,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "RamziAH", id: 1279957227612147747n, }, - SomeAspy: { + SomeAspy: { name: "SomeAspy", id: 516750892372852754n, }, + notvexi: { + name: "lethalfluff", + id: 337568120028004362n + } } satisfies Record); // iife so #__PURE__ works correctly