openvibers/openvibe.live Commits

Discover: 'streamers to check out' ranks real recent activity (hours + sessions in 14 days, live first, idle days penalized), category only as a tiebreak

OP OpenVibers committed 16 Sept 2026 · 4edb7827bd · 1 file, +17 −4

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMxJm1dB8rdsaMSgNjVa82

Changes

ddiff --git a/server/home/routes.js b/server/home/routes.js
iindex f397dbc9..9f12ddc1 100644
---- a/server/home/routes.js
++++ b/server/home/routes.js
@@@ -379,13 +379,26 @@ router.get('/discover', optionalAuth, async (req, res) => {
try {
const cat = me ? (db.getChannelByUserId(me.id) || {}) : {};
const category = cat.ai_category || cat.category || null;
-- similar = (db.all(`SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color, MAX(s.started_at) AS last_live_at, COUNT(s.id) AS sessions,
++ // Who is actually active: ranked by hours + sessions in the window (followers only as a
++ // nudge, category only as a tiebreak), live people first. 14 days; widen to 30 only if
++ // that leaves fewer than 3 names — nobody wants "live 29d ago" as a recommendation.
++ const pick = (days) => (db.all(`SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color, MAX(s.started_at) AS last_live_at, COUNT(s.id) AS sessions,
++ ROUND(SUM(COALESCE(s.duration_seconds, CASE WHEN s.ended_at IS NOT NULL THEN (julianday(s.ended_at) - julianday(s.started_at)) * 86400 ELSE 0 END)) / 3600.0, 1) AS hours,
(SELECT COUNT(*) FROM follows f WHERE f.streamer_id = u.id) AS followers,
(SELECT COALESCE(ch.ai_category, ch.category) FROM channels ch WHERE ch.user_id = u.id) AS category
FROM streams s JOIN users u ON u.id = s.user_id
-- WHERE s.started_at >= datetime('now', '-30 days') AND COALESCE(u.is_banned, 0) = 0 AND u.id != COALESCE(?, -1)
-- GROUP BY u.id ORDER BY (category = ?) DESC, followers DESC, last_live_at DESC LIMIT 8`, [exclude, category]) || [])
-- .map(r => ({ ...r, live: live.some(l => l.user_id === r.id), same_category: !!(category && r.category === category) }));
++ WHERE s.started_at >= datetime('now', ?) AND COALESCE(u.is_banned, 0) = 0 AND u.id != COALESCE(?, -1)
++ GROUP BY u.id HAVING hours >= 0.25 OR sessions >= 2
++ ORDER BY last_live_at DESC LIMIT 40`, [`-${days} days`, exclude]) || []);
++ let rows = pick(14);
++ if (rows.length < 3) rows = pick(30);
++ const score = (r) => (live.some(l => l.user_id === r.id) ? 1000 : 0)
++ + Number(r.hours || 0) * 2 + Math.min(Number(r.sessions || 0), 10)
++ + Math.min(Number(r.followers || 0), 50) * 0.2
++ + (category && r.category === category ? 2 : 0)
++ - Math.max(0, (Date.now() - Date.parse(String(r.last_live_at).replace(' ', 'T') + 'Z')) / 86400000) * 0.6; // every idle day costs
++ similar = rows.map(r => ({ ...r, live: live.some(l => l.user_id === r.id), same_category: !!(category && r.category === category), score: score(r) }))
++ .sort((a, b) => b.score - a.score).slice(0, 8);
} catch { similar = []; }
const data = { channel: me ? { username: me.username, display_name: me.display_name || me.username } : null, live, clips, recaps, star, similar };
_discoverCache.set(uname, { at: Date.now(), data });