Feat : Log recorder
Some checks failed
release / build (push) Successful in 29s
release / verify-windows (push) Failing after 1m0s

This commit is contained in:
jeanotx32
2026-08-11 22:34:45 +02:00
parent 03b1963150
commit 1dd3959f29
16 changed files with 631 additions and 53 deletions

View File

@@ -1,5 +1,11 @@
import { EventEmitter } from 'node:events';
import type { LogLevel, StreamState, WatchSettings, WatchState } from '@stream-control/shared';
import type {
AgentEvent,
LogLevel,
StreamState,
WatchSettings,
WatchState,
} from '@stream-control/shared';
import { emptyWatchState, fetchStripchatStatus } from '@stream-control/shared';
import { sendHotkey } from './hotkey.ts';
@@ -96,6 +102,7 @@ export class StreamWatcher extends EventEmitter {
`Surveillance de « ${this.settings.username} » (${this.settings.provider}) toutes les ${
this.settings.pollIntervalMs / 1000
} s`,
'watch.started',
);
void this.tick();
this.timer = setInterval(() => void this.tick(), this.settings.pollIntervalMs);
@@ -131,7 +138,12 @@ export class StreamWatcher extends EventEmitter {
this.state.lastCheckedAt = Date.now();
// Sonde en échec : on ne met surtout pas l'enregistrement en pause.
if (this.state.lastError !== message) {
this.emit('log', 'warn', `Sonde « ${this.settings.username} » en échec : ${message}`);
this.emit(
'log',
'warn',
`Sonde « ${this.settings.username} » en échec : ${message}`,
'watch.failed',
);
}
this.state.lastError = message;
} finally {
@@ -149,6 +161,7 @@ export class StreamWatcher extends EventEmitter {
'log',
'info',
`Stream « ${this.settings.username} » : ${label(previous)}${label(next)} (${this.state.rawStatus})`,
TRANSITIONS[next],
);
}
@@ -174,12 +187,18 @@ export class StreamWatcher extends EventEmitter {
if (!record.active || record.paused) return;
await this.actions.pauseRecording();
this.state.autoPaused = true;
this.emit('log', 'info', 'Show privé détecté : enregistrement mis en pause');
this.emit(
'log',
'info',
'Show privé détecté : enregistrement mis en pause',
'record.paused',
);
} catch (err) {
this.emit(
'log',
'error',
`Mise en pause automatique impossible : ${err instanceof Error ? err.message : String(err)}`,
'command.failed',
);
}
}
@@ -190,13 +209,14 @@ export class StreamWatcher extends EventEmitter {
const record = await this.actions.recordState();
if (record.active && record.paused) {
await this.actions.resumeRecording();
this.emit('log', 'info', 'Flux public rétabli : enregistrement repris');
this.emit('log', 'info', 'Flux public rétabli : enregistrement repris', 'record.resumed');
}
} catch (err) {
this.emit(
'log',
'error',
`Reprise automatique impossible : ${err instanceof Error ? err.message : String(err)}`,
'command.failed',
);
} finally {
this.state.autoPaused = false;
@@ -229,16 +249,25 @@ export class StreamWatcher extends EventEmitter {
'log',
'info',
`Plein écran rappelé : touche « ${key} » envoyée à « ${result.window ?? windowMatch} »`,
'fullscreen.restored',
);
return result;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.emit('log', 'warn', `Rappel du plein écran impossible : ${message}`);
this.emit('log', 'warn', `Rappel du plein écran impossible : ${message}`, 'fullscreen.failed');
throw err;
}
}
}
/** Évènement correspondant à l'état atteint ; `unknown` n'en produit aucun. */
const TRANSITIONS: Record<StreamState, AgentEvent | undefined> = {
public: 'watch.public',
private: 'watch.private',
offline: 'watch.offline',
unknown: undefined,
};
function label(state: StreamState): string {
switch (state) {
case 'public':