openvibers/openvibe.live Commits
No 'free' or $0 in Live's home metadata (owner copy rule; audit 32.2 row 2779): the server-rendered home title, description, heading and Organization description say open-source and community run instead, and the shell's SoftwareApplication JSON-LD drops its offers.price '0'. test/seo-ssr.test.js checks the home head and body (free speech stays fine).
Changes
@@@ -136,12 +136,7 @@"description": "Open source live streaming platform with restreaming, VODs, clips, and 200+ community tools","url": "https://openvibe.live","applicationCategory": "Multimedia",-- "operatingSystem": "Web",-- "offers": {-- "@type": "Offer",-- "price": "0",-- "priceCurrency": "USD"-- }++ "operatingSystem": "Web"}</script><script type="application/ld+json">@@@ -424,8 +424,8 @@ function _mediaSection(heading, items) {function _fmtDur(sec) { sec = Math.floor(Number(sec) || 0); const m = Math.floor(sec / 60), s = sec % 60; return sec ? `${m}:${String(s).padStart(2, '0')}` : ''; }async function _homeMeta() {-- const title = 'OpenVibe.Live — Free Open-Source Live Streaming Platform';-- const description = 'Free, open-source live streaming with sub-second WebRTC, OBS/RTMP & CLI ingest, auto VODs & AI clips, restreaming to Twitch/YouTube/Kick, global chat, viewer-controlled robots, and the whole OpenVibe network behind it.';++ const title = 'OpenVibe.Live — Open-Source Live Streaming, Community Run';++ const description = 'Open-source, community-run live streaming with sub-second WebRTC, OBS/RTMP & CLI ingest, auto VODs & AI clips, restreaming to Twitch/YouTube/Kick, global chat, viewer-controlled robots, and the whole OpenVibe network behind it.';// Pull the actual live content so the source has real, crawlable text.let live = [];@@@ -450,7 +450,7 @@ async function _homeMeta() {const statLine = stats ? `<p>${SITE_NAME} hosts ${stats.streamers || 0} streamers, ${stats.vods || 0} VODs, ${stats.clips || 0} clips, ${stats.pastes || 0} pastes and ${stats.chatMessages || 0} chat messages.</p>` : '';const snapshot =-- `<h1>${SITE_NAME} — free, open-source live streaming</h1>` +++ `<h1>${SITE_NAME} — open-source live streaming, community run</h1>` +`<p>${esc(description)}</p>` + statLine +(liveItems.length ? _mediaSection('Live now', liveItems) : '<section><h2>Live now</h2><p>No one is streaming right now — be the first to go live.</p></section>') +_mediaSection('Recent VODs', vodItems) +@@@ -469,7 +469,7 @@ async function _homeMeta() {{'@context': 'https://schema.org', '@type': 'Organization', '@id': `${baseUrl()}/#org`,name: 'OpenVibe', url: baseUrl(), logo: DEFAULT_OG_IMAGE,-- description: 'A free, open-source network of live streaming, media and developer tools, built and run by the people who use it.',++ description: 'An open-source network of live streaming, media and developer tools, built and run by the people who use it.',// sameAs is how a search engine learns these properties are one brand rather than// unrelated sites that happen to share a name.sameAs: [@@@ -283,6 +283,15 @@ const server = http.createServer(app).listen(0, '127.0.0.1', async () => {assert.ok(/max-age=30\b/.test(r.cache), r.cache);});++ await check('the home page\'s metadata says no "free" or "$0" (owner copy rule; free speech is fine)', async () => {++ const html = (await get('/')).body;++ const meta = head(html) + bodyOf(html);++ const hits = (meta.match(/[^.<>"]{0,30}\bfree\b[^.<>"]{0,30}/gi) || []).filter((m) => !/free speech/i.test(m));++ assert.deepStrictEqual(hits, [], 'free copy in the home metadata');++ assert.ok(!/"price"\s*:/.test(head(html)), 'no price in the structured data');++ assert.ok(!/\$0\b/.test(meta));++ });++await check('server/index.js sends the fallback shell through seo.shellHtml', () => {const src = fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8');assert.ok(src.includes("require('./seo/seo').shellHtml"));
@@ -136,12 +136,7 @@
@@ -136,12 +136,7 @@
"description": "Open source live streaming platform with restreaming, VODs, clips, and 200+ community tools",
"description": "Open source live streaming platform with restreaming, VODs, clips, and 200+ community tools",
"url": "https://openvibe.live",
"url": "https://openvibe.live",
"applicationCategory": "Multimedia",
"applicationCategory": "Multimedia",
- "operatingSystem": "Web",
- "offers": {
- "@type": "Offer",
- "price": "0",
- "priceCurrency": "USD"
- }
+ "operatingSystem": "Web"
}
}
</script>
</script>
<script type="application/ld+json">
<script type="application/ld+json">
@@ -424,8 +424,8 @@ function _mediaSection(heading, items) {
@@ -424,8 +424,8 @@ function _mediaSection(heading, items) {
function _fmtDur(sec) { sec = Math.floor(Number(sec) || 0); const m = Math.floor(sec / 60), s = sec % 60; return sec ? `${m}:${String(s).padStart(2, '0')}` : ''; }
function _fmtDur(sec) { sec = Math.floor(Number(sec) || 0); const m = Math.floor(sec / 60), s = sec % 60; return sec ? `${m}:${String(s).padStart(2, '0')}` : ''; }
async function _homeMeta() {
async function _homeMeta() {
- const title = 'OpenVibe.Live — Free Open-Source Live Streaming Platform';
- const description = 'Free, open-source live streaming with sub-second WebRTC, OBS/RTMP & CLI ingest, auto VODs & AI clips, restreaming to Twitch/YouTube/Kick, global chat, viewer-controlled robots, and the whole OpenVibe network behind it.';
+ const title = 'OpenVibe.Live — Open-Source Live Streaming, Community Run';
+ const description = 'Open-source, community-run live streaming with sub-second WebRTC, OBS/RTMP & CLI ingest, auto VODs & AI clips, restreaming to Twitch/YouTube/Kick, global chat, viewer-controlled robots, and the whole OpenVibe network behind it.';
// Pull the actual live content so the source has real, crawlable text.
// Pull the actual live content so the source has real, crawlable text.
let live = [];
let live = [];
@@ -450,7 +450,7 @@ async function _homeMeta() {
@@ -450,7 +450,7 @@ async function _homeMeta() {
const statLine = stats ? `<p>${SITE_NAME} hosts ${stats.streamers || 0} streamers, ${stats.vods || 0} VODs, ${stats.clips || 0} clips, ${stats.pastes || 0} pastes and ${stats.chatMessages || 0} chat messages.</p>` : '';
const statLine = stats ? `<p>${SITE_NAME} hosts ${stats.streamers || 0} streamers, ${stats.vods || 0} VODs, ${stats.clips || 0} clips, ${stats.pastes || 0} pastes and ${stats.chatMessages || 0} chat messages.</p>` : '';
const snapshot =
const snapshot =
- `<h1>${SITE_NAME} — free, open-source live streaming</h1>` +
+ `<h1>${SITE_NAME} — open-source live streaming, community run</h1>` +
`<p>${esc(description)}</p>` + statLine +
`<p>${esc(description)}</p>` + statLine +
(liveItems.length ? _mediaSection('Live now', liveItems) : '<section><h2>Live now</h2><p>No one is streaming right now — be the first to go live.</p></section>') +
(liveItems.length ? _mediaSection('Live now', liveItems) : '<section><h2>Live now</h2><p>No one is streaming right now — be the first to go live.</p></section>') +
_mediaSection('Recent VODs', vodItems) +
_mediaSection('Recent VODs', vodItems) +
@@ -469,7 +469,7 @@ async function _homeMeta() {
@@ -469,7 +469,7 @@ async function _homeMeta() {
{
{
'@context': 'https://schema.org', '@type': 'Organization', '@id': `${baseUrl()}/#org`,
'@context': 'https://schema.org', '@type': 'Organization', '@id': `${baseUrl()}/#org`,
name: 'OpenVibe', url: baseUrl(), logo: DEFAULT_OG_IMAGE,
name: 'OpenVibe', url: baseUrl(), logo: DEFAULT_OG_IMAGE,
- description: 'A free, open-source network of live streaming, media and developer tools, built and run by the people who use it.',
+ description: 'An open-source network of live streaming, media and developer tools, built and run by the people who use it.',
// sameAs is how a search engine learns these properties are one brand rather than
// sameAs is how a search engine learns these properties are one brand rather than
// unrelated sites that happen to share a name.
// unrelated sites that happen to share a name.
sameAs: [
sameAs: [
@@ -283,6 +283,15 @@ const server = http.createServer(app).listen(0, '127.0.0.1', async () => {
@@ -283,6 +283,15 @@ const server = http.createServer(app).listen(0, '127.0.0.1', async () => {
assert.ok(/max-age=30\b/.test(r.cache), r.cache);
assert.ok(/max-age=30\b/.test(r.cache), r.cache);
});
});
+ await check('the home page\'s metadata says no "free" or "$0" (owner copy rule; free speech is fine)', async () => {
+ const html = (await get('/')).body;
+ const meta = head(html) + bodyOf(html);
+ const hits = (meta.match(/[^.<>"]{0,30}\bfree\b[^.<>"]{0,30}/gi) || []).filter((m) => !/free speech/i.test(m));
+ assert.deepStrictEqual(hits, [], 'free copy in the home metadata');
+ assert.ok(!/"price"\s*:/.test(head(html)), 'no price in the structured data');
+ assert.ok(!/\$0\b/.test(meta));
+ });
+
await check('server/index.js sends the fallback shell through seo.shellHtml', () => {
await check('server/index.js sends the fallback shell through seo.shellHtml', () => {
const src = fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8');
const src = fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8');
assert.ok(src.includes("require('./seo/seo').shellHtml"));
assert.ok(src.includes("require('./seo/seo').shellHtml"));