Changeset 30271e6 in Klonkt
- Timestamp:
- 06/25/2026 06:13:00 AM (3 months ago)
- Branches:
- main
- Children:
- 6c3d805
- Parents:
- 2923a95
- Location:
- src
- Files:
-
- 4 edited
-
routes/embed.js (modified) (1 diff)
-
routes/posts.js (modified) (1 diff)
-
services/ActivityPubService.js (modified) (3 diffs)
-
views/shell.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/embed.js
r2923a95 r30271e6 45 45 for (const m of post.content.matchAll(/\[\[album:([^\]]+)\]\]/g)) for (const r of db.prepare('SELECT id FROM audio_tracks WHERE site_id = ? AND album = ? ORDER BY position').all(site.id, m[1].trim())) add(r.id); 46 46 for (const m of post.content.matchAll(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g)) for (const r of db.prepare('SELECT track_id FROM playlist_tracks WHERE playlist_id = ? ORDER BY position').all(m[1])) add(r.track_id); 47 if (ids.length) { 48 const byId = new Map(tracks.map((t) => [t.id, t])); 49 const scoped = ids.map((id) => byId.get(id)).filter(Boolean); 50 if (scoped.length) tracks = scoped; 51 } 47 // Strictly scope to this post's tracks — do NOT fall back to all-site 48 // tracks (that showed unrelated songs for a link-only-track post). 49 const byId = new Map(tracks.map((t) => [t.id, t])); 50 tracks = ids.map((id) => byId.get(id)).filter(Boolean); 52 51 } 53 52 } catch { /* fall back to the full site player */ } -
src/routes/posts.js
r2923a95 r30271e6 848 848 canManageSite, 849 849 siteAvatar, 850 postHasPlayableAudio: ActivityPubService.hasPlayableAudio(post.content || '', site.id), 850 851 pageTitle: post.title + ' - ' + site.title, 851 852 socialDescr: post.excerpt || '', -
src/services/ActivityPubService.js
r2923a95 r30271e6 97 97 } 98 98 99 // Does a post's audio shortcodes reference at least one PLAYABLE (file-backed) 100 // track? Link-only tracks (external Spotify/YouTube, media_id NULL) don't count — 101 // they have no Klonkt-hosted audio to embed, so no player card / cover-suppression. 102 export function hasPlayableAudio(content, siteId) { 103 if (!content || !/\[\[(track|album|playlist):/i.test(content)) return false; 104 try { 105 for (const m of content.matchAll(/\[\[track:([A-Za-z0-9_-]+)\]\]/g)) { const r = db.prepare('SELECT media_id FROM audio_tracks WHERE id = ?').get(m[1]); if (r && r.media_id) return true; } 106 for (const m of content.matchAll(/\[\[album:([^\]]+)\]\]/g)) { if (db.prepare('SELECT 1 FROM audio_tracks WHERE site_id = ? AND album = ? AND media_id IS NOT NULL LIMIT 1').get(siteId, m[1].trim())) return true; } 107 for (const m of content.matchAll(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g)) { if (db.prepare('SELECT 1 FROM playlist_tracks pt JOIN audio_tracks t ON t.id = pt.track_id WHERE pt.playlist_id = ? AND t.media_id IS NOT NULL LIMIT 1').get(m[1])) return true; } 108 } catch { /* non-fatal */ } 109 return false; 110 } 111 99 112 // A single post as an AS2 Note (the object), and as a Create activity (for outbox/delivery). 100 113 export function buildNote(base, site, post) { … … 117 130 }; 118 131 const hadAudio = /\[\[(track|album|playlist):/i.test(post.content || ''); 132 const playable = hasPlayableAudio(post.content || '', site && site.id); 119 133 const urls = []; 120 // Audio posts suppress image attachments so Mastodon renders the player CARD 121 // (twitter:player) instead of the cover — on Mastodon a media attachment and a 122 // link/player card are mutually exclusive, and the inline player is the point. 123 if (post.cover_image_url && !hadAudio) urls.push(abs(post.cover_image_url)); 134 // Posts with PLAYABLE hosted audio suppress image attachments so Mastodon renders 135 // the player CARD (twitter:player) instead of the cover — media attachment and 136 // link/player card are mutually exclusive on Mastodon. Link-only audio (external) 137 // keeps its cover (no player card to show). 138 if (post.cover_image_url && !playable) urls.push(abs(post.cover_image_url)); 124 139 let body = post.content || ''; 125 if (! hadAudio) for (const m of body.matchAll(/<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi)) urls.push(abs(m[1]));140 if (!playable) for (const m of body.matchAll(/<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi)) urls.push(abs(m[1])); 126 141 body = body.replace(/<img\b[^>]*>/gi, ''); 127 142 // Audio shortcodes: do NOT federate the raw audio file — Klonkt deliberately … … 960 975 getNotifications, listBlocks, isBlockedAny, blockTarget, unblock, 961 976 deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker, 962 getReplyUris, markNotificationsSeen, countUnseenNotifications, 977 getReplyUris, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio, 963 978 }; -
src/views/shell.ejs
r2923a95 r30271e6 158 158 // inline player that streams via the gated /audio/stream (no downloadable file). 159 159 const _postAudio = !!(typeof post !== 'undefined' && post 160 && /\[\[(track|album|playlist):/i.test(post.content || post.content_html || '')160 && typeof postHasPlayableAudio !== 'undefined' && postHasPlayableAudio 161 161 && typeof premiumUnlocked !== 'undefined' && premiumUnlocked); 162 162 const _embedUrl = _postAudio
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)