forked from jordanlambrecht/tracker-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.ts
More file actions
138 lines (128 loc) · 4.21 KB
/
Copy pathtypes.ts
File metadata and controls
138 lines (128 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// src/lib/notifications/types.ts
export const SUPPORTED_NOTIFICATION_TYPES = ["discord"] as const
export const VALID_NOTIFICATION_TYPES = ["discord", "gotify", "telegram", "slack", "email"] as const
export type NotificationTargetType = (typeof VALID_NOTIFICATION_TYPES)[number]
export const VALID_EVENT_TYPES = [
"ratio_drop",
"hit_and_run",
"tracker_down",
"buffer_milestone",
"warned",
"ratio_danger",
"zero_seeding",
"rank_change",
"anniversary",
"bonus_cap",
"vip_expiring",
"unsatisfied_limit",
"active_hnrs",
"download_disabled",
] as const
export type NotificationEventType = (typeof VALID_EVENT_TYPES)[number]
export interface NotificationThresholds {
ratioDropDelta?: number
bufferMilestoneBytes?: number
bonusCapLimit?: number // default 99999, used for MAM specifically
vipExpiringDays?: number // default 7
unsatisfiedLimitPercent?: number // default 80
}
export interface DiscordConfig {
webhookUrl: string
}
export interface GotifyConfig {
serverUrl: string
appToken: string
}
export interface TelegramConfig {
botToken: string
chatId: string
}
export interface SlackConfig {
webhookUrl: string
}
export interface EmailConfig {
host: string
port: number
username: string
password: string
from: string
to: string
}
export type NotificationConfig =
| DiscordConfig
| GotifyConfig
| TelegramConfig
| SlackConfig
| EmailConfig
export interface SnapshotContext {
trackerId: number
trackerName: string
storeUsernames: boolean
currentRatio: number | null
previousRatio: number | null
currentHnrs: number | null
previousHnrs: number | null
currentBufferBytes: bigint | null
previousBufferBytes: bigint | null
trackerDown: boolean
trackerError: string | null
currentWarned: boolean | null
previousWarned: boolean | null
currentSeedingCount: number | null
currentGroup: string | null
previousGroup: string | null
trackerIsActive: boolean
trackerPausedAt: string | null
trackerJoinedAt: string | null
minimumRatio: number | undefined
// Platform-specific extended fields; populated for MAM and AvistaZ, undefined for others
platformContext?: {
currentSeedbonus: number | null
previousSeedbonus: number | null
vipUntil: string | null
unsatisfiedCount: number | null
unsatisfiedLimit: number | null
inactiveHnrCount: number | null
previousInactiveHnrCount: number | null
canDownload: boolean | null
previousCanDownload: boolean | null
}
}
/** Safely parse JSONB thresholds with runtime field validation */
export function parseThresholds(raw: unknown): NotificationThresholds {
if (!raw || typeof raw !== "object") return {}
const r = raw as Record<string, unknown>
return {
...(typeof r.ratioDropDelta === "number" ? { ratioDropDelta: r.ratioDropDelta } : {}),
...(typeof r.bufferMilestoneBytes === "number"
? { bufferMilestoneBytes: r.bufferMilestoneBytes }
: {}),
...(typeof r.bonusCapLimit === "number" ? { bonusCapLimit: r.bonusCapLimit } : {}),
...(typeof r.vipExpiringDays === "number" ? { vipExpiringDays: r.vipExpiringDays } : {}),
...(typeof r.unsatisfiedLimitPercent === "number"
? { unsatisfiedLimitPercent: r.unsatisfiedLimitPercent }
: {}),
}
}
// Type guards — validates shape only. Callers must also check target.type
// since DiscordConfig and SlackConfig share the same { webhookUrl } shape.
export function isDiscordConfig(config: NotificationConfig): config is DiscordConfig {
return "webhookUrl" in config && typeof config.webhookUrl === "string"
}
export function isNotificationConfig(value: unknown): value is NotificationConfig {
if (!value || typeof value !== "object") return false
const obj = value as Record<string, unknown>
if (typeof obj.webhookUrl === "string") return true
if (typeof obj.serverUrl === "string" && typeof obj.appToken === "string") return true
if (typeof obj.botToken === "string" && typeof obj.chatId === "string") return true
if (
typeof obj.host === "string" &&
typeof obj.port === "number" &&
typeof obj.from === "string" &&
typeof obj.to === "string"
)
return true
return false
}
// Discord webhook URL pattern
export const DISCORD_WEBHOOK_RE = /^https:\/\/discord\.com\/api\/webhooks\/\d+\/[\w-]+$/