Changeset f2eacca in Klonkt for src/routes


Ignore:
Timestamp:
06/30/2026 01:50:00 AM (2 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
084d1c0
Parents:
80797d7
Message:

feat(music): per-track "share on the fediverse" — federate the file as a native AS2 Audio attachment

A per-track opt-in (default off) so an OPEN track's audio file is federated as a real AS2 Audio
attachment and served ungated → it plays inline in EVERY fediverse client, incl. the official
Mastodon apps (which only play native media, not external player cards). Gated tracks (default)
keep the file hidden + web-player-only. This is the spec-canonical way to federate audio; the
gated path stays the deliberate anti-steal choice.

  • src/config/database.js — audio_tracks.fedi_open column (default 0)
  • src/routes/audio.js — /audio/stream serves fedi_open tracks ungated so remote servers can fetch them
  • src/services/ActivityPubService.js (buildNote) — fedi_open tracks → AS2 Audio attachments (the file URL)
  • src/routes/admin-audio.js — POST /:id/fedi-open toggle (god-only) + fedi_open in the track query
  • src/views/pages/admin-audio.ejs — per-track share toggle next to the download toggle
  • src/services/i18n.js — aaud.fedi_on/off labels (nl/en/de)

Co-Authored-By: Claude <noreply@…>

Location:
src/routes
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-audio.js

    r80797d7 rf2eacca  
    9696  const rows = db.prepare(`
    9797    SELECT t.id, t.title, t.artist, t.album, t.duration, t.cover_url,
    98            t.position, t.created_at, t.downloadable, m.filename, m.size, m.mime_type
     98           t.position, t.created_at, t.downloadable, t.fedi_open, m.filename, m.size, m.mime_type
    9999    FROM audio_tracks t
    100100    LEFT JOIN media m ON m.id = t.media_id
     
    288288});
    289289
     290// Federate-the-file (fedi_open) per track on/off. When on, this track's audio file is shared
     291// as a real AS2 Audio attachment + served ungated → it plays inline in EVERY fediverse client
     292// (incl. the Mastodon apps), but the file is downloadable. Off (default) = gated, web-player only.
     293router.post('/:id/fedi-open', requireGod, (req, res) => {
     294  const site = res.locals.site;
     295  if (!site) return res.status(404).send('Site required');
     296  const row = db.prepare('SELECT fedi_open FROM audio_tracks WHERE id = ? AND site_id = ?').get(req.params.id, site.id);
     297  if (row) {
     298    db.prepare('UPDATE audio_tracks SET fedi_open = ? WHERE id = ? AND site_id = ?')
     299      .run(row.fedi_open ? 0 : 1, req.params.id, site.id);
     300  }
     301  res.redirect('/admin/audio');
     302});
     303
    290304router.post('/:id/delete', requireGod, (req, res) => {
    291305  const site = res.locals.site;
  • src/routes/audio.js

    r80797d7 rf2eacca  
    5454};
    5555
    56 // Access gate: allow only same-origin browser fetches / media loads.
    57 function isAllowedAudioRequest(req) {
     56// Access gate: same-origin browser fetches / media loads — PLUS fediverse-shared tracks.
     57function isAllowedAudioRequest(req, filename) {
    5858  if (req.get('X-Audio-Player') === '1') return true;  // our blob fetch
    5959  const site = req.get('Sec-Fetch-Site');              // set by modern browsers
    60   return site === 'same-origin' || site === 'same-site';
     60  if (site === 'same-origin' || site === 'same-site') return true;
     61  // fedi_open tracks are deliberately served ungated so remote servers (Mastodon, …) can
     62  // fetch + play the file inline. The operator opted this specific track in (per-track flag).
     63  if (filename) {
     64    try {
     65      const r = db.prepare(`SELECT 1 FROM audio_tracks t JOIN media m ON t.media_id = m.id
     66        WHERE t.fedi_open = 1 AND (m.storage_path = ? OR m.storage_path LIKE ?) LIMIT 1`).get(filename, '%' + filename);
     67      if (r) return true;
     68    } catch { /* ignore */ }
     69  }
     70  return false;
    6171}
    6272
     
    6474  const { filename } = req.params;
    6575
    66   if (!isAllowedAudioRequest(req)) {
     76  if (!isAllowedAudioRequest(req, filename)) {
    6777    return res.status(403).send('Direct access not allowed');
    6878  }
Note: See TracChangeset for help on using the changeset viewer.