From 51539a25d23421a1bafe1cd89db422c931872a64 Mon Sep 17 00:00:00 2001 From: Michcio Date: Mon, 26 Sep 2022 00:47:25 +0200 Subject: [PATCH] initial work --- .gitignore | 6 +++ index.ts | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 21 ++++++++++ tsconfig.json | 12 ++++++ yarn.lock | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 259 insertions(+) create mode 100644 .gitignore create mode 100644 index.ts create mode 100644 package.json create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bb93ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +.env.secret +*.d.ts +*.js +*.tsbuildinfo +image_cache diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..8c064e3 --- /dev/null +++ b/index.ts @@ -0,0 +1,113 @@ +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(() => { }); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a6a538c --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "fk-macos-notif", + "version": "1.0.0", + "main": "index.ts", + "type": "module", + "license": "MIT", + "scripts": { + "start": "node index.js", + "prestart": "tsc -b" + }, + "dependencies": { + "node-notifier": "^10.0.1", + "reconnecting-websocket": "^4.4.0", + "typescript": "^4.8.3", + "ws": "^8.9.0" + }, + "devDependencies": { + "@types/node-notifier": "^8.0.2", + "@types/ws": "^8.5.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..81b93ac --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "strict": true, + "composite": true, + "noEmitOnError": false + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e750c43 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,107 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node-notifier@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@types/node-notifier/-/node-notifier-8.0.2.tgz#77c5de29c6e8adb915222b01864128cc3e78d553" + integrity sha512-5v0PhPv0AManpxT7W25Zipmj/Lxp1WqfkcpZHyqSloB+gGoAHRBuzhrCelFKrPvNF5ki3gAcO4kxaGO2/21u8g== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "18.7.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.21.tgz#63ee6688070e456325b6748dc492a7b948593871" + integrity sha512-rLFzK5bhM0YPyCoTC8bolBjMk7bwnZ8qeZUBslBfjZQou2ssJdWslx9CZ8DGM+Dx7QXQiiTVZ/6QO6kwtHkZCA== + +"@types/ws@^8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +node-notifier@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-10.0.1.tgz#0e82014a15a8456c4cfcdb25858750399ae5f1c7" + integrity sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.5" + shellwords "^0.1.1" + uuid "^8.3.2" + which "^2.0.2" + +reconnecting-websocket@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" + integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng== + +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +typescript@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88" + integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +ws@^8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" + integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==