mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-07 21:53:04 -04:00
ToastNotifications
This commit is contained in:
parent
720edbdc3b
commit
3d1c654c20
7 changed files with 1029 additions and 1 deletions
|
@ -11,7 +11,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
### Extra included plugins
|
### Extra included plugins
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>167 additional plugins</summary>
|
<summary>168 additional plugins</summary>
|
||||||
|
|
||||||
### All Platforms
|
### All Platforms
|
||||||
|
|
||||||
|
@ -152,6 +152,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
- ThemeLibrary by Fafa
|
- ThemeLibrary by Fafa
|
||||||
- Timezones by Aria
|
- Timezones by Aria
|
||||||
- Title by Kyuuhachi
|
- Title by Kyuuhachi
|
||||||
|
- ToastNotifications by Skully, Ethan, Buzzy
|
||||||
- ToggleVideoBind by mochie
|
- ToggleVideoBind by mochie
|
||||||
- TosuRPC by AutumnVN
|
- TosuRPC by AutumnVN
|
||||||
- Translate+ by Prince527 & Ven
|
- Translate+ by Prince527 & Ven
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
|
import { React, useEffect, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { settings as PluginSettings } from "../index";
|
||||||
|
import { NotificationData } from "./Notifications";
|
||||||
|
|
||||||
|
export default ErrorBoundary.wrap(function NotificationComponent({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
richBody,
|
||||||
|
icon,
|
||||||
|
image,
|
||||||
|
permanent,
|
||||||
|
dismissOnClick,
|
||||||
|
index,
|
||||||
|
onClick,
|
||||||
|
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"}`,
|
||||||
|
timeout: (PluginSettings.store.timeout * 1000) || 5000,
|
||||||
|
opacity: PluginSettings.store.opacity / 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = useMemo(() => Date.now(), [isHover]); // Reset the timer when the user hovers over the notification.
|
||||||
|
|
||||||
|
// Precompute the position style.
|
||||||
|
const positionStyle = useMemo(() => {
|
||||||
|
if (index === undefined) return {};
|
||||||
|
const isTopPosition = AppearanceSettings.position.includes("top");
|
||||||
|
const actualHeight = 115; // Update this with the actual height including margin
|
||||||
|
const effectiveIndex = index % PluginSettings.store.maxNotifications;
|
||||||
|
const offset = 10 + (effectiveIndex * actualHeight); // 10 is the base offset
|
||||||
|
|
||||||
|
return isTopPosition ? { top: `${offset}px` } : { bottom: `${offset}px` };
|
||||||
|
}, [index, AppearanceSettings.position]);
|
||||||
|
|
||||||
|
// Handle notification timeout.
|
||||||
|
useEffect(() => {
|
||||||
|
if (isHover || permanent) return void setElapsed(0);
|
||||||
|
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
if (elapsed >= AppearanceSettings.timeout)
|
||||||
|
onClose!();
|
||||||
|
else
|
||||||
|
setElapsed(elapsed);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [isHover]);
|
||||||
|
|
||||||
|
const timeoutProgress = elapsed / AppearanceSettings.timeout;
|
||||||
|
|
||||||
|
// Render the notification.
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
style={positionStyle}
|
||||||
|
className={classes("toastnotifications-notification-root", AppearanceSettings.position)}
|
||||||
|
onClick={() => {
|
||||||
|
onClick?.();
|
||||||
|
if (dismissOnClick !== false)
|
||||||
|
onClose!();
|
||||||
|
}}
|
||||||
|
onContextMenu={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose!();
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setIsHover(true)}
|
||||||
|
onMouseLeave={() => setIsHover(false)}
|
||||||
|
>
|
||||||
|
<div className="toastnotifications-notification">
|
||||||
|
{icon && <img className="toastnotifications-notification-icon" src={icon} alt="User Avatar" />}
|
||||||
|
<div className="toastnotifications-notification-content">
|
||||||
|
<div className="toastnotifications-notification-header">
|
||||||
|
<h2 className="toastnotifications-notification-title">{title}</h2>
|
||||||
|
<button
|
||||||
|
className="toastnotifications-notification-close-btn"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose!();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" role="img" aria-labelledby="toastnotifications-notification-dismiss-title">
|
||||||
|
<title id="toastnotifications-notification-dismiss-title">Dismiss Notification</title>
|
||||||
|
<path fill="currentColor" d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{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>
|
||||||
|
{AppearanceSettings.timeout !== 0 && !permanent && (
|
||||||
|
<div
|
||||||
|
className="toastnotifications-notification-progressbar"
|
||||||
|
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: "var(--brand-experiment)" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
onError: ({ props }) => props.onClose!()
|
||||||
|
});
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React, ReactDOM } from "@webpack/common";
|
||||||
|
import type { JSX, ReactNode } from "react";
|
||||||
|
import type { Root } from "react-dom/client";
|
||||||
|
|
||||||
|
import { settings as PluginSettings } from "../index";
|
||||||
|
import NotificationComponent from "./NotificationComponent";
|
||||||
|
|
||||||
|
let NotificationQueue: JSX.Element[] = [];
|
||||||
|
let notificationID = 0;
|
||||||
|
let RootContainer: Root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getNotificationContainer()
|
||||||
|
* Gets the root container for the notifications, creating it if it doesn't exist.
|
||||||
|
* @returns {Root} The root DOM container.
|
||||||
|
*/
|
||||||
|
function getNotificationContainer() {
|
||||||
|
if (!RootContainer) {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.id = "toastnotifications-container";
|
||||||
|
document.body.append(container);
|
||||||
|
RootContainer = ReactDOM.createRoot(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RootContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationData {
|
||||||
|
title: string; // Title to display in the notification.
|
||||||
|
body: string; // Notification body text.
|
||||||
|
richBody?: ReactNode; // Same as body, though a rich ReactNode to be rendered within the notification.
|
||||||
|
icon?: string; // Avatar image of the message author or source.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showNotification(notification: NotificationData) {
|
||||||
|
const root = getNotificationContainer();
|
||||||
|
const thisNotificationID = notificationID++;
|
||||||
|
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
const ToastNotification = (
|
||||||
|
<NotificationComponent
|
||||||
|
key={thisNotificationID.toString()}
|
||||||
|
index={NotificationQueue.length}
|
||||||
|
{...notification}
|
||||||
|
onClose={() => {
|
||||||
|
// Remove this notification from the queue.
|
||||||
|
NotificationQueue = NotificationQueue.filter(n => n.key !== thisNotificationID.toString());
|
||||||
|
notification.onClose?.(); // Trigger the onClose callback if it exists.
|
||||||
|
console.log(`[DEBUG] [ToastNotifications] Removed #${thisNotificationID} from queue.`);
|
||||||
|
|
||||||
|
// Re-render remaining notifications with new reversed indices.
|
||||||
|
root.render(
|
||||||
|
<>
|
||||||
|
{NotificationQueue.map((notification, index) => {
|
||||||
|
const reversedIndex = (NotificationQueue.length - 1) - index;
|
||||||
|
return React.cloneElement(notification, { index: reversedIndex });
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add this notification to the queue.
|
||||||
|
NotificationQueue.push(ToastNotification);
|
||||||
|
console.log(`[DEBUG] [ToastNotifications] Added #${thisNotificationID} to queue.`);
|
||||||
|
|
||||||
|
// Limit the number of notifications to the configured maximum.
|
||||||
|
if (NotificationQueue.length > PluginSettings.store.maxNotifications) NotificationQueue.shift();
|
||||||
|
|
||||||
|
// Render the notifications.
|
||||||
|
root.render(
|
||||||
|
<>
|
||||||
|
{NotificationQueue.map((notification, index) => {
|
||||||
|
const reversedIndex = (NotificationQueue.length - 1) - index;
|
||||||
|
return React.cloneElement(notification, { index: reversedIndex });
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
146
src/equicordplugins/toastNotifications/components/styles.css
Normal file
146
src/equicordplugins/toastNotifications/components/styles.css
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
:root {
|
||||||
|
/* Body */
|
||||||
|
--toastnotifications-background-color: var(--background-secondary-alt);
|
||||||
|
--toastnotifications-text-color: var(--text-normal);
|
||||||
|
--toastnotifications-border-radius: 6px;
|
||||||
|
--toastnotifications-width: 25vw;
|
||||||
|
--toastnotifications-padding: 1.25rem;
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
--toastnotifications-title-color: var(--header-primary);
|
||||||
|
--toastnotifications-title-font-size: 1rem;
|
||||||
|
--toastnotifications-title-font-weight: 600;
|
||||||
|
--toastnotifications-title-line-height: 1.25rem;
|
||||||
|
|
||||||
|
/* Close Button */
|
||||||
|
--toastnotifications-close-button-color: var(--interactive-normal);
|
||||||
|
--toastnotifications-close-button-hover-color: var(--interactive-hover);
|
||||||
|
--toastnotifications-close-button-opacity: 0.5;
|
||||||
|
--toastnotifications-close-button-hover-opacity: 1;
|
||||||
|
|
||||||
|
/* Message Author Image */
|
||||||
|
--toastnotifications-image-height: 4rem;
|
||||||
|
--toastnotifications-image-width: var(--toastnotifications-image-height);
|
||||||
|
--toastnotifications-image-border-radius: 6px;
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
--toastnotifications-progressbar-height: 0.25rem;
|
||||||
|
|
||||||
|
/* Position Offset - Global inherited offset by all positions */
|
||||||
|
--toastnotifications-position-offset: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-root {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: var(--toastnotifications-text-color);
|
||||||
|
background-color: var(--toastnotifications-background-color);
|
||||||
|
border-radius: var(--toastnotifications-border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2147483647;
|
||||||
|
right: 1rem;
|
||||||
|
width: var(--toastnotifications-width);
|
||||||
|
min-height: 10vh;
|
||||||
|
bottom: calc(1rem + var(--notification-index) * 12vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: var(--toastnotifications-padding);
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-title {
|
||||||
|
color: var(--toastnotifications-title-color);
|
||||||
|
font-size: var(--toastnotifications-title-font-size);
|
||||||
|
font-weight: var(--toastnotifications-title-font-weight);
|
||||||
|
line-height: var(--toastnotifications-title-line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-close-btn {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--toastnotifications-close-button-color);
|
||||||
|
opacity: var(--toastnotifications-close-button-opacity);
|
||||||
|
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-close-btn:hover {
|
||||||
|
color: var(--toastnotifications-close-button-hover-color);
|
||||||
|
opacity: var(--toastnotifications-close-button-hover-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-icon {
|
||||||
|
height: var(--toastnotifications-image-height);
|
||||||
|
width: var(--toastnotifications-image-width);
|
||||||
|
border-radius: var(--toastnotifications-image-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-notification-progressbar {
|
||||||
|
height: var(--toastnotifications-progressbar-height);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 75%;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notification Positioning CSS */
|
||||||
|
.toastnotifications-position-bottom-left {
|
||||||
|
bottom: var(--toastnotifications-position-offset);
|
||||||
|
left: var(--toastnotifications-position-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-position-top-left {
|
||||||
|
top: var(--toastnotifications-position-offset);
|
||||||
|
left: var(--toastnotifications-position-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-position-top-right {
|
||||||
|
top: var(--toastnotifications-position-offset);
|
||||||
|
right: var(--toastnotifications-position-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastnotifications-position-bottom-right {
|
||||||
|
bottom: var(--toastnotifications-position-offset);
|
||||||
|
right: var(--toastnotifications-position-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rich Body classes */
|
||||||
|
.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;
|
||||||
|
}
|
583
src/equicordplugins/toastNotifications/index.tsx
Normal file
583
src/equicordplugins/toastNotifications/index.tsx
Normal file
|
@ -0,0 +1,583 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { makeRange } from "@components/PluginSettings/components";
|
||||||
|
import { EquicordDevs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
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 { NotificationData, showNotification } from "./components/Notifications";
|
||||||
|
import { MessageTypes, RelationshipType, StreamingTreatment } from "./types";
|
||||||
|
|
||||||
|
let ignoredUsers: string[] = [];
|
||||||
|
let notifyFor: string[] = [];
|
||||||
|
|
||||||
|
// Functional variables.
|
||||||
|
const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled");
|
||||||
|
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||||
|
const UserUtils = findByPropsLazy("getGlobalName");
|
||||||
|
|
||||||
|
// Adjustable variables.
|
||||||
|
const USER_MENTION_REGEX = /<@!?(\d{17,20})>|<#(\d{17,20})>|<@&(\d{17,20})>/g; // This regex captures user, channel, and role mentions.
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
position: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "The position of the toast notification",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Bottom Left",
|
||||||
|
value: "bottom-left",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Top Left",
|
||||||
|
value: "top-left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Top Right",
|
||||||
|
value: "top-right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Bottom Right",
|
||||||
|
value: "bottom-right"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Time in seconds notifications will be shown for",
|
||||||
|
default: 5,
|
||||||
|
markers: makeRange(1, 15, 1)
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Opacity of the notification",
|
||||||
|
default: 100,
|
||||||
|
markers: makeRange(10, 100, 10)
|
||||||
|
},
|
||||||
|
maxNotifications: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Maximum number of notifications displayed at once",
|
||||||
|
default: 3,
|
||||||
|
markers: makeRange(1, 5, 1)
|
||||||
|
},
|
||||||
|
determineServerNotifications: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Automatically determine what 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 the notification as normal",
|
||||||
|
value: StreamingTreatment.NORMAL,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "No Content - Hide the notification body",
|
||||||
|
value: StreamingTreatment.NO_CONTENT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Ignore - Don't show the notification 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 ignore all their notifications from (separate with commas)",
|
||||||
|
onChange: () => { ignoredUsers = stringToList(settings.store.ignoreUsers); },
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
exampleButton: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
description: "Show an example toast notification.",
|
||||||
|
component: () =>
|
||||||
|
<Button onClick={showExampleNotification}>
|
||||||
|
Show Example Notification
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMention = (id: string, type: string, guildId?: string): ReactNode => {
|
||||||
|
let name;
|
||||||
|
if (type === "user")
|
||||||
|
name = `@${UserStore.getUser(id)?.username || "unknown-user"}`;
|
||||||
|
else if (type === "channel")
|
||||||
|
name = `#${ChannelStore.getChannel(id)?.name || "unknown-channel"}`;
|
||||||
|
else if (type === "role" && guildId)
|
||||||
|
name = `@${GuildStore.getGuild(guildId).getRole(id)?.name || "unknown-role"}`;
|
||||||
|
|
||||||
|
// Return the mention as a styled span.
|
||||||
|
return (
|
||||||
|
<span key={`${type}-${id}`} className={"toastnotifications-mention-class"}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ToastNotifications",
|
||||||
|
description: "Show a toast notification whenever you receive a direct message.",
|
||||||
|
authors: [EquicordDevs.Skully, EquicordDevs.Ethan, EquicordDevs.Buzzy],
|
||||||
|
settings,
|
||||||
|
flux: {
|
||||||
|
async MESSAGE_CREATE({ message }: { message: Message; }) {
|
||||||
|
|
||||||
|
const channel: Channel = ChannelStore.getChannel(message.channel_id);
|
||||||
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
(
|
||||||
|
(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: ReactNode[] = [];
|
||||||
|
|
||||||
|
// If this channel is a group DM, include the channel name.
|
||||||
|
if (channel.isGroupDM()) {
|
||||||
|
let channelName = channel.name?.trim() ?? false;
|
||||||
|
if (!channelName) { // If the channel doesn't have a set name, use the first 3 recipients.
|
||||||
|
channelName = channel.rawRecipients.slice(0, 3).map(e => e.username).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, truncate the channel name if it's too long.
|
||||||
|
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.
|
||||||
|
{
|
||||||
|
Notification.title = `${getName(message.author)} (#${channel.name})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle specific message types.
|
||||||
|
switch (message.type) {
|
||||||
|
case MessageTypes.CALL: {
|
||||||
|
Notification.body = "Started a call with you!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageTypes.CHANNEL_RECIPIENT_ADD: {
|
||||||
|
const actor = UserStore.getUser(message.author.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 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)}.`;
|
||||||
|
} else {
|
||||||
|
Notification.body = "Left the group.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageTypes.CHANNEL_NAME_CHANGE: {
|
||||||
|
Notification.body = `Changed the channel name to '${message.content}'.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageTypes.CHANNEL_ICON_CHANGE: {
|
||||||
|
Notification.body = "Changed the channel icon.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageTypes.CHANNEL_PINNED_MESSAGE: {
|
||||||
|
Notification.body = "Pinned a message.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message contains an embed.
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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("noti 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} 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> {
|
||||||
|
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);
|
||||||
|
}
|
39
src/equicordplugins/toastNotifications/types.ts
Normal file
39
src/equicordplugins/toastNotifications/types.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const enum MessageTypes {
|
||||||
|
CHANNEL_RECIPIENT_ADD = 1,
|
||||||
|
CHANNEL_RECIPIENT_REMOVE = 2,
|
||||||
|
CALL = 3,
|
||||||
|
CHANNEL_NAME_CHANGE = 4,
|
||||||
|
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
|
||||||
|
}
|
|
@ -1050,6 +1050,14 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "byeoon",
|
name: "byeoon",
|
||||||
id: 1167275288036655133n
|
id: 1167275288036655133n
|
||||||
},
|
},
|
||||||
|
Skully: {
|
||||||
|
name: "Skully",
|
||||||
|
id: 150298098516754432n
|
||||||
|
},
|
||||||
|
Buzzy: {
|
||||||
|
name: "Buzzy",
|
||||||
|
id: 1273353654644117585n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue