Plugin
BypassDND
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.
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import { type NavContextMenuPatchCallback } from "@api/ContextMenu";8
import { Notifications } from "@api/index";9
import { definePluginSettings } from "@api/Settings";10
import { Devs } from "@utils/constants";11
import { getCurrentChannel } from "@utils/discord";12
import { Logger } from "@utils/Logger";13
import definePlugin, { OptionType } from "@utils/types";14
import { ChannelStore, Menu, MessageStore, NavigationRouter, PresenceStore, PrivateChannelsStore, UserStore, WindowStore } from "@webpack/common";15
import type { Message, User as DiscordUser } from "discord-types/general";16
17
interface User extends DiscordUser {18
globalName: string;19
}20
21
interface IMessageCreate {22
channelId: string;23
guildId: string;24
message: Message;25
}26
27
function Icon(enabled?: boolean): JSX.Element {28
return <svg29
width="18"30
height="18"31
>32
<circle cx="9" cy="9" r="8" fill={!enabled ? "var(--status-danger)" : "currentColor"} />33
<circle cx="9" cy="9" r="3.75" fill={!enabled ? "white" : "black"} />34
</svg>;35
}36
37
function processIds(value: string): string {38
return value.replace(/\s/g, "").split(",").filter(id => id.trim() !== "").join(", ");39
}40
41
async function showNotification(message: Message, guildId: string | undefined): Promise<void> {42
const channel = ChannelStore.getChannel(message.channel_id);43
const channelRegex = /<#(\d{19})>/g;44
const userRegex = /<@(\d{18})>/g;45
46
message.content = message.content.replace(channelRegex, (match, channelId: string) => {47
return `#${ChannelStore.getChannel(channelId)?.name}`;48
});49
50
message.content = message.content.replace(userRegex, (match, userId: string) => {51
return `@${(UserStore.getUser(userId) as User).globalName}`;52
});53
54
await Notifications.showNotification({55
title: `${(message.author as User).globalName} ${guildId ? `(#${channel?.name}, ${ChannelStore.getChannel(channel?.parent_id)?.name})` : ""}`,56
body: message.content,57
icon: UserStore.getUser(message.author.id).getAvatarURL(undefined, undefined, false),58
onClick: function (): void {59
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${message.channel_id}/${message.id}`);60
}61
});62
}63
64
function ContextCallback(name: "guild" | "user" | "channel"): NavContextMenuPatchCallback {65
return (children, props) => {66
const type = props[name];67
if (!type) return;68
const enabled = settings.store[`${name}s`].split(", ").includes(type.id);69
if (name === "user" && type.id === UserStore.getCurrentUser().id) return;70
children.splice(-1, 0, (71
<Menu.MenuGroup>72
<Menu.MenuItem73
id={`dnd-${name}-bypass`}74
label={`${enabled ? "Remove" : "Add"} DND Bypass`}75
icon={() => Icon(enabled)}76
action={() => {77
let bypasses: string[] = settings.store[`${name}s`].split(", ");78
if (enabled) bypasses = bypasses.filter(id => id !== type.id);79
else bypasses.push(type.id);80
settings.store[`${name}s`] = bypasses.filter(id => id.trim() !== "").join(", ");81
}}82
/>83
</Menu.MenuGroup>84
));85
};86
}87
88
const settings = definePluginSettings({89
guilds: {90
type: OptionType.STRING,91
description: "Guilds to let bypass (notified when pinged anywhere in guild)",92
default: "",93
placeholder: "Separate with commas",94
onChange: value => settings.store.guilds = processIds(value)95
},96
channels: {97
type: OptionType.STRING,98
description: "Channels to let bypass (notified when pinged in that channel)",99
default: "",100
placeholder: "Separate with commas",101
onChange: value => settings.store.channels = processIds(value)102
},103
users: {104
type: OptionType.STRING,105
description: "Users to let bypass (notified for all messages sent in DMs)",106
default: "",107
placeholder: "Separate with commas",108
onChange: value => settings.store.users = processIds(value)109
},110
allowOutsideOfDms: {111
type: OptionType.BOOLEAN,112
description: "Allow selected users to bypass DND outside of DMs too (acts like a channel/guild bypass, but it039;s for all messages sent by the selected users)"113
}114
});115
116
export default definePlugin({117
name: "BypassDND",118
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.",119
authors: [Devs.FiveCord],120
flux: {121
async MESSAGE_CREATE({ message, guildId, channelId }: IMessageCreate): Promise<void> {122
try {123
const currentUser = UserStore.getCurrentUser();124
const userStatus = await PresenceStore.getStatus(currentUser.id);125
const currentChannelId = getCurrentChannel()?.id ?? "0";126
if (message.state === "SENDING" || message.content === "" || message.author.id === currentUser.id || (channelId === currentChannelId && WindowStore.isFocused()) || userStatus !== "dnd") {127
return;128
}129
const mentioned = MessageStore.getMessage(channelId, message.id)?.mentioned;130
if ((settings.store.guilds.split(", ").includes(guildId) || settings.store.channels.split(", ").includes(channelId)) && mentioned) {131
await showNotification(message, guildId);132
} else if (settings.store.users.split(", ").includes(message.author.id)) {133
const userChannelId = await PrivateChannelsStore.getOrEnsurePrivateChannel(message.author.id);134
if (channelId === userChannelId || (mentioned && settings.store.allowOutsideOfDms === true)) {135
await showNotification(message, guildId);136
}137
}138
} catch (error) {139
new Logger("BypassDND").error("Failed to handle message", error);140
}141
}142
},143
settings,144
contextMenus: {145
"guild-context": ContextCallback("guild"),146
"channel-context": ContextCallback("channel"),147
"user-context": ContextCallback("user"),148
}149
});150