openvibers/openvibe.chat Commits
Search engines find Vibe: /vibe in the sitemap and llms.txt, the WebApplication keywords name an AI assistant, and robots.txt keeps crawlers off a person's own Vibe chats and memory (/vibe/) (#189)
Changes
@@@ -28,6 +28,7 @@ async function publicPages() {const pages = (await rooms.list(null, { limit: 100 })).public;const out = [{ path: '/', changefreq: 'hourly', priority: 1.0, lastmod: async () => await newestGlobalMessage() },++ { path: '/vibe', changefreq: 'weekly', priority: 0.9 },{ path: '/rooms', changefreq: 'daily', priority: 0.8, lastmod: () => newestRoomActivity(pages) },{ path: '/servers', changefreq: 'daily', priority: 0.8 },{ path: '/updates', changefreq: 'daily', priority: 0.5 },@@@ -68,7 +69,7 @@ function homeJsonLd(config) {seo.jsonLd.website({ name, url: site, description }),seo.jsonLd.softwareApp({name, url: site, description, category: 'SocialNetworkingApplication',-- keywords: 'chat, chat rooms, community servers, voice channels, video calls, direct messages, AI chat members, openvibe',++ keywords: 'AI assistant, chat, chat rooms, community servers, voice channels, video calls, direct messages, AI chat members, openvibe',}),seo.jsonLd.webPage({ name: 'Global chat', url: `${site}/`, description, siteUrl: site }),];@@@ -84,18 +85,20 @@ function createDiscoveryRoutes({ config }) {// the sitemap. Every previous Disallow is kept.res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.robotsTxt({sitemaps: [abs('/sitemap.xml')],-- allow: ['/', '/updates', '/developers', '/rooms', '/r/', '/servers', '/s/'],-- disallow: ['/r/*/settings', '/s/*/settings', '/join/', '/messages', '/settings', '/auth/', '/api/'],++ allow: ['/', '/vibe', '/updates', '/developers', '/rooms', '/r/', '/servers', '/s/'],++ // A person's own Vibe chats and memory (/vibe/…) are theirs: never crawled. /vibe itself is the app's front.++ disallow: ['/r/*/settings', '/s/*/settings', '/join/', '/messages', '/settings', '/auth/', '/api/', '/vibe/'],}));});router.get('/llms.txt', (req, res) => {res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({name: 'OpenVibe.Chat',-- summary: 'OpenVibe.Chat: global chat for the whole OpenVibe network, the chat of every stream on OpenVibe.Live, servers with text and voice channels, rooms, direct messages, calls and AI members.',++ summary: 'OpenVibe.Chat: Vibe, OpenVibe\'s free assistant, global chat for the whole OpenVibe network, the chat of every stream on OpenVibe.Live, servers with text and voice channels, rooms, direct messages, calls and AI members.',details: 'One OpenVibe account works here and on every other OpenVibe site. Anyone can read the global chat and the public rooms without an account; posting needs a signed-in person. The global chat and the public room pages are readable as plain HTML without JavaScript and update live over the site\'s own WebSocket. Direct messages, room settings and the API are per-person and are never listed here.',sections: [{ title: 'Start here', links: [++ { title: 'Vibe', url: abs('/vibe'), note: 'OpenVibe\'s assistant: searches the web and OpenVibe, reads pages and pictures, remembers what it is asked to, sets up recurring work; chats are private to the signed-in person' },{ title: 'Global chat', url: abs('/'), note: 'one room for everyone on OpenVibe, newest messages at the end' },{ title: 'Rooms', url: abs('/rooms'), note: 'every public room, with its topic and activity' },{ title: 'Servers', url: abs('/servers'), note: 'public servers: groups with text and voice channels, roles and invites' },@@@ -49,10 +49,10 @@ t('/robots.txt keeps every Disallow and welcomes the search and AI crawlers', asconst r = await req('/robots.txt');assert.strictEqual(r.status, 200);assert.match(r.type, /^text\/plain/);-- for (const d of ['/r/*/settings', '/messages', '/settings', '/auth/', '/api/']) {++ for (const d of ['/r/*/settings', '/messages', '/settings', '/auth/', '/api/', '/vibe/']) {assert.ok(r.text.includes(`Disallow: ${d}`), `still disallows ${d}`);}-- for (const a of ['/', '/updates', '/rooms', '/r/']) assert.ok(r.text.includes(`Allow: ${a}`), `allows ${a}`);++ for (const a of ['/', '/vibe', '/updates', '/rooms', '/r/']) assert.ok(r.text.includes(`Allow: ${a}`), `allows ${a}`);assert.match(r.text, /Sitemap: https:\/\/openvibe\.chat\/sitemap\.xml/);assert.match(r.text, /User-agent: GPTBot/);assert.match(r.text, /User-agent: ClaudeBot/);@@@ -63,7 +63,7 @@ t('/sitemap.xml is the public pages with a real lastmod, never today', async ()assert.strictEqual(r.status, 200);assert.match(r.type, /^application\/xml/);assert.match(r.text, /^<\?xml version="1\.0" encoding="UTF-8"\?>/);-- for (const p of ['/', '/rooms', '/updates', '/r/night-owls']) {++ for (const p of ['/', '/vibe', '/rooms', '/updates', '/r/night-owls']) {assert.ok(r.text.includes(`<loc>${SITE}${p === '/' ? '/' : p}</loc>`), `lists ${p}`);}assert.ok(!r.text.includes('private-room'), 'a private room is not in the sitemap');
@@ -28,6 +28,7 @@ async function publicPages() {
@@ -28,6 +28,7 @@ async function publicPages() {
const pages = (await rooms.list(null, { limit: 100 })).public;
const pages = (await rooms.list(null, { limit: 100 })).public;
const out = [
const out = [
{ path: '/', changefreq: 'hourly', priority: 1.0, lastmod: async () => await newestGlobalMessage() },
{ path: '/', changefreq: 'hourly', priority: 1.0, lastmod: async () => await newestGlobalMessage() },
+ { path: '/vibe', changefreq: 'weekly', priority: 0.9 },
{ path: '/rooms', changefreq: 'daily', priority: 0.8, lastmod: () => newestRoomActivity(pages) },
{ path: '/rooms', changefreq: 'daily', priority: 0.8, lastmod: () => newestRoomActivity(pages) },
{ path: '/servers', changefreq: 'daily', priority: 0.8 },
{ path: '/servers', changefreq: 'daily', priority: 0.8 },
{ path: '/updates', changefreq: 'daily', priority: 0.5 },
{ path: '/updates', changefreq: 'daily', priority: 0.5 },
@@ -68,7 +69,7 @@ function homeJsonLd(config) {
@@ -68,7 +69,7 @@ function homeJsonLd(config) {
seo.jsonLd.website({ name, url: site, description }),
seo.jsonLd.website({ name, url: site, description }),
seo.jsonLd.softwareApp({
seo.jsonLd.softwareApp({
name, url: site, description, category: 'SocialNetworkingApplication',
name, url: site, description, category: 'SocialNetworkingApplication',
- keywords: 'chat, chat rooms, community servers, voice channels, video calls, direct messages, AI chat members, openvibe',
+ keywords: 'AI assistant, chat, chat rooms, community servers, voice channels, video calls, direct messages, AI chat members, openvibe',
}),
}),
seo.jsonLd.webPage({ name: 'Global chat', url: `${site}/`, description, siteUrl: site }),
seo.jsonLd.webPage({ name: 'Global chat', url: `${site}/`, description, siteUrl: site }),
];
];
@@ -84,18 +85,20 @@ function createDiscoveryRoutes({ config }) {
@@ -84,18 +85,20 @@ function createDiscoveryRoutes({ config }) {
// the sitemap. Every previous Disallow is kept.
// the sitemap. Every previous Disallow is kept.
res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.robotsTxt({
res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.robotsTxt({
sitemaps: [abs('/sitemap.xml')],
sitemaps: [abs('/sitemap.xml')],
- allow: ['/', '/updates', '/developers', '/rooms', '/r/', '/servers', '/s/'],
- disallow: ['/r/*/settings', '/s/*/settings', '/join/', '/messages', '/settings', '/auth/', '/api/'],
+ allow: ['/', '/vibe', '/updates', '/developers', '/rooms', '/r/', '/servers', '/s/'],
+ // A person's own Vibe chats and memory (/vibe/…) are theirs: never crawled. /vibe itself is the app's front.
+ disallow: ['/r/*/settings', '/s/*/settings', '/join/', '/messages', '/settings', '/auth/', '/api/', '/vibe/'],
}));
}));
});
});
router.get('/llms.txt', (req, res) => {
router.get('/llms.txt', (req, res) => {
res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
name: 'OpenVibe.Chat',
name: 'OpenVibe.Chat',
- summary: 'OpenVibe.Chat: global chat for the whole OpenVibe network, the chat of every stream on OpenVibe.Live, servers with text and voice channels, rooms, direct messages, calls and AI members.',
+ summary: 'OpenVibe.Chat: Vibe, OpenVibe\'s free assistant, global chat for the whole OpenVibe network, the chat of every stream on OpenVibe.Live, servers with text and voice channels, rooms, direct messages, calls and AI members.',
details: 'One OpenVibe account works here and on every other OpenVibe site. Anyone can read the global chat and the public rooms without an account; posting needs a signed-in person. The global chat and the public room pages are readable as plain HTML without JavaScript and update live over the site\'s own WebSocket. Direct messages, room settings and the API are per-person and are never listed here.',
details: 'One OpenVibe account works here and on every other OpenVibe site. Anyone can read the global chat and the public rooms without an account; posting needs a signed-in person. The global chat and the public room pages are readable as plain HTML without JavaScript and update live over the site\'s own WebSocket. Direct messages, room settings and the API are per-person and are never listed here.',
sections: [
sections: [
{ title: 'Start here', links: [
{ title: 'Start here', links: [
+ { title: 'Vibe', url: abs('/vibe'), note: 'OpenVibe\'s assistant: searches the web and OpenVibe, reads pages and pictures, remembers what it is asked to, sets up recurring work; chats are private to the signed-in person' },
{ title: 'Global chat', url: abs('/'), note: 'one room for everyone on OpenVibe, newest messages at the end' },
{ title: 'Global chat', url: abs('/'), note: 'one room for everyone on OpenVibe, newest messages at the end' },
{ title: 'Rooms', url: abs('/rooms'), note: 'every public room, with its topic and activity' },
{ title: 'Rooms', url: abs('/rooms'), note: 'every public room, with its topic and activity' },
{ title: 'Servers', url: abs('/servers'), note: 'public servers: groups with text and voice channels, roles and invites' },
{ title: 'Servers', url: abs('/servers'), note: 'public servers: groups with text and voice channels, roles and invites' },
@@ -49,10 +49,10 @@ t('/robots.txt keeps every Disallow and welcomes the search and AI crawlers', as
@@ -49,10 +49,10 @@ t('/robots.txt keeps every Disallow and welcomes the search and AI crawlers', as
const r = await req('/robots.txt');
const r = await req('/robots.txt');
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
assert.match(r.type, /^text\/plain/);
assert.match(r.type, /^text\/plain/);
- for (const d of ['/r/*/settings', '/messages', '/settings', '/auth/', '/api/']) {
+ for (const d of ['/r/*/settings', '/messages', '/settings', '/auth/', '/api/', '/vibe/']) {
assert.ok(r.text.includes(`Disallow: ${d}`), `still disallows ${d}`);
assert.ok(r.text.includes(`Disallow: ${d}`), `still disallows ${d}`);
}
}
- for (const a of ['/', '/updates', '/rooms', '/r/']) assert.ok(r.text.includes(`Allow: ${a}`), `allows ${a}`);
+ for (const a of ['/', '/vibe', '/updates', '/rooms', '/r/']) assert.ok(r.text.includes(`Allow: ${a}`), `allows ${a}`);
assert.match(r.text, /Sitemap: https:\/\/openvibe\.chat\/sitemap\.xml/);
assert.match(r.text, /Sitemap: https:\/\/openvibe\.chat\/sitemap\.xml/);
assert.match(r.text, /User-agent: GPTBot/);
assert.match(r.text, /User-agent: GPTBot/);
assert.match(r.text, /User-agent: ClaudeBot/);
assert.match(r.text, /User-agent: ClaudeBot/);
@@ -63,7 +63,7 @@ t('/sitemap.xml is the public pages with a real lastmod, never today', async ()
@@ -63,7 +63,7 @@ t('/sitemap.xml is the public pages with a real lastmod, never today', async ()
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
assert.match(r.type, /^application\/xml/);
assert.match(r.type, /^application\/xml/);
assert.match(r.text, /^<\?xml version="1\.0" encoding="UTF-8"\?>/);
assert.match(r.text, /^<\?xml version="1\.0" encoding="UTF-8"\?>/);
- for (const p of ['/', '/rooms', '/updates', '/r/night-owls']) {
+ for (const p of ['/', '/vibe', '/rooms', '/updates', '/r/night-owls']) {
assert.ok(r.text.includes(`<loc>${SITE}${p === '/' ? '/' : p}</loc>`), `lists ${p}`);
assert.ok(r.text.includes(`<loc>${SITE}${p === '/' ? '/' : p}</loc>`), `lists ${p}`);
}
}
assert.ok(!r.text.includes('private-room'), 'a private room is not in the sitemap');
assert.ok(!r.text.includes('private-room'), 'a private room is not in the sitemap');