Merge pull request #3 from SKATTKOOKIE/feature/add-sound-for-role-notification
Feature/add sound for role notification
This commit is contained in:
commit
8f6d502b3e
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { toastScript } from "./toasts";
|
||||||
|
|
||||||
export function injectSounds(webContents: Electron.WebContents) {
|
export function injectSounds(webContents: Electron.WebContents) {
|
||||||
webContents.on("did-finish-load", () => {
|
webContents.on("did-finish-load", () => {
|
||||||
webContents.executeJavaScript(`
|
webContents.executeJavaScript(`
|
||||||
|
|
@ -5,9 +7,13 @@ export function injectSounds(webContents: Electron.WebContents) {
|
||||||
if (window.__soundsInjected) return;
|
if (window.__soundsInjected) return;
|
||||||
window.__soundsInjected = true;
|
window.__soundsInjected = true;
|
||||||
|
|
||||||
|
${toastScript}
|
||||||
|
|
||||||
let ctx = null;
|
let ctx = null;
|
||||||
let currentUserId = null;
|
let currentUserId = null;
|
||||||
let currentChannelId = null;
|
let currentChannelId = null;
|
||||||
|
const roleNameCache = {};
|
||||||
|
const userNameCache = {};
|
||||||
|
|
||||||
function getAudioContext() {
|
function getAudioContext() {
|
||||||
if (!ctx) ctx = new AudioContext();
|
if (!ctx) ctx = new AudioContext();
|
||||||
|
|
@ -41,13 +47,16 @@ export function injectSounds(webContents: Electron.WebContents) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function playUserJoinSound() {
|
function playUserJoinSound() {
|
||||||
playTone(880, 0.15);
|
playTone(880, 0.15, 0.05);
|
||||||
setTimeout(() => playTone(1100, 0.2), 150);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function playUserLeaveSound() {
|
function playUserLeaveSound() {
|
||||||
playTone(440, 0.15);
|
playTone(440, 0.15, 0.05);
|
||||||
setTimeout(() => playTone(220, 0.3), 150);
|
}
|
||||||
|
|
||||||
|
function playMentionSound() {
|
||||||
|
playTone(1200, 0.1, 0.08);
|
||||||
|
setTimeout(() => playTone(1200, 0.15, 0.08), 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const OriginalWebSocket = window.WebSocket;
|
const OriginalWebSocket = window.WebSocket;
|
||||||
|
|
@ -60,9 +69,27 @@ export function injectSounds(webContents: Electron.WebContents) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
if (data.type === "Ready" && data.users) {
|
if (data.type === "Ready") {
|
||||||
const self = data.users.find(u => u.relationship === "User");
|
if (data.users) {
|
||||||
if (self) currentUserId = self._id;
|
const self = data.users.find(u => u.relationship === "User");
|
||||||
|
if (self) currentUserId = self._id;
|
||||||
|
for (const user of data.users) {
|
||||||
|
if (user._id && user.username) userNameCache[user._id] = user.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.servers) {
|
||||||
|
for (const server of data.servers) {
|
||||||
|
if (server.roles) {
|
||||||
|
for (const role of Object.values(server.roles)) {
|
||||||
|
roleNameCache[role._id] = role.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === "UserUpdate" && data.id && data.data?.username) {
|
||||||
|
userNameCache[data.id] = data.data.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === "VoiceChannelJoin" && data.state?.id === currentUserId) {
|
if (data.type === "VoiceChannelJoin" && data.state?.id === currentUserId) {
|
||||||
|
|
@ -83,6 +110,17 @@ export function injectSounds(webContents: Electron.WebContents) {
|
||||||
playUserLeaveSound();
|
playUserLeaveSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.type === "Message" && data.role_mentions?.length > 0 && data.member?.roles?.length > 0) {
|
||||||
|
const matchedRoleId = data.role_mentions.find(roleId => data.member.roles.includes(roleId));
|
||||||
|
if (matchedRoleId) {
|
||||||
|
playMentionSound();
|
||||||
|
const senderName = userNameCache[data.author] || data.author || "Someone";
|
||||||
|
const roleName = roleNameCache[matchedRoleId] || matchedRoleId;
|
||||||
|
const content = data.content?.replace(/<%[^>]+>/g, '@' + roleName).trim() || "New message";
|
||||||
|
__showMentionToast(senderName, roleName, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
export const toastStyles = `
|
||||||
|
#__stoat-toast-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
z-index: 99999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.stoat-toast {
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #3a3a55;
|
||||||
|
border-left: 4px solid #7c6af7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
min-width: 280px;
|
||||||
|
max-width: 340px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
||||||
|
pointer-events: all;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.stoat-toast.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
.stoat-toast.hiding {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
.stoat-toast-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.stoat-toast-icon { font-size: 14px; }
|
||||||
|
.stoat-toast-sender {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #c9c9e0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.stoat-toast-role {
|
||||||
|
font-size: 11px;
|
||||||
|
background: #7c6af720;
|
||||||
|
color: #a89cf7;
|
||||||
|
border: 1px solid #7c6af740;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.stoat-toast-content {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8888aa;
|
||||||
|
font-family: sans-serif;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
.stoat-toast-progress {
|
||||||
|
height: 2px;
|
||||||
|
background: #7c6af7;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-top: 10px;
|
||||||
|
animation: stoat-progress 5s linear forwards;
|
||||||
|
}
|
||||||
|
@keyframes stoat-progress {
|
||||||
|
from { width: 100%; }
|
||||||
|
to { width: 0%; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const toastScript = `
|
||||||
|
function __initToasts() {
|
||||||
|
if (!document.getElementById('__stoat-toast-styles')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = '__stoat-toast-styles';
|
||||||
|
style.textContent = ${JSON.stringify(toastStyles)};
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function __getToastContainer() {
|
||||||
|
let container = document.getElementById('__stoat-toast-container');
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
container.id = '__stoat-toast-container';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __dismissToast(toast) {
|
||||||
|
toast.classList.remove('visible');
|
||||||
|
toast.classList.add('hiding');
|
||||||
|
setTimeout(() => toast.remove(), 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __showMentionToast(senderName, roleName, content) {
|
||||||
|
__initToasts();
|
||||||
|
const container = __getToastContainer();
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'stoat-toast';
|
||||||
|
toast.innerHTML =
|
||||||
|
'<div class="stoat-toast-header">' +
|
||||||
|
'<span class="stoat-toast-icon">🔔</span>' +
|
||||||
|
'<span class="stoat-toast-sender">' + senderName + '</span>' +
|
||||||
|
'<span class="stoat-toast-role">@' + roleName + '</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="stoat-toast-content">' + content + '</div>' +
|
||||||
|
'<div class="stoat-toast-progress"></div>';
|
||||||
|
toast.addEventListener('click', () => __dismissToast(toast));
|
||||||
|
container.appendChild(toast);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => toast.classList.add('visible'));
|
||||||
|
});
|
||||||
|
setTimeout(() => __dismissToast(toast), 5000);
|
||||||
|
}
|
||||||
|
`;
|
||||||
Loading…
Reference in New Issue