mirror of
https://github.com/RobinRMC/VencordPlus.git
synced 2025-05-10 17:35:39 +02:00
Update ToastNotifications
This commit is contained in:
parent
4dc444427f
commit
126ce3fdc9
5 changed files with 401 additions and 119 deletions
|
@ -1,20 +1,8 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
|
@ -35,11 +23,21 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
|||
dismissOnClick,
|
||||
index,
|
||||
onClick,
|
||||
onClose
|
||||
onClose,
|
||||
attachments
|
||||
}: NotificationData & { index?: number; }) {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const [elapsed, setElapsed] = useState(0);
|
||||
|
||||
let renderBody: boolean = true;
|
||||
let footer: boolean = false;
|
||||
|
||||
if (attachments > 0)
|
||||
footer = true;
|
||||
|
||||
if (body === "")
|
||||
renderBody = false;
|
||||
|
||||
// Precompute appearance settings.
|
||||
const AppearanceSettings = {
|
||||
position: `toastnotifications-position-${PluginSettings.store.position || "bottom-left"}`,
|
||||
|
@ -115,11 +113,12 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
|||
</button>
|
||||
</div>
|
||||
<div>
|
||||
{richBody ?? <p className="toastnotifications-notification-p">{body}</p>}
|
||||
{renderBody ? richBody ?? <p className="toastnotifications-notification-p">{body}</p> : null}
|
||||
{PluginSettings.store.renderImages && image && <img className="toastnotifications-notification-img" src={image} alt="ToastNotification Image" />}
|
||||
{footer && <p className="toastnotifications-notification-footer">{`${attachments} attachment${attachments > 1 ? "s" : ""} ${attachments > 1 ? "were" : "was"} sent.`}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{image && <img className="toastnotifications-notification-img" src={image} alt="ToastNotification Image" />}
|
||||
{AppearanceSettings.timeout !== 0 && !permanent && (
|
||||
<div
|
||||
className="toastnotifications-notification-progressbar"
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { React, ReactDOM } from "@webpack/common";
|
||||
import type { ReactNode } from "react";
|
||||
import type { JSX, ReactNode } from "react";
|
||||
import type { Root } from "react-dom/client";
|
||||
|
||||
import { settings as PluginSettings } from "../index";
|
||||
|
@ -51,6 +39,7 @@ export interface NotificationData {
|
|||
image?: string; // Large image to display in the notification for attachments.
|
||||
permanent?: boolean; // Whether or not the notification should be permanent or timeout.
|
||||
dismissOnClick?: boolean; // Whether or not the notification should be dismissed when clicked.
|
||||
attachments: number;
|
||||
onClick?(): void;
|
||||
onClose?(): void;
|
||||
}
|
||||
|
|
|
@ -97,11 +97,21 @@
|
|||
|
||||
.toastnotifications-notification-p {
|
||||
margin: 0.5rem 0 0;
|
||||
margin-bottom: 3px;
|
||||
line-height: 140%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.toastnotifications-notification-footer {
|
||||
margin: 0;
|
||||
margin-top: 4px;
|
||||
line-height: 140%;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.toastnotifications-notification-img {
|
||||
width: 100%;
|
||||
width: 75%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Notification Positioning CSS */
|
||||
|
@ -129,7 +139,8 @@
|
|||
.toastnotifications-mention-class {
|
||||
color: var(--mention-foreground);
|
||||
background: var(--mention-background);
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
unicode-bidi: -moz-plaintext;
|
||||
unicode-bidi: plaintext;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,26 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, GuildStore, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||
import type { Channel, Message, User } from "discord-types/general";
|
||||
import { findByPropsLazy, findStore } from "@webpack";
|
||||
import { Button, ChannelStore, GuildStore, NavigationRouter, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||
import { Channel, Message, User } from "discord-types/general";
|
||||
import { ReactNode } from "react";
|
||||
import { Webpack } from "Vencord";
|
||||
|
||||
import { NotificationData, showNotification } from "./components/Notifications";
|
||||
import { MessageTypes } from "./types";
|
||||
import { MessageTypes, RelationshipType, StreamingTreatment } from "./types";
|
||||
|
||||
let ignoredUsers: string[] = [];
|
||||
let notifyFor: string[] = [];
|
||||
|
||||
// Functional variables.
|
||||
const MuteStore = Webpack.findByPropsLazy("isSuppressEveryoneEnabled");
|
||||
const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled");
|
||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||
const UserUtils = findByPropsLazy("getGlobalName");
|
||||
|
||||
|
@ -79,6 +69,72 @@ export const settings = definePluginSettings({
|
|||
default: 3,
|
||||
markers: makeRange(1, 5, 1)
|
||||
},
|
||||
determineServerNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically determine which server notifications to show based on your channel/guild settings",
|
||||
default: true
|
||||
},
|
||||
disableInStreamerMode: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disable notifications while in streamer mode",
|
||||
default: true
|
||||
},
|
||||
renderImages: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Render images in notifications",
|
||||
default: true
|
||||
},
|
||||
directMessages: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show notifications for direct messages",
|
||||
default: true
|
||||
},
|
||||
groupMessages: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show notifications for group messages",
|
||||
default: true
|
||||
},
|
||||
friendServerNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show notifications when friends send messages in servers they share with you",
|
||||
default: true
|
||||
},
|
||||
friendActivity: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show notifications for adding someone or receiving a friend request",
|
||||
default: true
|
||||
},
|
||||
streamingTreatment: {
|
||||
type: OptionType.SELECT,
|
||||
description: "How to treat notifications while sharing your screen",
|
||||
options: [
|
||||
{
|
||||
label: "Normal - Show notifications as normal",
|
||||
value: StreamingTreatment.NORMAL,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "No Content - Hide notifications body",
|
||||
value: StreamingTreatment.NO_CONTENT
|
||||
},
|
||||
{
|
||||
label: "Ignore - Don't show notifications at all",
|
||||
value: StreamingTreatment.IGNORE
|
||||
}
|
||||
]
|
||||
},
|
||||
notifyFor: {
|
||||
type: OptionType.STRING,
|
||||
description: "Create a list of channel IDs to receive notifications from (separate with commas)",
|
||||
onChange: () => { notifyFor = stringToList(settings.store.notifyFor); },
|
||||
default: ""
|
||||
},
|
||||
ignoreUsers: {
|
||||
type: OptionType.STRING,
|
||||
description: "Create a list of user IDs to not receive notifications from (separate with commas)",
|
||||
onChange: () => { ignoredUsers = stringToList(settings.store.ignoreUsers); },
|
||||
default: ""
|
||||
},
|
||||
exampleButton: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "Show an example toast notification",
|
||||
|
@ -89,26 +145,38 @@ export const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* getName()
|
||||
* Helper function to get a user's nickname if they have one, otherwise their username.
|
||||
*
|
||||
* @param {User} user The user to get the name of.
|
||||
* @returns {String} The name of the user.
|
||||
*/
|
||||
function stringToList(str: string): string[] {
|
||||
if (str !== "") {
|
||||
const array: string[] = [];
|
||||
const string = str.replace(/\s/g, "");
|
||||
const splitArray = string.split(",");
|
||||
splitArray.forEach(id => {
|
||||
array.push(id);
|
||||
});
|
||||
|
||||
return array;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function limitMessageLength(body: string, hasAttachments: boolean): string {
|
||||
if (hasAttachments) {
|
||||
if (body?.length > 30) {
|
||||
return body.substring(0, 27) + "...";
|
||||
}
|
||||
}
|
||||
|
||||
if (body?.length > 165) {
|
||||
return body.substring(0, 162) + "...";
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function getName(user: User): string {
|
||||
return RelationshipStore.getNickname(user.id) ?? UserUtils.getName(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* addMention()
|
||||
* Helper function to add a mention to a notification.
|
||||
*
|
||||
* @param {string} id The id of the user, channel or role.
|
||||
* @param {string} type The type of mention.
|
||||
* @param {string} guildId The id of the guild.
|
||||
* @returns {ReactNode} The mention as a ReactNode.
|
||||
*/
|
||||
const addMention = (id: string, type: string, guildId?: string): ReactNode => {
|
||||
let name;
|
||||
if (type === "user")
|
||||
|
@ -128,36 +196,51 @@ const addMention = (id: string, type: string, guildId?: string): ReactNode => {
|
|||
|
||||
export default definePlugin({
|
||||
name: "ToastNotifications",
|
||||
description: "Show a toast notification whenever you receive a direct message.",
|
||||
authors: [Devs.Skully],
|
||||
description: "Receive in-app notifications",
|
||||
authors: [{ name: "Skully", id: 150298098516754432n }, EquicordDevs.Ethan, EquicordDevs.Buzzy],
|
||||
settings,
|
||||
flux: {
|
||||
async MESSAGE_CREATE({ message }: { message: Message; }) {
|
||||
|
||||
const channel: Channel = ChannelStore.getChannel(message.channel_id);
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
|
||||
// Determine whether or not to show notifications.
|
||||
const isStreaming = findStore("ApplicationStreamingStore").getAnyStreamForUser(UserStore.getCurrentUser()?.id);
|
||||
|
||||
const streamerMode = settings.store.disableInStreamerMode;
|
||||
const currentUserStreamerMode = findStore("StreamerModeStore").enabled;
|
||||
|
||||
if (streamerMode && currentUserStreamerMode) return;
|
||||
if (isStreaming && settings.store.streamingTreatment === StreamingTreatment.IGNORE) return;
|
||||
|
||||
if (
|
||||
(
|
||||
(channel.guild_id) // If this is a guild message and not a private message.
|
||||
|| (message.author.id === currentUser.id) // If message is from the user.
|
||||
|| (!MuteStore.allowAllMessages(channel)) // If user has muted the channel.
|
||||
(message.author.id === currentUser.id) // If message is from the user.
|
||||
|| (channel.id === SelectedChannelStore.getChannelId()) // If the user is currently in the channel.
|
||||
|| (ignoredUsers.includes(message.author.id)) // If the user is ignored.
|
||||
)
|
||||
) return;
|
||||
|
||||
if (channel.guild_id) { // If this is a guild message and not a private message.
|
||||
handleGuildMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.store.directMessages && channel.isDM() || !settings.store.groupMessages && channel.isGroupDM() || MuteStore.isChannelMuted(null, channel.id)) return;
|
||||
|
||||
// Prepare the notification.
|
||||
const Notification: NotificationData = {
|
||||
title: getName(message.author),
|
||||
icon: `https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`,
|
||||
body: message.content,
|
||||
attachments: message.attachments?.length,
|
||||
richBody: null,
|
||||
permanent: false,
|
||||
onClick() { SelectedChannelActionCreators.selectPrivateChannel(message.channel_id); }
|
||||
};
|
||||
|
||||
const notificationText = message.content.length > 0 ? message.content : false;
|
||||
const richBodyElements: React.ReactNode[] = [];
|
||||
const notificationText = message.content?.length > 0 ? message.content : false;
|
||||
const richBodyElements: ReactNode[] = [];
|
||||
|
||||
// If this channel is a group DM, include the channel name.
|
||||
if (channel.isGroupDM()) {
|
||||
|
@ -167,7 +250,7 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
// Finally, truncate the channel name if it's too long.
|
||||
const truncatedChannelName = channelName.length > 20 ? channelName.substring(0, 20) + "..." : channelName;
|
||||
const truncatedChannelName = channelName?.length > 20 ? channelName.substring(0, 20) + "..." : channelName;
|
||||
Notification.title = `${message.author.username} (${truncatedChannelName})`;
|
||||
}
|
||||
else if (channel.guild_id) // If this is a guild message and not a private message.
|
||||
|
@ -183,14 +266,16 @@ export default definePlugin({
|
|||
}
|
||||
case MessageTypes.CHANNEL_RECIPIENT_ADD: {
|
||||
const actor = UserStore.getUser(message.author.id);
|
||||
const targetUser = UserStore.getUser(message.mentions[0]?.id);
|
||||
const user = message.mentions[0];
|
||||
const targetUser = UserStore.getUser((user as any).id);
|
||||
|
||||
Notification.body = `${getName(targetUser)} was added to the group by ${getName(actor)}.`;
|
||||
break;
|
||||
}
|
||||
case MessageTypes.CHANNEL_RECIPIENT_REMOVE: {
|
||||
const actor = UserStore.getUser(message.author.id);
|
||||
const targetUser = UserStore.getUser(message.mentions[0]?.id);
|
||||
const user = message.mentions[0];
|
||||
const targetUser = UserStore.getUser((user as any).id);
|
||||
|
||||
if (actor.id !== targetUser.id) {
|
||||
Notification.body = `${getName(targetUser)} was removed from the group by ${getName(actor)}.`;
|
||||
|
@ -214,7 +299,7 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
// Message contains an embed.
|
||||
if (message.embeds.length !== 0) {
|
||||
if (message.embeds?.length !== 0) {
|
||||
Notification.body = notificationText || "Sent an embed.";
|
||||
}
|
||||
|
||||
|
@ -224,11 +309,11 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
// Message contains an attachment.
|
||||
if (message.attachments.length !== 0) {
|
||||
if (message.attachments?.length !== 0) {
|
||||
const images = message.attachments.filter(e => typeof e?.content_type === "string" && e?.content_type.startsWith("image"));
|
||||
// Label the notification with the attachment type.
|
||||
if (images.length !== 0) {
|
||||
Notification.body = notificationText || "Sent an image.";
|
||||
if (images?.length !== 0) {
|
||||
Notification.body = notificationText || ""; // Dont show any body
|
||||
Notification.image = images[0].url;
|
||||
} else {
|
||||
Notification.body += ` [Attachment: ${message.attachments[0].filename}]`;
|
||||
|
@ -244,7 +329,7 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
// Replace any mention of users, roles and channels.
|
||||
if (message.mentions.length !== 0 || message.mentionRoles?.length > 0) {
|
||||
if (message.mentions?.length !== 0 || message.mentionRoles?.length > 0) {
|
||||
let lastIndex = 0;
|
||||
Notification.body.replace(USER_MENTION_REGEX, (match, userId, channelId, roleId, offset) => {
|
||||
richBodyElements.push(Notification.body.slice(lastIndex, offset));
|
||||
|
@ -258,32 +343,229 @@ export default definePlugin({
|
|||
richBodyElements.push(addMention(roleId, "role", channel.guild_id));
|
||||
}
|
||||
|
||||
lastIndex = offset + match.length;
|
||||
lastIndex = offset + match?.length;
|
||||
return match; // This value is not used but is necessary for the replace function
|
||||
});
|
||||
}
|
||||
|
||||
if (richBodyElements.length > 0) {
|
||||
if (richBodyElements?.length > 0) {
|
||||
const MyRichBodyComponent = () => <>{richBodyElements}</>;
|
||||
Notification.richBody = <MyRichBodyComponent />;
|
||||
}
|
||||
|
||||
Notification.body = limitMessageLength(Notification.body, Notification.attachments > 0);
|
||||
|
||||
if (isStreaming && settings.store.streamingTreatment === StreamingTreatment.NO_CONTENT) {
|
||||
Notification.body = "Message content has been redacted.";
|
||||
}
|
||||
|
||||
if (!settings.store.renderImages) {
|
||||
Notification.icon = undefined;
|
||||
}
|
||||
|
||||
showNotification(Notification);
|
||||
},
|
||||
|
||||
async RELATIONSHIP_ADD({ relationship }) {
|
||||
if (ignoredUsers.includes(relationship.user.id)) return;
|
||||
relationshipAdd(relationship.user, relationship.type);
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
ignoredUsers = stringToList(settings.store.ignoreUsers);
|
||||
notifyFor = stringToList(settings.store.notifyFor);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* showExampleNotification()
|
||||
* Helper function to show an example notification.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the notification is shown.
|
||||
*/
|
||||
function switchChannels(guildId: string | null, channelId: string) {
|
||||
if (!ChannelStore.hasChannel(channelId)) return;
|
||||
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}/`);
|
||||
}
|
||||
|
||||
enum NotificationLevel {
|
||||
ALL_MESSAGES = 0,
|
||||
ONLY_MENTIONS = 1,
|
||||
NO_MESSAGES = 2
|
||||
}
|
||||
|
||||
function findNotificationLevel(channel: Channel): NotificationLevel {
|
||||
const store = findStore("UserGuildSettingsStore");
|
||||
const userGuildSettings = store.getAllSettings().userGuildSettings[channel.guild_id];
|
||||
|
||||
if (!settings.store.determineServerNotifications || MuteStore.isGuildOrCategoryOrChannelMuted(channel.guild_id, channel.id)) {
|
||||
return NotificationLevel.NO_MESSAGES;
|
||||
}
|
||||
|
||||
if (userGuildSettings) {
|
||||
const channelOverrides = userGuildSettings.channel_overrides?.[channel.id];
|
||||
const guildDefault = userGuildSettings.message_notifications;
|
||||
|
||||
// Check if channel overrides exist and are in the expected format
|
||||
if (channelOverrides && typeof channelOverrides === "object" && "message_notifications" in channelOverrides) {
|
||||
return channelOverrides.message_notifications;
|
||||
}
|
||||
|
||||
// Check if guild default is in the expected format
|
||||
if (typeof guildDefault === "number") {
|
||||
return guildDefault;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a default value if no valid overrides or guild default is found
|
||||
return NotificationLevel.NO_MESSAGES;
|
||||
}
|
||||
|
||||
async function handleGuildMessage(message: Message) {
|
||||
const c = ChannelStore.getChannel(message.channel_id);
|
||||
const notificationLevel: number = findNotificationLevel(c);
|
||||
let t = false;
|
||||
// 0: All messages 1: Only mentions 2: No messages
|
||||
// todo: check if the user who sent it is a friend
|
||||
const all = notifyFor.includes(message.channel_id);
|
||||
const friend = settings.store.friendServerNotifications && RelationshipStore.isFriend(message.author.id);
|
||||
|
||||
|
||||
|
||||
if (!all && !friend) {
|
||||
t = true;
|
||||
const isMention: boolean = message.content.includes(`<@${UserStore.getCurrentUser().id}>`);
|
||||
const meetsMentionCriteria = notificationLevel !== NotificationLevel.ALL_MESSAGES && !isMention;
|
||||
|
||||
if (notificationLevel === NotificationLevel.NO_MESSAGES || meetsMentionCriteria) return;
|
||||
}
|
||||
|
||||
const channel: Channel = ChannelStore.getChannel(message.channel_id);
|
||||
|
||||
const notificationText = message.content.length > 0 ? message.content : false;
|
||||
const richBodyElements: React.ReactNode[] = [];
|
||||
|
||||
// Prepare the notification.
|
||||
const Notification: NotificationData = {
|
||||
title: `${getName(message.author)} (#${channel.name})`,
|
||||
icon: `https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`,
|
||||
body: message.content,
|
||||
attachments: message.attachments?.length,
|
||||
richBody: null,
|
||||
permanent: false,
|
||||
onClick() { switchChannels(channel.guild_id, channel.id); }
|
||||
};
|
||||
|
||||
if (message.embeds?.length !== 0) {
|
||||
Notification.body = notificationText || "Sent an embed.";
|
||||
}
|
||||
|
||||
// Message contains a sticker.
|
||||
if (message?.stickerItems) {
|
||||
Notification.body = notificationText || "Sent a sticker.";
|
||||
}
|
||||
|
||||
// Message contains an attachment.
|
||||
if (message.attachments?.length !== 0) {
|
||||
const images = message.attachments.filter(e => typeof e?.content_type === "string" && e?.content_type.startsWith("image"));
|
||||
// Label the notification with the attachment type.
|
||||
if (images?.length !== 0) {
|
||||
Notification.body = notificationText || ""; // Dont show any body
|
||||
Notification.image = images[0].url;
|
||||
} else {
|
||||
Notification.body += ` [Attachment: ${message.attachments[0].filename}]`;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Format emotes properly.
|
||||
const matches = Notification.body.match(new RegExp("(<a?:\\w+:\\d+>)", "g"));
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
Notification.body = Notification.body.replace(new RegExp(`${match}`, "g"), `:${match.split(":")[1]}:`);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any mention of users, roles and channels.
|
||||
if (message.mentions?.length !== 0 || message.mentionRoles?.length > 0) {
|
||||
let lastIndex = 0;
|
||||
Notification.body.replace(USER_MENTION_REGEX, (match, userId, channelId, roleId, offset) => {
|
||||
richBodyElements.push(Notification.body.slice(lastIndex, offset));
|
||||
|
||||
// Add the mention itself as a styled span.
|
||||
if (userId) {
|
||||
richBodyElements.push(addMention(userId, "user"));
|
||||
} else if (channelId) {
|
||||
richBodyElements.push(addMention(channelId, "channel"));
|
||||
} else if (roleId) {
|
||||
richBodyElements.push(addMention(roleId, "role", channel.guild_id));
|
||||
}
|
||||
|
||||
lastIndex = offset + match?.length;
|
||||
return match; // This value is not used but is necessary for the replace function
|
||||
});
|
||||
}
|
||||
|
||||
if (richBodyElements?.length > 0) {
|
||||
const MyRichBodyComponent = () => <>{richBodyElements}</>;
|
||||
Notification.richBody = <MyRichBodyComponent />;
|
||||
}
|
||||
|
||||
Notification.body = limitMessageLength(Notification.body, Notification.attachments > 0);
|
||||
|
||||
const isStreaming = findStore("ApplicationStreamingStore").getAnyStreamForUser(UserStore.getCurrentUser()?.id);
|
||||
|
||||
if (isStreaming && settings.store.streamingTreatment === StreamingTreatment.NO_CONTENT) {
|
||||
Notification.body = "Message content has been redacted.";
|
||||
}
|
||||
|
||||
if (!settings.store.renderImages) {
|
||||
Notification.icon = undefined;
|
||||
}
|
||||
|
||||
console.log("Notification that went through: " + t);
|
||||
await showNotification(Notification);
|
||||
|
||||
}
|
||||
|
||||
async function relationshipAdd(user: User, type: Number) {
|
||||
user = UserStore.getUser(user.id);
|
||||
if (!settings.store.friendActivity) return;
|
||||
|
||||
const Notification: NotificationData = {
|
||||
title: "",
|
||||
icon: user.getAvatarURL(),
|
||||
body: "",
|
||||
attachments: 0,
|
||||
};
|
||||
|
||||
if (!settings.store.renderImages) {
|
||||
Notification.icon = undefined;
|
||||
}
|
||||
|
||||
if (type === RelationshipType.FRIEND) {
|
||||
Notification.title = `${user.username} is now your friend`;
|
||||
Notification.body = "You can now message them directly.";
|
||||
Notification.onClick = () => switchChannels(null, user.id);
|
||||
|
||||
|
||||
await showNotification(Notification);
|
||||
|
||||
} else if (type === RelationshipType.INCOMING_REQUEST) {
|
||||
|
||||
Notification.title = `${user.username} has sent you a friend request`;
|
||||
Notification.body = "You can accept or decline it in the Friends tab.";
|
||||
Notification.onClick = () => switchChannels(null, "");
|
||||
|
||||
await showNotification(Notification);
|
||||
}
|
||||
}
|
||||
|
||||
function showExampleNotification(): Promise<void> {
|
||||
return showNotification({
|
||||
title: "Example Notification",
|
||||
const Notification: NotificationData = {
|
||||
title: "Example notification",
|
||||
icon: `https://cdn.discordapp.com/avatars/${UserStore.getCurrentUser().id}/${UserStore.getCurrentUser().avatar}.png?size=128`,
|
||||
body: "This is an example toast notification!",
|
||||
attachments: 0,
|
||||
permanent: false
|
||||
});
|
||||
};
|
||||
|
||||
if (!settings.store.renderImages) {
|
||||
Notification.icon = undefined;
|
||||
}
|
||||
return showNotification(Notification);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const enum MessageTypes {
|
||||
CHANNEL_RECIPIENT_ADD = 1,
|
||||
|
@ -24,3 +12,16 @@ export const enum MessageTypes {
|
|||
CHANNEL_ICON_CHANGE = 5,
|
||||
CHANNEL_PINNED_MESSAGE = 6,
|
||||
}
|
||||
|
||||
export const enum RelationshipType {
|
||||
FRIEND = 1,
|
||||
BLOCKED = 2,
|
||||
INCOMING_REQUEST = 3,
|
||||
OUTGOING_REQUEST = 4,
|
||||
}
|
||||
|
||||
export const enum StreamingTreatment {
|
||||
NORMAL = 0,
|
||||
NO_CONTENT = 1,
|
||||
IGNORE = 2
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue