Feat : Streamer watch list
Some checks failed
release / build (push) Successful in 28s
release / verify-windows (push) Failing after 1m13s

This commit is contained in:
jeanotx32
2026-08-11 19:24:31 +02:00
parent b85a535dd8
commit e87afc49c0
13 changed files with 936 additions and 48 deletions

View File

@@ -6,13 +6,16 @@ import type {
LogLevel,
ObsSettings,
Platform,
StreamState,
WatchSettings,
WatchTarget,
} from '@stream-control/shared';
import {
DEFAULT_OBS_SETTINGS,
DEFAULT_WATCH_SETTINGS,
normalizeWatchSettings,
safeJsonParse,
stripchatProfileUrl,
} from '@stream-control/shared';
import { config } from './config.ts';
@@ -49,6 +52,26 @@ db.exec(`
);
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts DESC);
-- Profils surveillés : le serveur sonde leur statut et signale les passages
-- en direct. Indépendant des agents : on peut veiller sans rien enregistrer.
CREATE TABLE IF NOT EXISTS watch_targets (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL DEFAULT 'stripchat',
username TEXT NOT NULL,
label TEXT,
agent_id TEXT,
notify INTEGER NOT NULL DEFAULT 1,
state TEXT NOT NULL DEFAULT 'unknown',
raw_status TEXT,
state_since INTEGER NOT NULL,
last_checked_at INTEGER,
last_error TEXT,
created_at INTEGER NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_targets_identity
ON watch_targets (provider, username);
`);
/** Migrations additives : sûres à rejouer sur une base déjà peuplée. */
@@ -249,6 +272,136 @@ export const agentsRepo = {
},
};
// --- Profils surveillés ------------------------------------------------------
interface TargetRow {
id: string;
provider: string;
username: string;
label: string | null;
agent_id: string | null;
notify: number;
state: string;
raw_status: string | null;
state_since: number;
last_checked_at: number | null;
last_error: string | null;
created_at: number;
}
function toTarget(row: TargetRow): WatchTarget {
return {
id: row.id,
provider: (row.provider as WatchTarget['provider']) ?? 'stripchat',
username: row.username,
label: row.label,
url: stripchatProfileUrl(row.username),
agentId: row.agent_id,
notify: Number(row.notify) === 1,
state: (row.state as StreamState) ?? 'unknown',
rawStatus: row.raw_status,
stateSince: Number(row.state_since),
lastCheckedAt: row.last_checked_at === null ? null : Number(row.last_checked_at),
lastError: row.last_error,
createdAt: Number(row.created_at),
};
}
const targetStmts = {
list: db.prepare('SELECT * FROM watch_targets ORDER BY username COLLATE NOCASE'),
get: db.prepare('SELECT * FROM watch_targets WHERE id = ?'),
getByName: db.prepare('SELECT * FROM watch_targets WHERE provider = ? AND username = ?'),
insert: db.prepare(`
INSERT INTO watch_targets (id, provider, username, label, agent_id, notify,
state, state_since, created_at)
VALUES (?, ?, ?, ?, ?, ?, 'unknown', ?, ?)
`),
updateSettings: db.prepare(
'UPDATE watch_targets SET label = ?, agent_id = ?, notify = ? WHERE id = ?',
),
updateState: db.prepare(`
UPDATE watch_targets
SET state = ?, raw_status = ?, state_since = ?, last_checked_at = ?, last_error = ?
WHERE id = ?
`),
remove: db.prepare('DELETE FROM watch_targets WHERE id = ?'),
};
export const targetsRepo = {
list(): WatchTarget[] {
return (targetStmts.list.all() as unknown as TargetRow[]).map(toTarget);
},
get(id: string): WatchTarget | null {
const row = targetStmts.get.get(id) as unknown as TargetRow | undefined;
return row ? toTarget(row) : null;
},
findByUsername(provider: string, username: string): WatchTarget | null {
const row = targetStmts.getByName.get(provider, username) as unknown as TargetRow | undefined;
return row ? toTarget(row) : null;
},
create(input: {
id: string;
username: string;
provider?: string;
label?: string | null;
agentId?: string | null;
notify?: boolean;
}): WatchTarget {
const now = Date.now();
targetStmts.insert.run(
input.id,
input.provider ?? 'stripchat',
input.username,
input.label ?? null,
input.agentId ?? null,
input.notify === false ? 0 : 1,
now,
now,
);
const created = targetsRepo.get(input.id);
if (!created) throw new Error(`Échec de création du profil surveillé ${input.username}`);
return created;
},
updateSettings(
id: string,
settings: { label: string | null; agentId: string | null; notify: boolean },
): void {
targetStmts.updateSettings.run(
settings.label,
settings.agentId,
settings.notify ? 1 : 0,
id,
);
},
updateState(
id: string,
state: {
state: StreamState;
rawStatus: string | null;
stateSince: number;
lastError: string | null;
},
): void {
targetStmts.updateState.run(
state.state,
state.rawStatus,
state.stateSince,
Date.now(),
state.lastError,
id,
);
},
remove(id: string): void {
targetStmts.remove.run(id);
},
};
interface LogRow {
id: number;
agent_id: string | null;