mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-21 04:17:01 -04:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
750dd1cdd6
10 changed files with 184 additions and 125 deletions
382
src/plugins/xsOverlay/index.tsx
Normal file
382
src/plugins/xsOverlay/index.tsx
Normal file
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 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 { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||
import { findByCodeLazy, findLazy } from "@webpack";
|
||||
import { Button, ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
|
||||
|
||||
const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10);
|
||||
|
||||
interface Message {
|
||||
guild_id: string,
|
||||
attachments: MessageAttachment[],
|
||||
author: User,
|
||||
channel_id: string,
|
||||
components: any[],
|
||||
content: string,
|
||||
edited_timestamp: string,
|
||||
embeds: Embed[],
|
||||
sticker_items?: Sticker[],
|
||||
flags: number,
|
||||
id: string,
|
||||
member: GuildMember,
|
||||
mention_everyone: boolean,
|
||||
mention_roles: string[],
|
||||
mentions: Mention[],
|
||||
nonce: string,
|
||||
pinned: false,
|
||||
referenced_message: any,
|
||||
timestamp: string,
|
||||
tts: boolean,
|
||||
type: number;
|
||||
}
|
||||
|
||||
interface Mention {
|
||||
avatar: string,
|
||||
avatar_decoration_data: any,
|
||||
discriminator: string,
|
||||
global_name: string,
|
||||
id: string,
|
||||
public_flags: number,
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface Sticker {
|
||||
t: "Sticker";
|
||||
description: string;
|
||||
format_type: number;
|
||||
guild_id: string;
|
||||
id: string;
|
||||
name: string;
|
||||
tags: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
interface Call {
|
||||
channel_id: string,
|
||||
guild_id: string,
|
||||
message_id: string,
|
||||
region: string,
|
||||
ringing: string[];
|
||||
}
|
||||
|
||||
interface ApiObject {
|
||||
sender: string,
|
||||
target: string,
|
||||
command: string,
|
||||
jsonData: string,
|
||||
rawData: string | null,
|
||||
}
|
||||
|
||||
interface NotificationObject {
|
||||
type: number;
|
||||
timeout: number;
|
||||
height: number;
|
||||
opacity: number;
|
||||
volume: number;
|
||||
audioPath: string;
|
||||
title: string;
|
||||
content: string;
|
||||
useBase64Icon: boolean;
|
||||
icon: ArrayBuffer | string;
|
||||
sourceApp: string;
|
||||
}
|
||||
|
||||
const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1");
|
||||
const logger = new Logger("XSOverlay");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
webSocketPort: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Websocket port",
|
||||
default: 42070,
|
||||
async onChange() {
|
||||
await start();
|
||||
}
|
||||
},
|
||||
botNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Allow bot notifications",
|
||||
default: false
|
||||
},
|
||||
serverNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Allow server notifications",
|
||||
default: true
|
||||
},
|
||||
dmNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Allow Direct Message notifications",
|
||||
default: true
|
||||
},
|
||||
groupDmNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Allow Group DM notifications",
|
||||
default: true
|
||||
},
|
||||
callNotifications: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Allow call notifications",
|
||||
default: true
|
||||
},
|
||||
pingColor: {
|
||||
type: OptionType.STRING,
|
||||
description: "User mention color",
|
||||
default: "#7289da"
|
||||
},
|
||||
channelPingColor: {
|
||||
type: OptionType.STRING,
|
||||
description: "Channel mention color",
|
||||
default: "#8a2be2"
|
||||
},
|
||||
soundPath: {
|
||||
type: OptionType.STRING,
|
||||
description: "Notification sound (default/warning/error)",
|
||||
default: "default"
|
||||
},
|
||||
timeout: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Notification duration (secs)",
|
||||
default: 3,
|
||||
},
|
||||
lengthBasedTimeout: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Extend duration with message length",
|
||||
default: true
|
||||
},
|
||||
opacity: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "Notif opacity",
|
||||
default: 1,
|
||||
markers: makeRange(0, 1, 0.1)
|
||||
},
|
||||
volume: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "Volume",
|
||||
default: 0.2,
|
||||
markers: makeRange(0, 1, 0.1)
|
||||
},
|
||||
});
|
||||
|
||||
let socket: WebSocket;
|
||||
|
||||
async function start() {
|
||||
if (socket) socket.close();
|
||||
socket = new WebSocket(`ws://127.0.0.1:${settings.store.webSocketPort ?? 42070}/?client=Vencord`);
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.onopen = resolve;
|
||||
socket.onerror = reject;
|
||||
setTimeout(reject, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "XSOverlay",
|
||||
description: "Forwards discord notifications to XSOverlay, for easy viewing in VR",
|
||||
authors: [Devs.Nyako],
|
||||
tags: ["vr", "notify"],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
settings,
|
||||
|
||||
flux: {
|
||||
CALL_UPDATE({ call }: { call: Call; }) {
|
||||
if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) {
|
||||
const channel = ChannelStore.getChannel(call.channel_id);
|
||||
sendOtherNotif("Incoming call", `${channel.name} is calling you...`);
|
||||
}
|
||||
},
|
||||
MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) {
|
||||
if (optimistic) return;
|
||||
const channel = ChannelStore.getChannel(message.channel_id);
|
||||
if (!shouldNotify(message, message.channel_id)) return;
|
||||
|
||||
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
|
||||
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
|
||||
let finalMsg = message.content;
|
||||
let titleString = "";
|
||||
|
||||
if (channel.guild_id) {
|
||||
const guild = GuildStore.getGuild(channel.guild_id);
|
||||
titleString = `${message.author.username} (${guild.name}, #${channel.name})`;
|
||||
}
|
||||
|
||||
|
||||
switch (channel.type) {
|
||||
case ChannelTypes.DM:
|
||||
titleString = message.author.username.trim();
|
||||
break;
|
||||
case ChannelTypes.GROUP_DM:
|
||||
const channelName = channel.name.trim() ?? channel.rawRecipients.map(e => e.username).join(", ");
|
||||
titleString = `${message.author.username} (${channelName})`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.referenced_message) {
|
||||
titleString += " (reply)";
|
||||
}
|
||||
|
||||
if (message.embeds.length > 0) {
|
||||
finalMsg += " [embed] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent message embed(s)";
|
||||
}
|
||||
}
|
||||
|
||||
if (message.sticker_items) {
|
||||
finalMsg += " [sticker] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent a sticker";
|
||||
}
|
||||
}
|
||||
|
||||
const images = message.attachments.filter(e =>
|
||||
typeof e?.content_type === "string"
|
||||
&& e?.content_type.startsWith("image")
|
||||
);
|
||||
|
||||
|
||||
images.forEach(img => {
|
||||
finalMsg += ` [image: ${img.filename}] `;
|
||||
});
|
||||
|
||||
message.attachments.filter(a => a && !a.content_type?.startsWith("image")).forEach(a => {
|
||||
finalMsg += ` [attachment: ${a.filename}] `;
|
||||
});
|
||||
|
||||
// make mentions readable
|
||||
if (message.mentions.length > 0) {
|
||||
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `<color=#${pingColor}><b>@${UserStore.getUser(id)?.username || "unknown-user"}</color></b>`);
|
||||
}
|
||||
|
||||
// color role mentions (unity styling btw lol)
|
||||
if (message.mention_roles.length > 0) {
|
||||
for (const roleId of message.mention_roles) {
|
||||
const role = GuildStore.getRole(channel.guild_id, roleId);
|
||||
if (!role) continue;
|
||||
const roleColor = role.colorString ?? `#${pingColor}`;
|
||||
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
// make emotes and channel mentions readable
|
||||
const emoteMatches = finalMsg.match(new RegExp("(<a?:\\w+:\\d+>)", "g"));
|
||||
const channelMatches = finalMsg.match(new RegExp("<(#\\d+)>", "g"));
|
||||
|
||||
if (emoteMatches) {
|
||||
for (const eMatch of emoteMatches) {
|
||||
finalMsg = finalMsg.replace(new RegExp(`${eMatch}`, "g"), `:${eMatch.split(":")[1]}:`);
|
||||
}
|
||||
}
|
||||
|
||||
// color channel mentions
|
||||
if (channelMatches) {
|
||||
for (const cMatch of channelMatches) {
|
||||
let channelId = cMatch.split("<#")[1];
|
||||
channelId = channelId.substring(0, channelId.length - 1);
|
||||
finalMsg = finalMsg.replace(new RegExp(`${cMatch}`, "g"), `<b><color=#${channelPingColor}>#${ChannelStore.getChannel(channelId).name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreForChannelType(channel)) return;
|
||||
sendMsgNotif(titleString, finalMsg, message);
|
||||
}
|
||||
},
|
||||
|
||||
start,
|
||||
|
||||
stop() {
|
||||
socket.close();
|
||||
},
|
||||
|
||||
settingsAboutComponent: () => (
|
||||
<>
|
||||
<Button onClick={() => sendOtherNotif("This is a test notification! explode", "Hello from Vendor!")}>
|
||||
Send test notification
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
});
|
||||
|
||||
function shouldIgnoreForChannelType(channel: Channel) {
|
||||
if (channel.type === ChannelTypes.DM && settings.store.dmNotifications) return false;
|
||||
if (channel.type === ChannelTypes.GROUP_DM && settings.store.groupDmNotifications) return false;
|
||||
else return !settings.store.serverNotifications;
|
||||
}
|
||||
|
||||
function sendMsgNotif(titleString: string, content: string, message: Message) {
|
||||
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
|
||||
const msgData: NotificationObject = {
|
||||
type: 1,
|
||||
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
|
||||
height: calculateHeight(content),
|
||||
opacity: settings.store.opacity,
|
||||
volume: settings.store.volume,
|
||||
audioPath: settings.store.soundPath,
|
||||
title: titleString,
|
||||
content: content,
|
||||
useBase64Icon: true,
|
||||
icon: new TextDecoder().decode(result),
|
||||
sourceApp: "Vencord"
|
||||
};
|
||||
|
||||
sendToOverlay(msgData);
|
||||
});
|
||||
}
|
||||
|
||||
function sendOtherNotif(content: string, titleString: string) {
|
||||
const msgData: NotificationObject = {
|
||||
type: 1,
|
||||
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
|
||||
height: calculateHeight(content),
|
||||
opacity: settings.store.opacity,
|
||||
volume: settings.store.volume,
|
||||
audioPath: settings.store.soundPath,
|
||||
title: titleString,
|
||||
content: content,
|
||||
useBase64Icon: false,
|
||||
icon: "default",
|
||||
sourceApp: "Equicord"
|
||||
};
|
||||
sendToOverlay(msgData);
|
||||
}
|
||||
|
||||
async function sendToOverlay(notif: NotificationObject) {
|
||||
const apiObject: ApiObject = {
|
||||
sender: "Vencord",
|
||||
target: "xsoverlay",
|
||||
command: "SendNotification",
|
||||
jsonData: JSON.stringify(notif),
|
||||
rawData: null
|
||||
};
|
||||
if (socket.readyState !== socket.OPEN) await start();
|
||||
socket.send(JSON.stringify(apiObject));
|
||||
}
|
||||
|
||||
function shouldNotify(message: Message, channel: string) {
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
if (message.author.id === currentUser.id) return false;
|
||||
if (message.author.bot && !settings.store.botNotifications) return false;
|
||||
return notificationsShouldNotify(message, channel);
|
||||
}
|
||||
|
||||
function calculateHeight(content: string) {
|
||||
if (content.length <= 100) return 100;
|
||||
if (content.length <= 200) return 150;
|
||||
if (content.length <= 300) return 200;
|
||||
return 250;
|
||||
}
|
||||
|
||||
function calculateTimeout(content: string) {
|
||||
if (content.length <= 100) return 3;
|
||||
if (content.length <= 200) return 4;
|
||||
if (content.length <= 300) return 5;
|
||||
return 6;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue