/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { type NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Notifications } from "@api/index"; import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { ChannelStore, Menu, MessageStore, NavigationRouter, PresenceStore, PrivateChannelsStore, UserStore, WindowStore } from "@webpack/common"; import type { Message } from "discord-types/general"; interface IMessageCreate { channelId: string; guildId: string; message: Message; } function Icon(enabled?: boolean): JSX.Element { return ; } function processIds(value: string): string { return value.replace(/\s/g, "").split(",").filter(id => id.trim() !== "").join(", "); } async function showNotification(message: Message, guildId: string | undefined): Promise { try { const channel = ChannelStore.getChannel(message.channel_id); const channelRegex = /<#(\d{19})>/g; const userRegex = /<@(\d{18})>/g; message.content = message.content.replace(channelRegex, (match: any, channelId: string) => { return `#${ChannelStore.getChannel(channelId)?.name}`; }); message.content = message.content.replace(userRegex, (match: any, userId: string) => { return `@${(UserStore.getUser(userId) as any).globalName}`; }); await Notifications.showNotification({ title: `${(message.author as any).globalName} ${guildId ? `(#${channel?.name}, ${ChannelStore.getChannel(channel?.parent_id)?.name})` : ""}`, body: message.content, icon: UserStore.getUser(message.author.id).getAvatarURL(undefined, undefined, false), onClick: function (): void { NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${message.channel_id}/${message.id}`); } }); if (settings.store.notificationSound) { new Audio("https://discord.com/assets/9422aef94aa931248105.mp3").play(); } } catch (error) { new Logger("BypassDND").error("Failed to notify user: ", error); } } function ContextCallback(name: "guild" | "user" | "channel"): NavContextMenuPatchCallback { return (children, props) => { const type = props[name]; if (!type) return; const enabled = settings.store[`${name}s`].split(", ").includes(type.id); if (name === "user" && type.id === UserStore.getCurrentUser().id) return; children.splice(-1, 0, ( Icon(enabled)} action={() => { let bypasses: string[] = settings.store[`${name}s`].split(", "); if (enabled) bypasses = bypasses.filter(id => id !== type.id); else bypasses.push(type.id); settings.store[`${name}s`] = bypasses.filter(id => id.trim() !== "").join(", "); }} /> )); }; } const settings = definePluginSettings({ guilds: { type: OptionType.STRING, description: "Guilds to let bypass (notified when pinged anywhere in guild)", default: "", placeholder: "Separate with commas", onChange: value => settings.store.guilds = processIds(value) }, channels: { type: OptionType.STRING, description: "Channels to let bypass (notified when pinged in that channel)", default: "", placeholder: "Separate with commas", onChange: value => settings.store.channels = processIds(value) }, users: { type: OptionType.STRING, description: "Users to let bypass (notified for all messages sent in DMs)", default: "", placeholder: "Separate with commas", onChange: value => settings.store.users = processIds(value) }, allowOutsideOfDms: { type: OptionType.BOOLEAN, description: "Allow selected users to bypass DND outside of DMs too (acts like a channel/guild bypass, but it's for all messages sent by the selected users)" }, notificationSound: { type: OptionType.BOOLEAN, description: "Whether the notification sound should be played", default: true, }, statusToUse: { type: OptionType.SELECT, description: "Status to use for whitelist", options: [ { label: "Online", value: "online", }, { label: "Idle", value: "idle", }, { label: "Do Not Disturb", value: "dnd", default: true }, { label: "Invisible", value: "invisible", } ] } }); migratePluginSettings("BypassStatus", "BypassDND"); export default definePlugin({ name: "BypassStatus", description: "Still get notifications from specific sources when in do not disturb mode. Right-click on users/channels/guilds to set them to bypass do not disturb mode.", authors: [Devs.Inbestigator], flux: { async MESSAGE_CREATE({ message, guildId, channelId }: IMessageCreate): Promise { try { const currentUser = UserStore.getCurrentUser(); const userStatus = await PresenceStore.getStatus(currentUser.id); const currentChannelId = getCurrentChannel()?.id ?? "0"; if (message.state === "SENDING" || message.content === "" || message.author.id === currentUser.id || (channelId === currentChannelId && WindowStore.isFocused()) || userStatus !== settings.store.statusToUse) { return; } const mentioned = MessageStore.getMessage(channelId, message.id)?.mentioned; if ((settings.store.guilds.split(", ").includes(guildId) || settings.store.channels.split(", ").includes(channelId)) && mentioned) { await showNotification(message, guildId); } else if (settings.store.users.split(", ").includes(message.author.id)) { const userChannelId = await PrivateChannelsStore.getOrEnsurePrivateChannel(message.author.id); if (channelId === userChannelId || (mentioned && settings.store.allowOutsideOfDms === true)) { await showNotification(message, guildId); } } } catch (error) { new Logger("BypassDND").error("Failed to handle message: ", error); } } }, settings, contextMenus: { "guild-context": ContextCallback("guild"), "channel-context": ContextCallback("channel"), "user-context": ContextCallback("user"), } });