fk-macos-notif/index.ts

113 lines
4.2 KiB
TypeScript

import fs from 'fs/promises';
import path from 'path';
import notifier from 'node-notifier';
import ReconnectingWebSocket from 'reconnecting-websocket';
import { WebSocket } from 'ws';
const INSTANCE = process.env.FK_INSTANCE;
const TOKEN = process.env.FK_TOKEN;
const image_cache = path.resolve('image_cache');
await fs.mkdir(image_cache, { recursive: true });
const iconPath = path.join(image_cache, 'instance.jpg');
{
const resp = await fetch(`https://${INSTANCE}/api/meta`, { method: 'POST', headers: { authorization: `bearer ${TOKEN}` } });
const meta = await resp.json();
const iconResp = await fetch(meta.iconUrl);
const iconBuf = await iconResp.blob();
await fs.writeFile(iconPath, iconBuf.stream());
}
const socket = new ReconnectingWebSocket(`wss://${INSTANCE}/streaming?i=${TOKEN}`, undefined, { WebSocket });
socket.addEventListener('open', ({ type }) => console.log({ type }));
socket.addEventListener('close', ({ code, reason, wasClean, type }) => console.log({ code, reason, wasClean, type }));
socket.addEventListener('error', ({ error, message, type }) => console.log({ error, message, type }));
socket.addEventListener('open', (ev) => {
socket.send(JSON.stringify({ type: "connect", body: { channel: "main", id: "1" } }))
});
socket.addEventListener('message', ({ data }) => {
const payload = JSON.parse(data);
if (payload.type === 'channel' && payload.body.type === 'notification') {
const notification = payload.body.body;
const user = notification.user;
const note = notification.note;
const userName = user.host !== null ? `${user.username}@${user.host}` : user.username;
let title: string;
let body = 'note' in notification ? (note.cw != null ? note.cw : (note.text || '')) : undefined;
switch (notification.type) {
case 'pollEnded':
title = `${userName}'s poll has ended`;
break;
case 'follow':
title = `follow from ${userName}`;
break;
case 'followRequestAccepted':
title = `accept from ${userName}`;
break;
case 'mention':
title = `mention from ${userName}`;
break;
case 'pollVote':
title = `vote from ${userName}`;
break;
case 'quote':
title = `quote from ${userName}`;
break;
case 'reaction':
{
let reaction: string = notification.reaction;
reaction = reaction.split('@')[0];
if (reaction.startsWith(':'))
reaction = reaction.slice(1);
title = `${reaction} from ${userName}`;
}
break;
case 'receiveFollowRequest':
title = `follow req from ${userName}`;
break;
case 'renote':
title = `renote from ${userName}`;
break;
case 'reply':
title = `reply from ${userName}`;
break;
case 'groupInvited':
title = `group invite from ${userName}`;
break;
default:
console.log({ type: notification.type });
return;
}
const avatarUrl = new URL(user.avatarUrl);
const avatarServerPath = avatarUrl.pathname.split('/');
const avatarStem = avatarServerPath[avatarServerPath.length - 1];
const avatarFilename = `${avatarStem}.webp`;
const avatarPath = path.join(image_cache, avatarFilename);
(async () => {
try {
await fs.access(avatarPath);
} catch {
const resp = await fetch(avatarUrl);
if (!resp.ok) {
throw resp;
}
const buf = await resp.blob();
await fs.writeFile(avatarPath, buf.stream());
}
// console.log(user, userName, title, body);
notifier.notify({
title,
message: body,
icon: iconPath,
contentImage: avatarPath,
});
})()
}
});
await new Promise(() => { });