215 lines
7.4 KiB
TypeScript
215 lines
7.4 KiB
TypeScript
import { createEffect, onCleanup, onMount } from "solid-js";
|
|
|
|
import { useLingui } from "@lingui-solid/solid/macro";
|
|
import {
|
|
ChannelEditSystemMessage,
|
|
ChannelOwnershipChangeSystemMessage,
|
|
ChannelRenamedSystemMessage,
|
|
Message,
|
|
MessagePinnedSystemMessage,
|
|
TextSystemMessage,
|
|
UserModeratedSystemMessage,
|
|
UserSystemMessage,
|
|
} from "stoat.js";
|
|
|
|
import { useNavigate, useSmartParams } from "@revolt/routing";
|
|
import { useState } from "@revolt/state";
|
|
|
|
import { useClient } from ".";
|
|
|
|
/**
|
|
* Process and display desktop notifications
|
|
*/
|
|
export function NotificationsWorker() {
|
|
const state = useState();
|
|
const { t } = useLingui();
|
|
const client = useClient();
|
|
const navigate = useNavigate();
|
|
const params = useSmartParams();
|
|
|
|
/**
|
|
* Handle incoming messages
|
|
* @param message Message
|
|
*/
|
|
function onMessage(message: Message) {
|
|
const us = client().user!;
|
|
|
|
// Ignore if we are currently looking at the channel
|
|
if (params().channelId === message.channelId && document.hasFocus()) return;
|
|
|
|
// Ignore our own messages
|
|
if (message.author?.self) return;
|
|
|
|
// Ignore blocked users
|
|
if (message.author?.relationship === "Blocked") return;
|
|
|
|
// Ignore muted channels
|
|
if (state.notifications.isMuted(message.channel)) return;
|
|
|
|
// Check channel notification settings
|
|
switch (state.notifications.computeForChannel(message.channel!)) {
|
|
case "none":
|
|
return; // ignore if muted/none
|
|
case "mention":
|
|
if (!message.mentioned) return; // ignore if not mentioned
|
|
}
|
|
|
|
// Ignore if we're busy or focused
|
|
if (
|
|
us.status?.presence === "Busy" ||
|
|
(us.status?.presence === "Focus" && !message.mentioned)
|
|
)
|
|
return;
|
|
|
|
// Generate the title
|
|
let title;
|
|
switch (message.channel!.type) {
|
|
case "SavedMessages":
|
|
return;
|
|
case "DirectMessage":
|
|
title = `@${message.username}`;
|
|
break;
|
|
case "Group":
|
|
if (message.author?.id === "00000000000000000000000000") {
|
|
title = message.channel?.name;
|
|
} else {
|
|
title = `@${message.username} - ${message.channel?.name}`;
|
|
}
|
|
break;
|
|
case "TextChannel":
|
|
title = `@${message.username} (#${message.channel?.name}, ${message.channel?.server?.name})`;
|
|
break;
|
|
}
|
|
|
|
// Find image if applicable
|
|
const image = message.attachments?.find(
|
|
(x) => x.metadata.type === "Image",
|
|
)?.previewUrl;
|
|
|
|
// Find body/icon
|
|
let body, icon;
|
|
if (message.content) {
|
|
body = message.contentPlain;
|
|
icon = message.avatarURL;
|
|
} else if (message.systemMessage) {
|
|
switch (message.systemMessage.type) {
|
|
case "text":
|
|
body = (message.systemMessage as TextSystemMessage).content;
|
|
break;
|
|
case "user_added":
|
|
body = t`${(message.systemMessage as UserModeratedSystemMessage).user?.username} was added by ${(message.systemMessage as UserModeratedSystemMessage).by?.username}`;
|
|
icon = (message.systemMessage as UserModeratedSystemMessage).user
|
|
?.avatarURL;
|
|
break;
|
|
case "user_remove":
|
|
body = t`${(message.systemMessage as UserModeratedSystemMessage).user?.username} was removed by ${(message.systemMessage as UserModeratedSystemMessage).by?.username}`;
|
|
icon = (message.systemMessage as UserModeratedSystemMessage).user
|
|
?.avatarURL;
|
|
break;
|
|
case "user_joined":
|
|
body = t`${(message.systemMessage as UserSystemMessage).user?.username} joined`;
|
|
icon = (message.systemMessage as UserSystemMessage).user?.avatarURL;
|
|
break;
|
|
case "user_left":
|
|
body = t`${(message.systemMessage as UserSystemMessage).user?.username} left`;
|
|
icon = (message.systemMessage as UserSystemMessage).user?.avatarURL;
|
|
break;
|
|
case "user_kicked":
|
|
body = t`${(message.systemMessage as UserSystemMessage).user?.username} was kicked`;
|
|
icon = (message.systemMessage as UserSystemMessage).user?.avatarURL;
|
|
break;
|
|
case "user_banned":
|
|
body = t`${(message.systemMessage as UserSystemMessage).user?.username} was banned`;
|
|
icon = (message.systemMessage as UserSystemMessage).user?.avatarURL;
|
|
break;
|
|
case "channel_renamed":
|
|
body = t`${(message.systemMessage as ChannelRenamedSystemMessage).by?.username} renamed the channel`;
|
|
icon = (message.systemMessage as ChannelRenamedSystemMessage).by
|
|
?.avatarURL;
|
|
break;
|
|
case "channel_description_changed":
|
|
body = t`${(message.systemMessage as ChannelEditSystemMessage).by?.username} changed the channel description`;
|
|
icon = (message.systemMessage as ChannelEditSystemMessage).by
|
|
?.avatarURL;
|
|
break;
|
|
case "channel_icon_changed":
|
|
body = t`${(message.systemMessage as ChannelEditSystemMessage).by?.username} changed the channel icon`;
|
|
icon = (message.systemMessage as ChannelEditSystemMessage).by
|
|
?.avatarURL;
|
|
break;
|
|
case "channel_ownership_changed":
|
|
body = t`${(message.systemMessage as ChannelOwnershipChangeSystemMessage).from?.username} made ${(message.systemMessage as ChannelOwnershipChangeSystemMessage).to?.username} the new group owner`;
|
|
icon = (message.systemMessage as ChannelOwnershipChangeSystemMessage)
|
|
.from?.avatarURL;
|
|
break;
|
|
case "message_pinned":
|
|
body = t`${(message.systemMessage as MessagePinnedSystemMessage).by?.username} pinned a message`;
|
|
icon = (message.systemMessage as MessagePinnedSystemMessage).by
|
|
?.avatarURL;
|
|
break;
|
|
case "message_unpinned":
|
|
body = t`${(message.systemMessage as MessagePinnedSystemMessage).by?.username} unpinned a message`;
|
|
icon = (message.systemMessage as MessagePinnedSystemMessage).by
|
|
?.avatarURL;
|
|
break;
|
|
}
|
|
} else if (message.attachments?.length) {
|
|
body = t`Sent ${message.attachments!.length} attachments`;
|
|
}
|
|
|
|
// todo: play sound
|
|
|
|
// Don't continue if we don't have notification permissions
|
|
if (Notification.permission !== "granted") return;
|
|
|
|
console.info(`[notification] ${title} ${icon} ${body}`);
|
|
|
|
const notification = new Notification(title!, {
|
|
icon,
|
|
// @ts-expect-error this does exist on some platforms
|
|
image,
|
|
body,
|
|
timestamp: message.createdAt,
|
|
tag: message.channelId,
|
|
badge: "/assets/web/android-chrome-512x512.png",
|
|
silent: true,
|
|
});
|
|
|
|
notification.addEventListener("click", () => {
|
|
window.focus();
|
|
navigate(message.path);
|
|
});
|
|
}
|
|
|
|
createEffect(() => {
|
|
client().addListener("messageCreate", onMessage);
|
|
onCleanup(() => client().removeListener("messageCreate", onMessage));
|
|
});
|
|
|
|
/**
|
|
* Handle page click to request notifications
|
|
*/
|
|
function tryRequest() {
|
|
document.removeEventListener("click", tryRequest);
|
|
|
|
if (!localStorage.getItem("denied-notifications")) {
|
|
Notification.requestPermission().then(
|
|
(permission) =>
|
|
permission === "denied" &&
|
|
localStorage.setItem("denied-notifications", "1"),
|
|
);
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
// don't bother mounting if denied before
|
|
if (!localStorage.getItem("denied-notifications")) {
|
|
document.addEventListener("click", tryRequest);
|
|
}
|
|
});
|
|
|
|
onCleanup(() => document.removeEventListener("click", tryRequest));
|
|
|
|
return null;
|
|
}
|