openvibers/openvibe.live Commits
The home's "Lately on OpenVibe" quotes skip a take with a slur, strong profanity or a hate topic and show the next best; the Rundown keeps every take (#345)
Changes
nnew file mode 100644@@@ -0,0 +1,20 @@++'use strict';++/**++ * What streamers said, as the home page's "Lately on OpenVibe" strip quotes it: the front page of the site, read by++ * everyone who arrives. A quote with a slur (chat's built-in categories), strong profanity or a hate topic out of++ * context ("…that's got an anti-semitic trope", "…people that I want to fucking follow anymore", the strip on++ * 2026-10-24) is left off it; the next best take is shown instead. The Rundown keeps every take as said.++ */++const { CORE_SLUR_CATEGORIES } = require('../chat/moderation-utils');++++const ROUGH = /\b(?:f+u+c+k\w*|motherf\w*|sh[i1]t(?:s|ty|ting|ted)?|bullshit|cunts?|bitch\w*|whores?|sluts?|pussy|retard\w*|nazis?|hitler|holocaust|anti-?semit\w*|kkk|rap(?:e|ed|es|ist|ists|ing))\b/i;++const SLUR_RES = CORE_SLUR_CATEGORIES.map((c) => ({ c, res: c.patterns.map((p) => new RegExp(p, 'i')) }));++++/** True when the text may lead the front page. */++function frontPageOk(text) {++ const t = String(text || '');++ if (!t.trim() || ROUGH.test(t)) return false;++ return !SLUR_RES.some(({ c, res }) => c.generateCandidates(t).some((x) => res.some((re) => re.test(x))));++}++++module.exports = { frontPageOk };@@@ -503,13 +503,17 @@ router.get('/digest', optionalAuth, async (req, res) => {new_members: Number((await one('SELECT COUNT(*) AS n FROM users WHERE created_at >= ?', [sinceSql]) || {}).n || 0),takes: Number((await one("SELECT COUNT(*) AS n FROM rundown_segments WHERE status = 'live' AND said_at >= ?", [sinceSql]) || {}).n || 0),};-- // The best Rundown takes since then (server/rundown): the quote when there is one, else the headline.++ // The best Rundown takes since then (server/rundown): the quote when there is one, else the headline. The front++ // page leaves out a take with a slur, strong profanity or a hate topic (front-page-text.js) and shows the next.let hot = [];try {++ const { frontPageOk } = require('./front-page-text');hot = (await db.all(`SELECT s.title, s.quotes_json, s.said_at, t.title AS topic, t.slug, u.username, u.display_name FROM rundown_segments sJOIN users u ON u.id = s.user_id LEFT JOIN rundown_topics t ON t.id = s.topic_id-- WHERE s.status = 'live' AND s.said_at >= ? AND s.score >= 4 AND COALESCE(u.is_banned, 0) = 0 ORDER BY s.score DESC, s.said_at DESC LIMIT 3`, [sinceSql]) || [])-- .map((r) => { let q = null; try { q = JSON.parse(r.quotes_json || '[]')[0] || null; } catch { q = null; } return { text: q ? q.text : r.title, quoted: !!q, topic: r.topic || null, slug: r.slug || null, said_at: r.said_at, username: r.username, display_name: r.display_name }; });++ WHERE s.status = 'live' AND s.said_at >= ? AND s.score >= 4 AND COALESCE(u.is_banned, 0) = 0 ORDER BY s.score DESC, s.said_at DESC LIMIT 15`, [sinceSql]) || [])++ .map((r) => { let q = null; try { q = JSON.parse(r.quotes_json || '[]')[0] || null; } catch { q = null; } return { text: q ? q.text : r.title, quoted: !!q, topic: r.topic || null, slug: r.slug || null, said_at: r.said_at, username: r.username, display_name: r.display_name }; })++ .filter((h) => frontPageOk(h.text) && (!h.topic || frontPageOk(h.topic)))++ .slice(0, 3);} catch { hot = []; }res.set('Cache-Control', uid ? 'private, max-age=30' : 'public, max-age=60');res.json({ since, signed_in: !!uid, liveNow, streamed, stats, hot });nnew file mode 100644@@@ -0,0 +1,32 @@++'use strict';++/**++ * The home page's "Lately on OpenVibe" quotes leave out a take with a slur, strong profanity or a hate topic, and show++ * the next best one (server/home/front-page-text.js); the Rundown keeps every take as said.++ *++ * node test/home-front-page-text.test.js++ */++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const { frontPageOk } = require('../server/home/front-page-text');++++// The strip on 2026-10-24, and more of the same kind.++for (const t of [++ 'I look up to and frankly I don\'t even see people that I want to fucking follow anymore.',++ 'I had Hermes say, oh, that\'s got an anti-semitic trope. I can\'t help.',++ 'that was some bullshit', 'what a bitch move', 'he went full nazi on it', 'the Holocaust bit again',++ 'n1gger', 'f a g g o t', 'Motherfucker!', '', null,++]) assert.ok(!frontPageOk(t), `left off the front page: ${t}`);++for (const t of [++ 'That first time I walked in, I knew that I was not alone.', 'We finally beat the boss on hard mode',++ 'Shitake mushrooms are underrated', 'My dad Dick says hi', 'Scunthorpe won again', 'Grape soda forever',++ 'The cockpit view in this sim is amazing',++]) assert.ok(frontPageOk(t), `fine for the front page: ${t}`);++console.log(' ✓ slurs, strong profanity and hate topics are left off; ordinary takes (and Shitake, Scunthorpe, grape) stay');++++const routes = fs.readFileSync(path.join(__dirname, '..', 'server', 'home', 'routes.js'), 'utf8');++assert.ok(routes.includes("const { frontPageOk } = require('./front-page-text');"));++assert.ok(routes.includes('.filter((h) => frontPageOk(h.text) && (!h.topic || frontPageOk(h.topic)))') && routes.includes('.slice(0, 3);'), 'the digest filters, then keeps three');++assert.ok(/ORDER BY s\.score DESC, s\.said_at DESC LIMIT 15/.test(routes), 'from the best fifteen, so a filtered take has a replacement');++console.log(' ✓ the home digest takes the three best takes that pass');++console.log('home front-page text: all passed');
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+'use strict';
+/**
+ * What streamers said, as the home page's "Lately on OpenVibe" strip quotes it: the front page of the site, read by
+ * everyone who arrives. A quote with a slur (chat's built-in categories), strong profanity or a hate topic out of
+ * context ("…that's got an anti-semitic trope", "…people that I want to fucking follow anymore", the strip on
+ * 2026-10-24) is left off it; the next best take is shown instead. The Rundown keeps every take as said.
+ */
+const { CORE_SLUR_CATEGORIES } = require('../chat/moderation-utils');
+
+const ROUGH = /\b(?:f+u+c+k\w*|motherf\w*|sh[i1]t(?:s|ty|ting|ted)?|bullshit|cunts?|bitch\w*|whores?|sluts?|pussy|retard\w*|nazis?|hitler|holocaust|anti-?semit\w*|kkk|rap(?:e|ed|es|ist|ists|ing))\b/i;
+const SLUR_RES = CORE_SLUR_CATEGORIES.map((c) => ({ c, res: c.patterns.map((p) => new RegExp(p, 'i')) }));
+
+/** True when the text may lead the front page. */
+function frontPageOk(text) {
+ const t = String(text || '');
+ if (!t.trim() || ROUGH.test(t)) return false;
+ return !SLUR_RES.some(({ c, res }) => c.generateCandidates(t).some((x) => res.some((re) => re.test(x))));
+}
+
+module.exports = { frontPageOk };
@@ -503,13 +503,17 @@ router.get('/digest', optionalAuth, async (req, res) => {
@@ -503,13 +503,17 @@ router.get('/digest', optionalAuth, async (req, res) => {
new_members: Number((await one('SELECT COUNT(*) AS n FROM users WHERE created_at >= ?', [sinceSql]) || {}).n || 0),
new_members: Number((await one('SELECT COUNT(*) AS n FROM users WHERE created_at >= ?', [sinceSql]) || {}).n || 0),
takes: Number((await one("SELECT COUNT(*) AS n FROM rundown_segments WHERE status = 'live' AND said_at >= ?", [sinceSql]) || {}).n || 0),
takes: Number((await one("SELECT COUNT(*) AS n FROM rundown_segments WHERE status = 'live' AND said_at >= ?", [sinceSql]) || {}).n || 0),
};
};
- // The best Rundown takes since then (server/rundown): the quote when there is one, else the headline.
+ // The best Rundown takes since then (server/rundown): the quote when there is one, else the headline. The front
+ // page leaves out a take with a slur, strong profanity or a hate topic (front-page-text.js) and shows the next.
let hot = [];
let hot = [];
try {
try {
+ const { frontPageOk } = require('./front-page-text');
hot = (await db.all(`SELECT s.title, s.quotes_json, s.said_at, t.title AS topic, t.slug, u.username, u.display_name FROM rundown_segments s
hot = (await db.all(`SELECT s.title, s.quotes_json, s.said_at, t.title AS topic, t.slug, u.username, u.display_name FROM rundown_segments s
JOIN users u ON u.id = s.user_id LEFT JOIN rundown_topics t ON t.id = s.topic_id
JOIN users u ON u.id = s.user_id LEFT JOIN rundown_topics t ON t.id = s.topic_id
- WHERE s.status = 'live' AND s.said_at >= ? AND s.score >= 4 AND COALESCE(u.is_banned, 0) = 0 ORDER BY s.score DESC, s.said_at DESC LIMIT 3`, [sinceSql]) || [])
- .map((r) => { let q = null; try { q = JSON.parse(r.quotes_json || '[]')[0] || null; } catch { q = null; } return { text: q ? q.text : r.title, quoted: !!q, topic: r.topic || null, slug: r.slug || null, said_at: r.said_at, username: r.username, display_name: r.display_name }; });
+ WHERE s.status = 'live' AND s.said_at >= ? AND s.score >= 4 AND COALESCE(u.is_banned, 0) = 0 ORDER BY s.score DESC, s.said_at DESC LIMIT 15`, [sinceSql]) || [])
+ .map((r) => { let q = null; try { q = JSON.parse(r.quotes_json || '[]')[0] || null; } catch { q = null; } return { text: q ? q.text : r.title, quoted: !!q, topic: r.topic || null, slug: r.slug || null, said_at: r.said_at, username: r.username, display_name: r.display_name }; })
+ .filter((h) => frontPageOk(h.text) && (!h.topic || frontPageOk(h.topic)))
+ .slice(0, 3);
} catch { hot = []; }
} catch { hot = []; }
res.set('Cache-Control', uid ? 'private, max-age=30' : 'public, max-age=60');
res.set('Cache-Control', uid ? 'private, max-age=30' : 'public, max-age=60');
res.json({ since, signed_in: !!uid, liveNow, streamed, stats, hot });
res.json({ since, signed_in: !!uid, liveNow, streamed, stats, hot });
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+'use strict';
+/**
+ * The home page's "Lately on OpenVibe" quotes leave out a take with a slur, strong profanity or a hate topic, and show
+ * the next best one (server/home/front-page-text.js); the Rundown keeps every take as said.
+ *
+ * node test/home-front-page-text.test.js
+ */
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const { frontPageOk } = require('../server/home/front-page-text');
+
+// The strip on 2026-10-24, and more of the same kind.
+for (const t of [
+ 'I look up to and frankly I don\'t even see people that I want to fucking follow anymore.',
+ 'I had Hermes say, oh, that\'s got an anti-semitic trope. I can\'t help.',
+ 'that was some bullshit', 'what a bitch move', 'he went full nazi on it', 'the Holocaust bit again',
+ 'n1gger', 'f a g g o t', 'Motherfucker!', '', null,
+]) assert.ok(!frontPageOk(t), `left off the front page: ${t}`);
+for (const t of [
+ 'That first time I walked in, I knew that I was not alone.', 'We finally beat the boss on hard mode',
+ 'Shitake mushrooms are underrated', 'My dad Dick says hi', 'Scunthorpe won again', 'Grape soda forever',
+ 'The cockpit view in this sim is amazing',
+]) assert.ok(frontPageOk(t), `fine for the front page: ${t}`);
+console.log(' ✓ slurs, strong profanity and hate topics are left off; ordinary takes (and Shitake, Scunthorpe, grape) stay');
+
+const routes = fs.readFileSync(path.join(__dirname, '..', 'server', 'home', 'routes.js'), 'utf8');
+assert.ok(routes.includes("const { frontPageOk } = require('./front-page-text');"));
+assert.ok(routes.includes('.filter((h) => frontPageOk(h.text) && (!h.topic || frontPageOk(h.topic)))') && routes.includes('.slice(0, 3);'), 'the digest filters, then keeps three');
+assert.ok(/ORDER BY s\.score DESC, s\.said_at DESC LIMIT 15/.test(routes), 'from the best fifteen, so a filtered take has a replacement');
+console.log(' ✓ the home digest takes the three best takes that pass');
+console.log('home front-page text: all passed');