stoat-for-desktop/components/client/NotificationsWorker.tsx

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;
}