feat(notifications): alert on remaining volume

This commit is contained in:
Sucukdeluxe
2026-08-22 06:42:13 +02:00
parent f4c4fb2fac
commit 2142ae8bf0
4 changed files with 541 additions and 4 deletions
+63
View File
@@ -62,6 +62,25 @@ export interface HistoryEntryContext {
provider: DebridProvider | null;
}
export interface RunRemainingSnapshot {
remainingBytes: number;
openItems: number;
openPackages: number;
unknownCount: number;
speedBps: number;
etaSeconds: number;
}
export interface RemainingThresholdDecision {
emit: boolean;
remainingBytes?: number;
}
export interface RemainingThresholdState {
snapshot: RunRemainingSnapshot | null;
crossings: number;
}
const SUCCESS_TTL_MS = 6 * 60 * 60 * 1000;
const IMPORTANT_TTL_MS = 24 * 60 * 60 * 1000;
const DIGEST_PACKAGE_LIMIT = 20;
@@ -73,6 +92,25 @@ function finiteNonNegative(value: unknown): number {
return Number.isFinite(numeric) && numeric > 0 ? numeric : 0;
}
export function evaluateRemainingThreshold(
previous: RunRemainingSnapshot | null,
current: RunRemainingSnapshot,
thresholdBytes: number
): RemainingThresholdDecision {
const threshold = finiteNonNegative(thresholdBytes);
if (!previous
|| threshold <= 0
|| previous.openItems <= 0
|| current.openItems <= 0
|| previous.unknownCount > 0
|| current.unknownCount > 0
|| previous.remainingBytes <= threshold
|| current.remainingBytes > threshold) {
return { emit: false };
}
return { emit: true, remainingBytes: current.remainingBytes };
}
function formatBytes(bytes: number): string {
let value = finiteNonNegative(bytes);
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
@@ -285,6 +323,31 @@ export function buildRunNotificationEvent(result: RunResult): NotificationEvent
);
}
export function buildRemainingThresholdNotificationEvent(
runId: string,
crossing: number,
snapshot: RunRemainingSnapshot,
thresholdBytes: number,
createdAt: number
): NotificationEvent {
return event(
`run:${runId}:remaining_threshold_crossed:${crossing}`,
"remaining_threshold_crossed",
"success",
createdAt,
"📉 Restmenge erreicht",
`Der aktive Durchlauf liegt bei oder unter ${formatBytes(thresholdBytes)}.`,
0x3498db,
[
{ name: "Restmenge", value: formatBytes(snapshot.remainingBytes), inline: true },
{ name: "Offene Pakete", value: String(snapshot.openPackages), inline: true },
{ name: "Offene Dateien", value: String(snapshot.openItems), inline: true },
{ name: "Geschwindigkeit", value: snapshot.speedBps > 0 ? `${formatBytes(snapshot.speedBps)}/s` : "—", inline: true },
{ name: "ETA", value: snapshot.etaSeconds >= 0 ? formatDuration(snapshot.etaSeconds) : "—", inline: true }
]
);
}
export function buildHistoryEntry(
result: PackageResult,
context: HistoryEntryContext