iceshrimp-legacy/packages/sw/src/scripts/create-notification.ts
okayurisotto 599417de6e
Refactor sw (#10579)
* refactor(sw): remove dead code

* refactor(sw): remove dead code

* refactor(sw): remove dead code

* refactor(sw): remove dead code

* refactor(sw): remove dead code

* refactor(sw): remove dead code

* refactor(sw): 冗長な部分を変更

* refactor(sw): 使われていない煩雑な機能を削除

* refactor(sw): remove dead code

* refactor(sw): URL文字列の作成に`URL`を使うように

* refactor(sw): 型アサーションの削除とそれに伴い露呈したエラーへの対処

* refactor(sw): `append` -> `set` in `URLSearchParams`

* refactor(sw): `any`の削除とそれに伴い露呈したエラーへの対処

* refactor(sw): 型アサーションの削除とそれに伴い露呈したエラーへの対処

対処と言っても`throw`するだけ。いままでもこの状況ではエラーが投げられていたはずなので、この対処により新たな問題が起きることはないはず。

* refactor(sw): i18n loading

* refactor(sw): 型推論がうまくできる書き方に変更

`codes`が`(string | undefined)[]`から`string[]`になった

* refactor(sw): クエリ文字列の作成に`URLSearchParams`を使うように

* refactor(sw): `findClient`

* refactor(sw): `openClient`における`any`や`as`の書き換え

* refactor(sw): `openPost`における`any`の書き換え

* refactor(sw): `let` -> `const`

* refactor(sw): `any` -> `unknown`

* cleanup(sw): import

* cleanup(sw)

* cleanup(sw): `?.`

* cleanup(sw/.eslintrc.js)

* refactor(sw): `@typescript-eslint/explicit-function-return-type`

* refactor(sw): `@typescript-eslint/no-unused-vars`

* refactor(sw): どうしようもないところに`eslint-disable-next-line`を

* refactor(sw): `import/no-default-export`

* update operations.ts

* throw new Error

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: Kainoa kanter <kainoa@t1c.dev>
2023-07-13 16:52:04 -07:00

366 lines
8.5 KiB
TypeScript

/*
* Notification manager for SW
*/
import type { BadgeNames, PushNotificationDataMap } from "@/types";
import { char2fileName } from "@/scripts/twemoji-base";
import { cli } from "@/scripts/operations";
import { getAccountFromId } from "@/scripts/get-account-from-id";
import { swLang } from "@/scripts/lang";
import { getUserName } from "@/scripts/get-user-name";
const closeNotificationsByTags = async (tags: string[]): Promise<void> => {
for (const n of (
await Promise.all(
tags.map((tag) => globalThis.registration.getNotifications({ tag })),
)
).flat()) {
n.close();
}
};
const iconUrl = (name: BadgeNames): string =>
`/static-assets/tabler-badges/${name}.png`;
/* How to add a new badge:
* 1. Find the icon and download png from https://tabler-icons.io/
* 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png;
* 3. mv ~/icon-name.png ~/misskey/packages/backend/assets/tabler-badges/
* 4. Add 'icon-name' to BadgeNames
* 5. Add `badge: iconUrl('icon-name'),`
*/
export async function createNotification<
K extends keyof PushNotificationDataMap,
>(data: PushNotificationDataMap[K]): Promise<void> {
const n = await composeNotification(data);
if (n) {
return self.registration.showNotification(...n);
} else {
console.error("Could not compose notification", data);
return createEmptyNotification();
}
}
async function composeNotification(
data: PushNotificationDataMap[keyof PushNotificationDataMap],
): Promise<[string, NotificationOptions] | null> {
const i18n = await (swLang.i18n ?? swLang.fetchLocale());
const { t } = i18n;
switch (data.type) {
/*
case 'driveFileCreated': // TODO (Server Side)
return [t('_notification.fileUploaded'), {
body: body.name,
icon: body.url,
data
}];
*/
case "notification":
switch (data.body.type) {
case "follow": {
// users/showの型定義をswos.apiへ当てはめるのが困難なのでapiFetch.requestを直接使用
const account = await getAccountFromId(data.userId);
if (!account) return null;
const userDetail = await cli.request(
"users/show",
{ userId: data.body.userId },
account.token,
);
return [
t("_notification.youWereFollowed"),
{
body: getUserName(data.body.user),
icon: data.body.user.avatarUrl,
badge: iconUrl("user-plus"),
data,
actions: userDetail.isFollowing
? []
: [
{
action: "follow",
title: t("_notification._actions.followBack"),
},
],
},
];
}
case "mention":
return [
t("_notification.youGotMention", {
name: getUserName(data.body.user),
}),
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge: iconUrl("at"),
data,
actions: [
{
action: "reply",
title: t("_notification._actions.reply"),
},
],
},
];
case "reply":
return [
t("_notification.youGotReply", {
name: getUserName(data.body.user),
}),
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge: iconUrl("reply"),
data,
actions: [
{
action: "reply",
title: t("_notification._actions.reply"),
},
],
},
];
case "renote":
return [
t("_notification.youRenoted", {
name: getUserName(data.body.user),
}),
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge: iconUrl("retweet"),
data,
actions: [
{
action: "showUser",
title: getUserName(data.body.user),
},
],
},
];
case "quote":
return [
t("_notification.youGotQuote", {
name: getUserName(data.body.user),
}),
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge: iconUrl("quote-right"),
data,
actions: [
{
action: "reply",
title: t("_notification._actions.reply"),
},
...(data.body.note.visibility === "public" ||
data.body.note.visibility === "home"
? [
{
action: "renote",
title: t("_notification._actions.renote"),
},
]
: []),
],
},
];
case "reaction": {
let reaction = data.body.reaction;
let badge: string | undefined;
if (reaction.startsWith(":")) {
// カスタム絵文字の場合
const name = reaction.substring(1, reaction.length - 1);
const badgeUrl = new URL(`/emoji/${name}.webp`, origin);
badgeUrl.searchParams.set("badge", "1");
badge = badgeUrl.href;
reaction = name.split("@")[0];
} else {
// Unicode絵文字の場合
badge = `/twemoji-badge/${char2fileName(reaction)}.png`;
}
if (
await fetch(badge)
.then((res) => res.status !== 200)
.catch(() => true)
) {
badge = iconUrl("plus");
}
return [
`${reaction} ${getUserName(data.body.user)}`,
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge,
data,
actions: [
{
action: "showUser",
title: getUserName(data.body.user),
},
],
},
];
}
case "pollVote":
return [
t("_notification.youGotPoll", {
name: getUserName(data.body.user),
}),
{
body: data.body.note.text || "",
icon: data.body.user.avatarUrl,
badge: iconUrl("poll-h"),
data,
},
];
case "pollEnded":
return [
t("_notification.pollEnded"),
{
body: data.body.note.text || "",
badge: iconUrl("clipboard-check-solid"),
data,
},
];
case "receiveFollowRequest":
return [
t("_notification.youReceivedFollowRequest"),
{
body: getUserName(data.body.user),
icon: data.body.user.avatarUrl,
badge: iconUrl("clock"),
data,
actions: [
{
action: "accept",
title: t("accept"),
},
{
action: "reject",
title: t("reject"),
},
],
},
];
case "followRequestAccepted":
return [
t("_notification.yourFollowRequestAccepted"),
{
body: getUserName(data.body.user),
icon: data.body.user.avatarUrl,
badge: iconUrl("check"),
data,
},
];
case "groupInvited":
return [
t("_notification.youWereInvitedToGroup", {
userName: getUserName(data.body.user),
}),
{
body: data.body.invitation.group.name,
badge: iconUrl("id-card-alt"),
data,
actions: [
{
action: "accept",
title: t("accept"),
},
{
action: "reject",
title: t("reject"),
},
],
},
];
case "app":
return [
data.body.header || data.body.body,
{
body: data.body.header && data.body.body,
icon: data.body.icon,
data,
},
];
default:
return null;
}
case "unreadMessagingMessage":
if (data.body.groupId === null) {
return [
t("_notification.youGotMessagingMessageFromUser", {
name: getUserName(data.body.user),
}),
{
icon: data.body.user.avatarUrl,
badge: iconUrl("comments"),
tag: `messaging:user:${data.body.userId}`,
data,
renotify: true,
},
];
}
return [
t("_notification.youGotMessagingMessageFromGroup", {
name: data.body.group.name,
}),
{
icon: data.body.user.avatarUrl,
badge: iconUrl("comments"),
tag: `messaging:group:${data.body.groupId}`,
data,
renotify: true,
},
];
default:
return null;
}
}
export async function createEmptyNotification(): Promise<void> {
return new Promise<void>(async (res) => {
const i18n = await (swLang.i18n ?? swLang.fetchLocale());
const { t } = i18n;
await self.registration.showNotification(
t("_notification.emptyPushNotificationMessage"),
{
silent: true,
badge: iconUrl("null"),
tag: "read_notification",
},
);
res();
setTimeout(async () => {
for (const n of [
...(await self.registration.getNotifications({
tag: "user_visible_auto_notification",
})),
...(await self.registration.getNotifications({
tag: "read_notification",
})),
]) {
n.close();
}
}, 1000);
});
}