openvibers/openvibe.live Commits
A tip with a hidden amount says 'sent a tip', never 'donated 0 Vibes': Tips sends amount null for privacy.hide_amount, and Live turned it into 0 in the chat event, the saved line and TTS. The delivery route keeps null, the saved line reads '<name> sent a tip: <message>', and chat renders and speaks it without an amount. Test: a hidden-amount delivery broadcasts amount null and saves 'Viewer sent a tip: hi'.
Changes
@@@ -4119,24 +4119,26 @@ function addDonationMessage(msg) {el.className = 'chat-msg donation';const donorName = esc(msg.username || msg.from || 'Anonymous');-- const amount = Math.round(msg.amount || 0);-- const amtStr = amount.toLocaleString();++ // A supporter can hide the amount (Tips privacy.hide_amount): the amount arrives as null and the line says "sent a tip".++ const hidden = msg.amount == null;++ const amount = hidden ? 0 : Math.round(msg.amount || 0);++ const did = hidden ? 'sent a tip' : `donated <strong>${amount.toLocaleString()} Vibes</strong>`;const rawDonText = msg.message || '';const text = rawDonText ? `: ${(typeof parseEmotes === 'function') ? parseEmotes(rawDonText) : esc(rawDonText)}` : '';-- el.innerHTML = `<i class="fa-solid fa-coins" style="color:var(--accent)"></i> <strong>${donorName}</strong> donated <strong>${amtStr} Vibes</strong>${text}`;++ el.innerHTML = `<i class="fa-solid fa-coins" style="color:var(--accent)"></i> <strong>${donorName}</strong> ${did}${text}`;container.appendChild(el);scrollChat();if (_chatUserScrolledUp) _onNewChatMessageWhileScrolledUp();queueFullscreenChatEntry({kind: 'donation',-- html: `<div class="fullscreen-chat-meta"><span class="fullscreen-chat-user" style="color:var(--accent)"><i class="fa-solid fa-coins"></i> ${donorName}</span></div><div class="fullscreen-chat-text">donated <strong>${amtStr} Vibes</strong>${text}</div>`,++ html: `<div class="fullscreen-chat-meta"><span class="fullscreen-chat-user" style="color:var(--accent)"><i class="fa-solid fa-coins"></i> ${donorName}</span></div><div class="fullscreen-chat-text">${did}${text}</div>`,});// TTS for donationsif (document.getElementById('tts-checkbox')?.checked) {-- speakTTS(`${donorName} donated ${amount} Vibes. ${msg.message || ''}`);++ speakTTS(`${donorName} ${hidden ? 'sent a tip' : `donated ${amount} Vibes`}. ${msg.message || ''}`);}}@@@ -116,7 +116,8 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ limconst chatServer = require('../chat/chat-server');const streamId = streamIdFor(b.target, userId);const name = String((b.supporter && b.supporter.name) || 'Someone').slice(0, 80);-- const amount = Math.max(0, Math.round(Number(i.amount) || 0));++ // null = the supporter hid the amount (Tips privacy.hide_amount): it stays null all the way to the chat line.++ const amount = i.amount == null ? null : Math.max(0, Math.round(Number(i.amount) || 0));const message = String(i.message || '').slice(0, 500);try {if (b.effect === 'chat_line' || b.effect === 'paid_message') {@@@ -128,7 +129,7 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ limtry { chatServer.broadcastGlobal({ ...event, global: true, channel_user_id: userId }); } catch { /* non-critical */ }const saved = db.saveChatMessage({stream_id: streamId, channel_user_id: userId, user_id: null, username: name,-- message: String(b.text || `${name} tipped ${amount.toLocaleString()} Vibes${message ? ': ' + message : ''}`).slice(0, 1000),++ message: String(b.text || `${name} ${amount == null ? 'sent a tip' : `tipped ${amount.toLocaleString()} Vibes`}${message ? ': ' + message : ''}`).slice(0, 1000),message_type: 'donation',metadata: { kind: 'donation', amount, message, username: name, source: 'tips', interaction_id: i.id || null, paid_message: event.paid_message, highlight_seconds: event.highlight_seconds, test: !!b.test },});@@@ -122,6 +122,14 @@ const token = (cap, aud = 'openvibe.live') => serviceAuth.signServiceToken({ issassert.strictEqual(tts[0][2], 'read me');assert.strictEqual(tts[0][6], 501);++ // A hidden amount (Tips privacy.hide_amount sends null) stays hidden: no "0 Vibes" anywhere.++ const h = await post({ ...job, text: undefined, interaction: { ...job.interaction, id: 'tint_h', amount: null, message: 'hi' } }, { key: 'tint_h:chat_line' });++ assert.strictEqual(h.status, 200);++ assert.strictEqual(broadcasts[broadcasts.length - 1].ev.amount, null);++ const hs = d.prepare('SELECT * FROM chat_messages WHERE id = ?').get(h.json.ref.chat_message_id);++ assert.strictEqual(hs.message, 'Viewer sent a tip: hi');++ assert.strictEqual(JSON.parse(hs.metadata).amount, null);++// Unknown creator: permanent refusal.const u = await post({ ...job, creator: { type: 'user', id: 'usr_01J0000000000000000000000Y' } }, { key: 'tint_2:chat_line' });assert.strictEqual(u.status, 404);
@@ -4119,24 +4119,26 @@ function addDonationMessage(msg) {
@@ -4119,24 +4119,26 @@ function addDonationMessage(msg) {
el.className = 'chat-msg donation';
el.className = 'chat-msg donation';
const donorName = esc(msg.username || msg.from || 'Anonymous');
const donorName = esc(msg.username || msg.from || 'Anonymous');
- const amount = Math.round(msg.amount || 0);
- const amtStr = amount.toLocaleString();
+ // A supporter can hide the amount (Tips privacy.hide_amount): the amount arrives as null and the line says "sent a tip".
+ const hidden = msg.amount == null;
+ const amount = hidden ? 0 : Math.round(msg.amount || 0);
+ const did = hidden ? 'sent a tip' : `donated <strong>${amount.toLocaleString()} Vibes</strong>`;
const rawDonText = msg.message || '';
const rawDonText = msg.message || '';
const text = rawDonText ? `: ${(typeof parseEmotes === 'function') ? parseEmotes(rawDonText) : esc(rawDonText)}` : '';
const text = rawDonText ? `: ${(typeof parseEmotes === 'function') ? parseEmotes(rawDonText) : esc(rawDonText)}` : '';
- el.innerHTML = `<i class="fa-solid fa-coins" style="color:var(--accent)"></i> <strong>${donorName}</strong> donated <strong>${amtStr} Vibes</strong>${text}`;
+ el.innerHTML = `<i class="fa-solid fa-coins" style="color:var(--accent)"></i> <strong>${donorName}</strong> ${did}${text}`;
container.appendChild(el);
container.appendChild(el);
scrollChat();
scrollChat();
if (_chatUserScrolledUp) _onNewChatMessageWhileScrolledUp();
if (_chatUserScrolledUp) _onNewChatMessageWhileScrolledUp();
queueFullscreenChatEntry({
queueFullscreenChatEntry({
kind: 'donation',
kind: 'donation',
- html: `<div class="fullscreen-chat-meta"><span class="fullscreen-chat-user" style="color:var(--accent)"><i class="fa-solid fa-coins"></i> ${donorName}</span></div><div class="fullscreen-chat-text">donated <strong>${amtStr} Vibes</strong>${text}</div>`,
+ html: `<div class="fullscreen-chat-meta"><span class="fullscreen-chat-user" style="color:var(--accent)"><i class="fa-solid fa-coins"></i> ${donorName}</span></div><div class="fullscreen-chat-text">${did}${text}</div>`,
});
});
// TTS for donations
// TTS for donations
if (document.getElementById('tts-checkbox')?.checked) {
if (document.getElementById('tts-checkbox')?.checked) {
- speakTTS(`${donorName} donated ${amount} Vibes. ${msg.message || ''}`);
+ speakTTS(`${donorName} ${hidden ? 'sent a tip' : `donated ${amount} Vibes`}. ${msg.message || ''}`);
}
}
}
}
@@ -116,7 +116,8 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ lim
@@ -116,7 +116,8 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ lim
const chatServer = require('../chat/chat-server');
const chatServer = require('../chat/chat-server');
const streamId = streamIdFor(b.target, userId);
const streamId = streamIdFor(b.target, userId);
const name = String((b.supporter && b.supporter.name) || 'Someone').slice(0, 80);
const name = String((b.supporter && b.supporter.name) || 'Someone').slice(0, 80);
- const amount = Math.max(0, Math.round(Number(i.amount) || 0));
+ // null = the supporter hid the amount (Tips privacy.hide_amount): it stays null all the way to the chat line.
+ const amount = i.amount == null ? null : Math.max(0, Math.round(Number(i.amount) || 0));
const message = String(i.message || '').slice(0, 500);
const message = String(i.message || '').slice(0, 500);
try {
try {
if (b.effect === 'chat_line' || b.effect === 'paid_message') {
if (b.effect === 'chat_line' || b.effect === 'paid_message') {
@@ -128,7 +129,7 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ lim
@@ -128,7 +129,7 @@ router.post('/deliveries', guard('live.tips_delivery.write'), express.json({ lim
try { chatServer.broadcastGlobal({ ...event, global: true, channel_user_id: userId }); } catch { /* non-critical */ }
try { chatServer.broadcastGlobal({ ...event, global: true, channel_user_id: userId }); } catch { /* non-critical */ }
const saved = db.saveChatMessage({
const saved = db.saveChatMessage({
stream_id: streamId, channel_user_id: userId, user_id: null, username: name,
stream_id: streamId, channel_user_id: userId, user_id: null, username: name,
- message: String(b.text || `${name} tipped ${amount.toLocaleString()} Vibes${message ? ': ' + message : ''}`).slice(0, 1000),
+ message: String(b.text || `${name} ${amount == null ? 'sent a tip' : `tipped ${amount.toLocaleString()} Vibes`}${message ? ': ' + message : ''}`).slice(0, 1000),
message_type: 'donation',
message_type: 'donation',
metadata: { kind: 'donation', amount, message, username: name, source: 'tips', interaction_id: i.id || null, paid_message: event.paid_message, highlight_seconds: event.highlight_seconds, test: !!b.test },
metadata: { kind: 'donation', amount, message, username: name, source: 'tips', interaction_id: i.id || null, paid_message: event.paid_message, highlight_seconds: event.highlight_seconds, test: !!b.test },
});
});
@@ -122,6 +122,14 @@ const token = (cap, aud = 'openvibe.live') => serviceAuth.signServiceToken({ iss
@@ -122,6 +122,14 @@ const token = (cap, aud = 'openvibe.live') => serviceAuth.signServiceToken({ iss
assert.strictEqual(tts[0][2], 'read me');
assert.strictEqual(tts[0][2], 'read me');
assert.strictEqual(tts[0][6], 501);
assert.strictEqual(tts[0][6], 501);
+ // A hidden amount (Tips privacy.hide_amount sends null) stays hidden: no "0 Vibes" anywhere.
+ const h = await post({ ...job, text: undefined, interaction: { ...job.interaction, id: 'tint_h', amount: null, message: 'hi' } }, { key: 'tint_h:chat_line' });
+ assert.strictEqual(h.status, 200);
+ assert.strictEqual(broadcasts[broadcasts.length - 1].ev.amount, null);
+ const hs = d.prepare('SELECT * FROM chat_messages WHERE id = ?').get(h.json.ref.chat_message_id);
+ assert.strictEqual(hs.message, 'Viewer sent a tip: hi');
+ assert.strictEqual(JSON.parse(hs.metadata).amount, null);
+
// Unknown creator: permanent refusal.
// Unknown creator: permanent refusal.
const u = await post({ ...job, creator: { type: 'user', id: 'usr_01J0000000000000000000000Y' } }, { key: 'tint_2:chat_line' });
const u = await post({ ...job, creator: { type: 'user', id: 'usr_01J0000000000000000000000Y' } }, { key: 'tint_2:chat_line' });
assert.strictEqual(u.status, 404);
assert.strictEqual(u.status, 404);