openvibers/openvibe.network Commits
Go-live links open the channel: notifications (Live's direct call and the live.stream.started consumer) and the Discord go-live post linked to https://openvibe.live/<username>, which is not a page on Live (it showed the home page). stream-live.js channelUrl() builds /@<username>, and canonicalChannelUrl() rewrites an old-style link an event still carries; other Live links pass through
Changes
@@@ -133,7 +133,7 @@ class DiscordService {}const displayName = streamer.display_name || streamer.username;-- const streamUrl = `https://openvibe.live/${streamer.username}`;++ const streamUrl = require('../notifications/stream-live').channelUrl(streamer.username);const title = stream.title || 'Started streaming';const embed = new Discord.EmbedBuilder()@@@ -377,7 +377,7 @@ router.post('/events/stream-live', principals.guard('network.notifications.push'const db = getDb(req);const notifData = streamLive.streamLiveNotification({username: streamer.username, displayName: streamer.display_name, avatarUrl: streamer.avatar_url,-- senderId: streamer.id || null, stream, url: `https://openvibe.live/${streamer.username}`,++ senderId: streamer.id || null, stream, url: streamLive.channelUrl(streamer.username),});// The streaming follow graph lives in OpenVibe.Live, keyed by LIVE user ids; Live@@@ -143,7 +143,7 @@ function liveStarted(event, { now, maxAgeMs }) {return {streamId, subject, username, displayName: clean(ch.display_name, 60) || username,title: clean(p.title, 200) || null, protocol: clean(p.protocol, 20) || null,-- url: publicLink(ch.url) || `https://openvibe.live/${encodeURIComponent(username)}`,++ url: require('./stream-live').canonicalChannelUrl(publicLink(ch.url), username),};}@@@ -12,6 +12,20 @@* consumes the event, whichever arrives first announces and the other is skipped as `cooldown`.*/++/**++ * A channel's page on OpenVibe.Live is /@<username>. Links built before 2026-09-23 (and events++ * still queued from then) say https://openvibe.live/<username>, which is not a page: rewrite those.++ */++const LIVE_ORIGIN = 'https://openvibe.live';++function channelUrl(username) {++ return `${LIVE_ORIGIN}/@${encodeURIComponent(String(username || '').replace(/^@+/, ''))}`;++}++function canonicalChannelUrl(url, username) {++ const m = /^https:\/\/openvibe\.live\/([A-Za-z0-9_]{3,24})\/?$/.exec(String(url || ''));++ if (m) return channelUrl(m[1]);++ return url || channelUrl(username);++}++/** One fan-out per streamer per `stream_live_cooldown_min` (60) and at most `stream_live_daily_cap` (8) per 24h. */function ensureAnnouncements(db) {db.exec(`CREATE TABLE IF NOT EXISTS stream_live_announcements (@@@ -62,7 +76,7 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, stsender_name: name,sender_avatar: avatarUrl || null,service: 'live',-- url: url || `https://openvibe.live/${encodeURIComponent(username)}`,++ url: canonicalChannelUrl(url, username),rich_content: {thumbnail: avatarUrl || null,context: {@@@ -76,4 +90,4 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, st};}--module.exports = { ensureAnnouncements, claimAnnouncement, allLiveSubscribers, streamLiveNotification };++module.exports = { ensureAnnouncements, claimAnnouncement, allLiveSubscribers, streamLiveNotification, channelUrl, canonicalChannelUrl };@@@ -253,7 +253,16 @@ const rows = (userId) => db.prepare('SELECT * FROM notifications WHERE user_id =assert.strictEqual(n1.title, 'Carol 3 is live!');assert.strictEqual(n1.message, 'Building a b robot /b', 'no markup reaches the inbox');assert.strictEqual(n1.category, 'stream');-- assert.strictEqual(n1.url, 'https://openvibe.live/carol');++ assert.strictEqual(n1.url, 'https://openvibe.live/@carol', 'an old-style channel link is rewritten to the channel page');++ {++ const { channelUrl, canonicalChannelUrl } = require('../server/notifications/stream-live');++ assert.strictEqual(channelUrl('carol'), 'https://openvibe.live/@carol');++ assert.strictEqual(channelUrl('@carol'), 'https://openvibe.live/@carol');++ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/@carol', 'carol'), 'https://openvibe.live/@carol', 'a channel link is kept');++ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/carol/', 'carol'), 'https://openvibe.live/@carol');++ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/vod/5', 'carol'), 'https://openvibe.live/vod/5', 'other Live pages are kept');++ assert.strictEqual(canonicalChannelUrl(null, 'carol'), 'https://openvibe.live/@carol', 'no link: the channel page');++ }assert.strictEqual(n1.sender_id, 20, 'sender is the streamer\'s Network account (dedupe key)');assert.strictEqual(n1.sender_avatar, 'https://openvibe.media/avatar/carol');assert.strictEqual(JSON.parse(n1.rich_content).context.event_id, s1.event_id);
@@ -133,7 +133,7 @@ class DiscordService {
@@ -133,7 +133,7 @@ class DiscordService {
}
}
const displayName = streamer.display_name || streamer.username;
const displayName = streamer.display_name || streamer.username;
- const streamUrl = `https://openvibe.live/${streamer.username}`;
+ const streamUrl = require('../notifications/stream-live').channelUrl(streamer.username);
const title = stream.title || 'Started streaming';
const title = stream.title || 'Started streaming';
const embed = new Discord.EmbedBuilder()
const embed = new Discord.EmbedBuilder()
@@ -377,7 +377,7 @@ router.post('/events/stream-live', principals.guard('network.notifications.push'
@@ -377,7 +377,7 @@ router.post('/events/stream-live', principals.guard('network.notifications.push'
const db = getDb(req);
const db = getDb(req);
const notifData = streamLive.streamLiveNotification({
const notifData = streamLive.streamLiveNotification({
username: streamer.username, displayName: streamer.display_name, avatarUrl: streamer.avatar_url,
username: streamer.username, displayName: streamer.display_name, avatarUrl: streamer.avatar_url,
- senderId: streamer.id || null, stream, url: `https://openvibe.live/${streamer.username}`,
+ senderId: streamer.id || null, stream, url: streamLive.channelUrl(streamer.username),
});
});
// The streaming follow graph lives in OpenVibe.Live, keyed by LIVE user ids; Live
// The streaming follow graph lives in OpenVibe.Live, keyed by LIVE user ids; Live
@@ -143,7 +143,7 @@ function liveStarted(event, { now, maxAgeMs }) {
@@ -143,7 +143,7 @@ function liveStarted(event, { now, maxAgeMs }) {
return {
return {
streamId, subject, username, displayName: clean(ch.display_name, 60) || username,
streamId, subject, username, displayName: clean(ch.display_name, 60) || username,
title: clean(p.title, 200) || null, protocol: clean(p.protocol, 20) || null,
title: clean(p.title, 200) || null, protocol: clean(p.protocol, 20) || null,
- url: publicLink(ch.url) || `https://openvibe.live/${encodeURIComponent(username)}`,
+ url: require('./stream-live').canonicalChannelUrl(publicLink(ch.url), username),
};
};
}
}
@@ -12,6 +12,20 @@
@@ -12,6 +12,20 @@
* consumes the event, whichever arrives first announces and the other is skipped as `cooldown`.
* consumes the event, whichever arrives first announces and the other is skipped as `cooldown`.
*/
*/
+/**
+ * A channel's page on OpenVibe.Live is /@<username>. Links built before 2026-09-23 (and events
+ * still queued from then) say https://openvibe.live/<username>, which is not a page: rewrite those.
+ */
+const LIVE_ORIGIN = 'https://openvibe.live';
+function channelUrl(username) {
+ return `${LIVE_ORIGIN}/@${encodeURIComponent(String(username || '').replace(/^@+/, ''))}`;
+}
+function canonicalChannelUrl(url, username) {
+ const m = /^https:\/\/openvibe\.live\/([A-Za-z0-9_]{3,24})\/?$/.exec(String(url || ''));
+ if (m) return channelUrl(m[1]);
+ return url || channelUrl(username);
+}
+
/** One fan-out per streamer per `stream_live_cooldown_min` (60) and at most `stream_live_daily_cap` (8) per 24h. */
/** One fan-out per streamer per `stream_live_cooldown_min` (60) and at most `stream_live_daily_cap` (8) per 24h. */
function ensureAnnouncements(db) {
function ensureAnnouncements(db) {
db.exec(`CREATE TABLE IF NOT EXISTS stream_live_announcements (
db.exec(`CREATE TABLE IF NOT EXISTS stream_live_announcements (
@@ -62,7 +76,7 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, st
@@ -62,7 +76,7 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, st
sender_name: name,
sender_name: name,
sender_avatar: avatarUrl || null,
sender_avatar: avatarUrl || null,
service: 'live',
service: 'live',
- url: url || `https://openvibe.live/${encodeURIComponent(username)}`,
+ url: canonicalChannelUrl(url, username),
rich_content: {
rich_content: {
thumbnail: avatarUrl || null,
thumbnail: avatarUrl || null,
context: {
context: {
@@ -76,4 +90,4 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, st
@@ -76,4 +90,4 @@ function streamLiveNotification({ username, displayName, avatarUrl, senderId, st
};
};
}
}
-module.exports = { ensureAnnouncements, claimAnnouncement, allLiveSubscribers, streamLiveNotification };
+module.exports = { ensureAnnouncements, claimAnnouncement, allLiveSubscribers, streamLiveNotification, channelUrl, canonicalChannelUrl };
@@ -253,7 +253,16 @@ const rows = (userId) => db.prepare('SELECT * FROM notifications WHERE user_id =
@@ -253,7 +253,16 @@ const rows = (userId) => db.prepare('SELECT * FROM notifications WHERE user_id =
assert.strictEqual(n1.title, 'Carol 3 is live!');
assert.strictEqual(n1.title, 'Carol 3 is live!');
assert.strictEqual(n1.message, 'Building a b robot /b', 'no markup reaches the inbox');
assert.strictEqual(n1.message, 'Building a b robot /b', 'no markup reaches the inbox');
assert.strictEqual(n1.category, 'stream');
assert.strictEqual(n1.category, 'stream');
- assert.strictEqual(n1.url, 'https://openvibe.live/carol');
+ assert.strictEqual(n1.url, 'https://openvibe.live/@carol', 'an old-style channel link is rewritten to the channel page');
+ {
+ const { channelUrl, canonicalChannelUrl } = require('../server/notifications/stream-live');
+ assert.strictEqual(channelUrl('carol'), 'https://openvibe.live/@carol');
+ assert.strictEqual(channelUrl('@carol'), 'https://openvibe.live/@carol');
+ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/@carol', 'carol'), 'https://openvibe.live/@carol', 'a channel link is kept');
+ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/carol/', 'carol'), 'https://openvibe.live/@carol');
+ assert.strictEqual(canonicalChannelUrl('https://openvibe.live/vod/5', 'carol'), 'https://openvibe.live/vod/5', 'other Live pages are kept');
+ assert.strictEqual(canonicalChannelUrl(null, 'carol'), 'https://openvibe.live/@carol', 'no link: the channel page');
+ }
assert.strictEqual(n1.sender_id, 20, 'sender is the streamer\'s Network account (dedupe key)');
assert.strictEqual(n1.sender_id, 20, 'sender is the streamer\'s Network account (dedupe key)');
assert.strictEqual(n1.sender_avatar, 'https://openvibe.media/avatar/carol');
assert.strictEqual(n1.sender_avatar, 'https://openvibe.media/avatar/carol');
assert.strictEqual(JSON.parse(n1.rich_content).context.event_id, s1.event_id);
assert.strictEqual(JSON.parse(n1.rich_content).context.event_id, s1.event_id);