Ignore:
Timestamp:
06/26/2026 07:47:20 AM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
ce0bedf
Parents:
283f618
Message:

feat(fediverse): auto-boost a followed account ('feature an artist')

Phase 1 of the Cirkels-on-AP rework. A followed account can be marked auto-boost:
their new top-level posts are automatically re-Announced (boosted) to your own
followers. Toggle in the /tijdlijn following list + a checkbox on the follow box.
ap_following.auto_boost column (migrated), setAutoBoost(), hook in handleInbox's
timeline loop, POST /tijdlijn/autoboost. nl/en/de.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r283f618 rf278df9  
    642642    // Home timeline (client): a top-level post from an account we follow.
    643643    if (actorUri && !isLocalActor && !o.inReplyTo && o.id) {
    644       let subs = []; try { subs = db.prepare('SELECT slug FROM ap_following WHERE actor_uri = ?').all(actorUri); } catch { /* table may not exist yet */ }
     644      let subs = []; try { subs = db.prepare('SELECT slug, auto_boost FROM ap_following WHERE actor_uri = ?').all(actorUri); } catch { /* table may not exist yet */ }
    645645      if (subs.length) {
    646646        const ai = actorInfo(await resolveActor(actorUri), actorUri);
    647647        const html = HtmlSanitizerService.sanitize(o.content || '');
    648648        const media = JSON.stringify((Array.isArray(o.attachment) ? o.attachment : []).map((a) => ({ url: safeUrl(a && a.url), type: (a && a.mediaType) || '' })).filter((m) => m.url));
    649         for (const s of subs) tlStmts().ins.run(o.id, s.slug, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.url || null, o.published || null, media);
     649        for (const s of subs) {
     650          tlStmts().ins.run(o.id, s.slug, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.url || null, o.published || null, media);
     651          // "Feature an artist": auto-boost (re-Announce) their new posts to our own followers.
     652          if (s.auto_boost) sendInteraction({ slug: s.slug }, 'boost', o.id, actorUri).catch(() => { /* best-effort */ });
     653        }
    650654        console.log('[AP] timeline +', actorUri, 'x' + subs.length);
    651655      }
     
    9971001}
    9981002
    999 let _insFw, _delFw, _listFw, _accFw, _oneFw;
     1003let _insFw, _delFw, _listFw, _accFw, _oneFw, _setAB;
    10001004function fwStmts() {
    10011005  if (!_insFw) {
    1002     _insFw = db.prepare('INSERT OR REPLACE INTO ap_following (slug, actor_uri, handle, name, icon, url, inbox, follow_id, status, created_at) VALUES (?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     1006    _insFw = db.prepare('INSERT OR REPLACE INTO ap_following (slug, actor_uri, handle, name, icon, url, inbox, follow_id, status, auto_boost, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
    10031007    _delFw = db.prepare('DELETE FROM ap_following WHERE slug = ? AND actor_uri = ?');
    10041008    _listFw = db.prepare('SELECT * FROM ap_following WHERE slug = ? ORDER BY created_at DESC');
    10051009    _accFw = db.prepare("UPDATE ap_following SET status = 'accepted' WHERE follow_id = ?");
    10061010    _oneFw = db.prepare('SELECT * FROM ap_following WHERE slug = ? AND actor_uri = ?');
    1007   }
    1008   return { ins: _insFw, del: _delFw, list: _listFw, acc: _accFw, one: _oneFw };
     1011    _setAB = db.prepare('UPDATE ap_following SET auto_boost = ? WHERE slug = ? AND actor_uri = ?');
     1012  }
     1013  return { ins: _insFw, del: _delFw, list: _listFw, acc: _accFw, one: _oneFw, setAB: _setAB };
    10091014}
    10101015export function listFollowing(slug) { return fwStmts().list.all(slug); }
     1016
     1017// Toggle auto-boost ("feature") on an account we already follow.
     1018export function setAutoBoost(slug, actorUri, on) {
     1019  try { fwStmts().setAB.run(on ? 1 : 0, slug, actorUri); } catch { /* ignore */ }
     1020  return { ok: true };
     1021}
    10111022
    10121023let _insTl, _listTl, _delTl;
     
    10221033
    10231034// Follow a fediverse account by @handle (WebFinger → actor → signed Follow).
    1024 export async function followActor(site, handle) {
     1035export async function followActor(site, handle, autoBoost = false) {
    10251036  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
    10261037  if (!base || !site || !site.slug) return { error: 'config' };
     
    10361047  const keys = getOrCreateKeys(site.slug);
    10371048  const followId = `${me}#follow-${Date.now()}-${rid()}`;
    1038   fwStmts().ins.run(site.slug, actor.id, ai.handle, ai.name, ai.icon, ai.url, actor.inbox, followId, 'pending');
     1049  fwStmts().ins.run(site.slug, actor.id, ai.handle, ai.name, ai.icon, ai.url, actor.inbox, followId, 'pending', autoBoost ? 1 : 0);
    10391050  const follow = { '@context': 'https://www.w3.org/ns/activitystreams', id: followId, type: 'Follow', actor: me, object: actor.id };
    10401051  try { await deliver(actor.inbox, follow, `${me}#main-key`, keys.private_pem); }
     
    11781189  getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
    11791190  listOutbox, deliverOutboxDelete,
    1180   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, getTimeline, sendInteraction,
     1191  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction,
    11811192  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    11821193  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
Note: See TracChangeset for help on using the changeset viewer.