mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 06:10:22 +00:00
* feat: ensure djs clients are singleton instances * feat: add votes table * feat: vote notification * chore: reduce vote cooldown to 15 min * feat: add SetNotification to server * chore: add debug logs * fix: do not add notification when token and voteid already exists * feat: add loading indicator * feat: refresh notification when voted * feat: add opt-out * feat: add debug log * fix: initialize firebase app * fix: remove app on messaging * feat: show notifications only with service worker * fix: state improperly used * fix: schedule notification if notification is newly added * chore: remove duplicated notification * chore: add spacing * chore: get token if notification is granted * chore: change vote cooldown to 12 hours * chore: remove logging
115 lines
2.9 KiB
TypeScript
115 lines
2.9 KiB
TypeScript
import { initializeApp } from 'firebase-admin/app'
|
|
import { KoreanbotsEndPoints, VOTE_COOLDOWN } from './Constants'
|
|
import { get, getNotifications, removeNotification as removeNotificationData } from './Query'
|
|
import { ObjectType } from '@types'
|
|
import { messaging } from 'firebase-admin'
|
|
|
|
export type Notification = {
|
|
vote_id: number
|
|
token: string
|
|
user_id: string
|
|
target_id: string
|
|
type: ObjectType
|
|
last_voted: Date
|
|
}
|
|
|
|
export default class NotificationManager {
|
|
private timeouts: Record<`${string}:${string}:${string}`, NodeJS.Timeout> = {}
|
|
private firebaseApp: ReturnType<typeof initializeApp>
|
|
|
|
public constructor() {
|
|
this.initVotes()
|
|
this.firebaseApp = initializeApp()
|
|
console.log('NotificationManager initialized')
|
|
}
|
|
|
|
public async setNotification(token: string, userId: string, targetId: string) {
|
|
const noti = await get.notifications.token(token, targetId)
|
|
|
|
if (!noti) return false
|
|
await this.scheduleNotification(noti)
|
|
}
|
|
|
|
/**
|
|
* This is a service. This removes the timeout and the notification data.
|
|
*/
|
|
public async removeNotification({
|
|
userId,
|
|
targetId,
|
|
token,
|
|
}: {
|
|
userId: string
|
|
targetId: string
|
|
token: string
|
|
}): ReturnType<typeof removeNotificationData> {
|
|
clearTimeout(this.timeouts[`${userId}:${targetId}:${token}`])
|
|
return await removeNotificationData({ targetId, token })
|
|
}
|
|
|
|
public async scheduleNotification(noti: Notification) {
|
|
const time = noti.last_voted.getTime() + VOTE_COOLDOWN + 1000 * 60 - Date.now()
|
|
|
|
if (time <= 0) {
|
|
return
|
|
}
|
|
|
|
const key = `${noti.user_id}:${noti.target_id}:${noti.token}`
|
|
|
|
this.timeouts[key] = setTimeout(() => {
|
|
this.pushNotification(noti)
|
|
clearTimeout(this.timeouts[key])
|
|
}, time)
|
|
}
|
|
|
|
public async refresh(userId: string, targetId: string) {
|
|
const notifications = await getNotifications(userId, targetId)
|
|
|
|
for (const noti of notifications) {
|
|
clearTimeout(this.timeouts[`${userId}:${targetId}:${noti.token}`])
|
|
this.scheduleNotification(noti)
|
|
}
|
|
}
|
|
|
|
public async initVotes() {
|
|
const res = await getNotifications()
|
|
for (const noti of res) {
|
|
this.scheduleNotification(noti)
|
|
}
|
|
}
|
|
|
|
private async pushNotification(noti: Notification) {
|
|
const isBot = noti.type === ObjectType.Bot
|
|
const target = isBot
|
|
? await get.bot.load(noti.target_id)
|
|
: await get.server.load(noti.target_id)
|
|
|
|
const image =
|
|
process.env.KOREANBOTS_URL +
|
|
('avatar' in target
|
|
? KoreanbotsEndPoints.CDN.avatar(target.id, { size: 256 })
|
|
: KoreanbotsEndPoints.CDN.icon(target.id, { size: 256 }))
|
|
|
|
await messaging()
|
|
.send({
|
|
token: noti.token,
|
|
data: {
|
|
type: 'vote-available',
|
|
name: target.name,
|
|
imageUrl: image ?? undefined,
|
|
url: `/${isBot ? 'bots' : 'servers'}/${noti.target_id}`,
|
|
},
|
|
})
|
|
.catch((e) => {
|
|
if ('code' in e) {
|
|
if (e.code === 'messaging/registration-token-not-registered') {
|
|
this.removeNotification({
|
|
userId: noti.user_id,
|
|
token: noti.token,
|
|
targetId: null,
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|