openvibers/openvibe.live Commits

Home: AI moment frames load through Community's guarded address (broken images) (#652)

OP OpenVibers committed 6 days ago · 239f4ef631 · 2 files, +39 −3

Screenshots are private Media objects since the sharing work, so the moment frames saved in
home_hero_moments (https://openvibe.media/o/med_...) were broken images on the home page. They are
read as <community>/p/<slug>/screenshot; a frame without a paste is dropped, never drawn broken.

Changes

ddiff --git a/server/home/routes.js b/server/home/routes.js
iindex 8c4f5c8e..89c98db1 100644
---- a/server/home/routes.js
++++ b/server/home/routes.js
@@@ -66,8 +66,24 @@ async function _mediaContent() {
return _mediaPool;
}
++// Community's public address: a screenshot paste's bytes are served by its guarded /p/<slug>/screenshot.
++const COMMUNITY_PUBLIC_URL = String(process.env.OV_COMMUNITY_PUBLIC_URL || 'https://openvibe.community').replace(/\/+$/, '');
++const MEDIA_OBJECT_RE = /\/o\/med_[0-9A-HJKMNP-TV-Z]{26}(?:[/?#]|$)/;
++
++/**
++ * A moment frame or paste image that can actually load. Screenshots are private Media objects since the sharing work,
++ * so a stored https://openvibe.media/o/med_… is a 404 in a browser (2026-11-07: every AI moment frame was broken). A
++ * frame that came from a paste is read through Community's guarded /p/<slug>/screenshot; one without a paste to point
++ * at is dropped rather than drawn broken.
++ */
++function _loadableShot(url, pasteSlug) {
++ if (!url) return null;
++ if (!MEDIA_OBJECT_RE.test(String(url))) return url;
++ return pasteSlug && /^[A-Za-z0-9_-]{1,80}$/.test(String(pasteSlug)) ? `${COMMUNITY_PUBLIC_URL}/p/${encodeURIComponent(pasteSlug)}/screenshot` : null;
++}
++
function _pasteThumb(p) {
-- if (p.screenshot_url) return media.publicUrl(p.screenshot_url);
++ if (p.screenshot_url) return _loadableShot(media.publicUrl(p.screenshot_url), p.slug);
if (p.type === 'screenshot' && p.slug) return media.pasteScreenshotUrl(p.slug);
return null;
}
@@@ -103,7 +119,7 @@ async function heroMedia() {
for (const m of (hm.moments || [])) {
if (!m || !m.vodId) continue;
items.push({
-- kind: 'moment', title: m.title || 'AI Moment', thumbnail: m.thumbnail || null,
++ kind: 'moment', title: m.title || 'AI Moment', thumbnail: _loadableShot(m.thumbnail, m.pasteSlug),
href: videoUrl('vod', m.vodId, m.offset || 0), username: m.username,
});
}
@@@ -120,7 +136,8 @@ async function heroMoments() {
try {
const hm = JSON.parse(await db.getState('home_hero_moments') || '{}');
return (hm.moments || [])
-- .filter(m => m && m.vodId && m.thumbnail && require('./front-page-text').frontPageOk(m.title || ''))
++ .map(m => (m && m.vodId ? { ...m, thumbnail: _loadableShot(m.thumbnail, m.pasteSlug) } : null))
++ .filter(m => m && m.thumbnail && require('./front-page-text').frontPageOk(m.title || ''))
.map(m => ({ title: m.title || 'AI Moment', thumbnail: m.thumbnail, href: videoUrl('vod', m.vodId, m.offset || 0), username: m.username }));
} catch { return []; }
}
@@@ -628,3 +645,4 @@ module.exports.heroMoments = heroMoments;
// after a deploy). The timer is unref'd so it never holds the process open.
// A restore drill runs no timers (and its hero would only find Media and Network down).
if (!require('../drill').enabled) setTimeout(() => { buildHero(null).catch(() => {}); }, 15000).unref?.();
++module.exports._loadableShot = _loadableShot;
ddiff --git a/test/home-moment-thumbs.test.js b/test/home-moment-thumbs.test.js
nnew file mode 100644
iindex 00000000..5f6700f9
---- /dev/null
++++ b/test/home-moment-thumbs.test.js
@@@ -0,0 +1,18 @@
++'use strict';
++// A moment frame or paste image on the home page must be one a browser can load (server/home/routes.js
++// _loadableShot; 2026-11-07, the owner's phone: every AI moment and image paste was a broken image). Screenshots are
++// private Media objects, so a stored https://openvibe.media/o/med_… is read through Community's guarded
++// /p/<slug>/screenshot, and a frame without a paste to point at is dropped instead of drawn broken.
++// node --import ./test/helpers/pg-preload.mjs test/home-moment-thumbs.test.js
++const assert = require('assert');
++const { _loadableShot } = require('../server/home/routes');
++
++const OBJ = 'https://openvibe.media/o/med_01M6SM78JFYD26R13Q3Q3WDHDN';
++assert.strictEqual(_loadableShot(OBJ, 'wild-vibe-2564'), 'https://openvibe.community/p/wild-vibe-2564/screenshot', 'a private object becomes the guarded paste address');
++assert.strictEqual(_loadableShot(`${OBJ}?w=480`, 'wild-vibe-2564'), 'https://openvibe.community/p/wild-vibe-2564/screenshot', 'with a size query too');
++assert.strictEqual(_loadableShot(OBJ, null), null, 'no paste to point at: dropped, never drawn broken');
++assert.strictEqual(_loadableShot(OBJ, '../../admin'), null, 'a slug that is not a slug is never put in a URL');
++assert.strictEqual(_loadableShot('https://openvibe.media/t/clip-538-1.jpg', 'x'), 'https://openvibe.media/t/clip-538-1.jpg', 'a thumbnail URL is left alone');
++assert.strictEqual(_loadableShot('https://openvibe.community/p/a/screenshot', 'a'), 'https://openvibe.community/p/a/screenshot', 'an already guarded address is left alone');
++assert.strictEqual(_loadableShot(null, 'a'), null);
++console.log('home moment thumbs: ok');