openvibers/openvibe.live Commits
The Rundown announces an episode once: a re-read writes it again without a second THE RUNDOWN line in global chat, and a stream that keeps nothing when read again loses its old write-up (#263)
Changes
@@@ -287,7 +287,13 @@ async function readStretch(stream, user) {// ── Episodes ─────────────────────────────────────────────────async function writeEpisode(stream, user) {const segs = await db.all("SELECT id, start_sec, title, what, take, quotes_json, vod_id, topic_id FROM rundown_segments WHERE stream_id = ? AND status = 'live' ORDER BY start_sec ASC LIMIT 40", [stream.id]);-- if (!segs.length) { await setProgress(stream.id, stream.read_to_sec, 1); return { called: false }; }++ if (!segs.length) {++ // Read again (after a desk fix) and nothing was kept: the old write-up goes too (one staff took down stays down).++ await db.run("DELETE FROM rundown_reactions WHERE item_type = 'episode' AND item_id = ? AND EXISTS (SELECT 1 FROM rundown_episodes WHERE stream_id = ? AND status = 'live')", [stream.id, stream.id]);++ await db.run("DELETE FROM rundown_episodes WHERE stream_id = ? AND status = 'live'", [stream.id]);++ await setProgress(stream.id, stream.read_to_sec, 1);++ return { called: false };++ }const topics = new Map((await db.all('SELECT id, title FROM rundown_topics WHERE id = ANY(?)', [[...new Set(segs.map((s) => s.topic_id).filter(Boolean))]])).map((t) => [t.id, t.title]));let chat = null;const minutes = Math.max(0, Math.round((Number(stream.duration_seconds) || ((toMs(stream.ended_at) || Date.now()) - (toMs(stream.started_at) || Date.now())) / 1000) / 60));@@@ -303,12 +309,13 @@ async function writeEpisode(stream, user) {if (!title || filters.addsAccusation(`${out.title} ${out.logline || ''}`, source) || filters.privateDetail(`${out.title} ${out.logline || ''}`)) { await setProgress(stream.id, stream.read_to_sec, 1); return { called: true, failed: true }; }const chapters = (out.chapters || []).map((c) => segs[c.index] ? { sec: Math.round(Number(segs[c.index].start_sec)), title: clean(c.title, 60), segment_id: segs[c.index].id } : null).filter((c) => c && c.title);const vod = (segs.find((s) => s.vod_id) || {}).vod_id || null;++ const rewrite = !!await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [stream.id]);await db.run(`INSERT INTO rundown_episodes (stream_id, user_id, title, logline, recap, chapters_json, vod_id, minutes, aired_at)VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (stream_id) DO UPDATE SET title = excluded.title, logline = excluded.logline, recap = excluded.recap,chapters_json = excluded.chapters_json, vod_id = excluded.vod_id, minutes = excluded.minutes`,[stream.id, user.id, title, clean(out.logline, 220), recap ? recap.replace(/ ¶ /g, '\n\n') : null, JSON.stringify(chapters), vod, minutes, stream.started_at || sqlTime(Date.now())]);await setProgress(stream.id, stream.read_to_sec, 1);-- await announce(stream, user, title).catch(() => {});++ if (!rewrite) await announce(stream, user, title).catch(() => {}); // written again (a re-read): announced once alreadyreturn { called: true, episode: true };}@@@ -310,6 +310,23 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /console.log('✅ limits: lines per stretch, refused stretches skipped after three tries');}++// ── A re-read: the episode is written again but not announced again; a stream left with nothing has no episode ──++{++ const st1 = { ...await db.get('SELECT * FROM streams WHERE id = ?', [s1]), read_to_sec: (await db.get('SELECT read_to_sec FROM rundown_progress WHERE stream_id = ?', [s1])).read_to_sec };++ const was = announced.length;++ await desk.writeEpisode(st1, await db.get('SELECT id, username, display_name FROM users WHERE id = ?', [goose]));++ assert.ok(await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [s1]));++ assert.strictEqual(announced.length, was, 'written again: not announced again');++ const quiet = await mk('quietone');++ const sq = await stream(quiet, { startedAgoMin: 900, endedAgoMin: 800, title: 'Quiet' });++ await db.run("INSERT INTO rundown_episodes (stream_id, user_id, title, recap, chapters_json, aired_at) VALUES (?, ?, 'Old write-up', 'x', '[]', ov_now())", [sq, quiet]);++ await db.run("INSERT INTO rundown_reactions (item_type, item_id, voter, reaction) VALUES ('episode', ?, 'anon:q', 'lol')", [sq]);++ await desk.writeEpisode({ ...await db.get('SELECT * FROM streams WHERE id = ?', [sq]), read_to_sec: 0 }, await db.get('SELECT id, username, display_name FROM users WHERE id = ?', [quiet]));++ assert.ok(!await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [sq]), 'read again with nothing kept: the old write-up goes');++ assert.ok(!await db.get("SELECT 1 FROM rundown_reactions WHERE item_type = 'episode' AND item_id = ?", [sq]), 'and its reactions');++ console.log('✅ a re-read never announces twice and leaves no stale episode');++}++// ── Play-by-play: a stream keeps its best 2 takes on a topic (and 2 with no topic) ──{const gamer = await mk('leaguenight');
@@ -287,7 +287,13 @@ async function readStretch(stream, user) {
@@ -287,7 +287,13 @@ async function readStretch(stream, user) {
// ── Episodes ─────────────────────────────────────────────────
// ── Episodes ─────────────────────────────────────────────────
async function writeEpisode(stream, user) {
async function writeEpisode(stream, user) {
const segs = await db.all("SELECT id, start_sec, title, what, take, quotes_json, vod_id, topic_id FROM rundown_segments WHERE stream_id = ? AND status = 'live' ORDER BY start_sec ASC LIMIT 40", [stream.id]);
const segs = await db.all("SELECT id, start_sec, title, what, take, quotes_json, vod_id, topic_id FROM rundown_segments WHERE stream_id = ? AND status = 'live' ORDER BY start_sec ASC LIMIT 40", [stream.id]);
- if (!segs.length) { await setProgress(stream.id, stream.read_to_sec, 1); return { called: false }; }
+ if (!segs.length) {
+ // Read again (after a desk fix) and nothing was kept: the old write-up goes too (one staff took down stays down).
+ await db.run("DELETE FROM rundown_reactions WHERE item_type = 'episode' AND item_id = ? AND EXISTS (SELECT 1 FROM rundown_episodes WHERE stream_id = ? AND status = 'live')", [stream.id, stream.id]);
+ await db.run("DELETE FROM rundown_episodes WHERE stream_id = ? AND status = 'live'", [stream.id]);
+ await setProgress(stream.id, stream.read_to_sec, 1);
+ return { called: false };
+ }
const topics = new Map((await db.all('SELECT id, title FROM rundown_topics WHERE id = ANY(?)', [[...new Set(segs.map((s) => s.topic_id).filter(Boolean))]])).map((t) => [t.id, t.title]));
const topics = new Map((await db.all('SELECT id, title FROM rundown_topics WHERE id = ANY(?)', [[...new Set(segs.map((s) => s.topic_id).filter(Boolean))]])).map((t) => [t.id, t.title]));
let chat = null;
let chat = null;
const minutes = Math.max(0, Math.round((Number(stream.duration_seconds) || ((toMs(stream.ended_at) || Date.now()) - (toMs(stream.started_at) || Date.now())) / 1000) / 60));
const minutes = Math.max(0, Math.round((Number(stream.duration_seconds) || ((toMs(stream.ended_at) || Date.now()) - (toMs(stream.started_at) || Date.now())) / 1000) / 60));
@@ -303,12 +309,13 @@ async function writeEpisode(stream, user) {
@@ -303,12 +309,13 @@ async function writeEpisode(stream, user) {
if (!title || filters.addsAccusation(`${out.title} ${out.logline || ''}`, source) || filters.privateDetail(`${out.title} ${out.logline || ''}`)) { await setProgress(stream.id, stream.read_to_sec, 1); return { called: true, failed: true }; }
if (!title || filters.addsAccusation(`${out.title} ${out.logline || ''}`, source) || filters.privateDetail(`${out.title} ${out.logline || ''}`)) { await setProgress(stream.id, stream.read_to_sec, 1); return { called: true, failed: true }; }
const chapters = (out.chapters || []).map((c) => segs[c.index] ? { sec: Math.round(Number(segs[c.index].start_sec)), title: clean(c.title, 60), segment_id: segs[c.index].id } : null).filter((c) => c && c.title);
const chapters = (out.chapters || []).map((c) => segs[c.index] ? { sec: Math.round(Number(segs[c.index].start_sec)), title: clean(c.title, 60), segment_id: segs[c.index].id } : null).filter((c) => c && c.title);
const vod = (segs.find((s) => s.vod_id) || {}).vod_id || null;
const vod = (segs.find((s) => s.vod_id) || {}).vod_id || null;
+ const rewrite = !!await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [stream.id]);
await db.run(`INSERT INTO rundown_episodes (stream_id, user_id, title, logline, recap, chapters_json, vod_id, minutes, aired_at)
await db.run(`INSERT INTO rundown_episodes (stream_id, user_id, title, logline, recap, chapters_json, vod_id, minutes, aired_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (stream_id) DO UPDATE SET title = excluded.title, logline = excluded.logline, recap = excluded.recap,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (stream_id) DO UPDATE SET title = excluded.title, logline = excluded.logline, recap = excluded.recap,
chapters_json = excluded.chapters_json, vod_id = excluded.vod_id, minutes = excluded.minutes`,
chapters_json = excluded.chapters_json, vod_id = excluded.vod_id, minutes = excluded.minutes`,
[stream.id, user.id, title, clean(out.logline, 220), recap ? recap.replace(/ ¶ /g, '\n\n') : null, JSON.stringify(chapters), vod, minutes, stream.started_at || sqlTime(Date.now())]);
[stream.id, user.id, title, clean(out.logline, 220), recap ? recap.replace(/ ¶ /g, '\n\n') : null, JSON.stringify(chapters), vod, minutes, stream.started_at || sqlTime(Date.now())]);
await setProgress(stream.id, stream.read_to_sec, 1);
await setProgress(stream.id, stream.read_to_sec, 1);
- await announce(stream, user, title).catch(() => {});
+ if (!rewrite) await announce(stream, user, title).catch(() => {}); // written again (a re-read): announced once already
return { called: true, episode: true };
return { called: true, episode: true };
}
}
@@ -310,6 +310,23 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /
@@ -310,6 +310,23 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /
console.log('✅ limits: lines per stretch, refused stretches skipped after three tries');
console.log('✅ limits: lines per stretch, refused stretches skipped after three tries');
}
}
+// ── A re-read: the episode is written again but not announced again; a stream left with nothing has no episode ──
+{
+ const st1 = { ...await db.get('SELECT * FROM streams WHERE id = ?', [s1]), read_to_sec: (await db.get('SELECT read_to_sec FROM rundown_progress WHERE stream_id = ?', [s1])).read_to_sec };
+ const was = announced.length;
+ await desk.writeEpisode(st1, await db.get('SELECT id, username, display_name FROM users WHERE id = ?', [goose]));
+ assert.ok(await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [s1]));
+ assert.strictEqual(announced.length, was, 'written again: not announced again');
+ const quiet = await mk('quietone');
+ const sq = await stream(quiet, { startedAgoMin: 900, endedAgoMin: 800, title: 'Quiet' });
+ await db.run("INSERT INTO rundown_episodes (stream_id, user_id, title, recap, chapters_json, aired_at) VALUES (?, ?, 'Old write-up', 'x', '[]', ov_now())", [sq, quiet]);
+ await db.run("INSERT INTO rundown_reactions (item_type, item_id, voter, reaction) VALUES ('episode', ?, 'anon:q', 'lol')", [sq]);
+ await desk.writeEpisode({ ...await db.get('SELECT * FROM streams WHERE id = ?', [sq]), read_to_sec: 0 }, await db.get('SELECT id, username, display_name FROM users WHERE id = ?', [quiet]));
+ assert.ok(!await db.get('SELECT 1 FROM rundown_episodes WHERE stream_id = ?', [sq]), 'read again with nothing kept: the old write-up goes');
+ assert.ok(!await db.get("SELECT 1 FROM rundown_reactions WHERE item_type = 'episode' AND item_id = ?", [sq]), 'and its reactions');
+ console.log('✅ a re-read never announces twice and leaves no stale episode');
+}
+
// ── Play-by-play: a stream keeps its best 2 takes on a topic (and 2 with no topic) ──
// ── Play-by-play: a stream keeps its best 2 takes on a topic (and 2 with no topic) ──
{
{
const gamer = await mk('leaguenight');
const gamer = await mk('leaguenight');