Changeset 8ad1784 in Klonkt


Ignore:
Timestamp:
06/25/2026 09:08:20 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
7c62efb
Parents:
4c12783
Message:

feat(ap): authorize_interaction handles actor URIs as a Follow

The fediverse interaction page only did reply/like — it always tried to
resolve the URI as a post. A profile/actor URL fell through to the reply
composer, so 'following' produced a reply. Now: if the URI isn't a post,
resolve it as an actor and show a Follow action (followActor, which now
accepts a profile URL as well as an @handle). Adds fedi.follow_* i18n (nl/en/de).

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

Location:
src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r4c12783 r8ad1784  
    485485  const uri = (req.query.uri || '').toString();
    486486  const sent = !!req.query.sent;
    487   let target = null;
    488   if (!sent) { try { target = await ActivityPubService.resolveRemoteNote(uri); } catch { /* ignore */ } }
     487  const followed = !!req.query.followed;
     488  let target = null, followTarget = null;
     489  if (!sent && !followed && uri) {
     490    try { target = await ActivityPubService.resolveRemoteNote(uri); } catch { /* ignore */ }
     491    // Not a post? Maybe the URI is a profile/actor → offer Follow, not reply.
     492    if (!target) { try { followTarget = await ActivityPubService.resolveRemoteActor(uri); } catch { /* ignore */ } }
     493  }
    489494  renderPage(req, res, 'pages/authorize-interaction', {
    490495    pageTitle: 'Interacteer via de fediverse',
     
    492497    uri,
    493498    target,
     499    followTarget,
    494500    sent,
     501    followed,
    495502    liked: !!req.query.liked,
    496503    siteTitle: site ? site.title : '',
     
    508515  }
    509516  res.redirect('/authorize_interaction?liked=1&uri=' + encodeURIComponent(uri));
     517});
     518
     519// Follow a remote actor from your own site (when the target is a profile, not a post).
     520router.post('/authorize_interaction/follow', requireSiteManager, (req, res) => {
     521  const site = res.locals.site;
     522  const uri = (req.body.uri || '').toString();
     523  if (site && uri) {
     524    ActivityPubService.followActor(site, uri)
     525      .catch((e) => console.warn('[AP] remote follow failed:', e.message));
     526  }
     527  res.redirect('/authorize_interaction?followed=1&uri=' + encodeURIComponent(uri));
    510528});
    511529
  • src/services/ActivityPubService.js

    r4c12783 r8ad1784  
    10251025  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
    10261026  if (!base || !site || !site.slug) return { error: 'config' };
    1027   const actorUrl = await webfingerResolve(handle);
     1027  // Accept either an @user@host handle (WebFinger) or a profile/actor URL directly
     1028  // (the authorize_interaction Follow flow passes a URL).
     1029  const s = String(handle || '').trim();
     1030  const actorUrl = /^https?:\/\//i.test(s) ? (safeUrl(s) || null) : await webfingerResolve(s);
    10281031  if (!actorUrl) return { error: 'not_found' };
    10291032  const actor = await fetchActor(actorUrl).catch(() => null);
     
    10391042  console.log('[AP] follow', site.slug, '→', actor.id);
    10401043  return { ok: true, name: ai.name, handle: ai.handle };
     1044}
     1045
     1046// Resolve a profile URL or @handle to a followable remote actor (for the
     1047// authorize_interaction "Follow" flow). Returns display fields + inbox, or null
     1048// when it isn't a reachable actor (e.g. the input was a post, not a profile).
     1049export async function resolveRemoteActor(input) {
     1050  const s = String(input || '').trim();
     1051  const actorUrl = /^https?:\/\//i.test(s) ? (safeUrl(s) || null) : await webfingerResolve(s);
     1052  if (!actorUrl) return null;
     1053  const actor = await fetchActor(actorUrl).catch(() => null);
     1054  if (!actor || !actor.id || !actor.inbox) return null;
     1055  const ai = actorInfo(actor, actor.id);
     1056  return { actor_uri: actor.id, actor_name: ai.name, actor_handle: ai.handle, actor_url: ai.url, actor_icon: ai.icon, inbox: actor.inbox };
    10411057}
    10421058
     
    11621178  getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
    11631179  listOutbox, deliverOutboxDelete,
    1164   webfingerResolve, followActor, unfollowActor, listFollowing, getTimeline, sendInteraction,
     1180  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, getTimeline, sendInteraction,
    11651181  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    11661182  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
  • src/services/i18n.js

    r4c12783 r8ad1784  
    109109    'fedi.heading': 'Vanuit de fediverse', 'fedi.likes': 'sterren', 'fedi.boosts': 'boosts', 'fedi.replies': 'Reacties uit de fediverse',
    110110    'fedi.reply': 'Reageer', 'fedi.reply_ph': 'Je antwoord aan de fediverse…', 'fedi.send': 'Versturen', 'fedi.you': 'Jij',
    111     'fedi.remote_title': 'Reageer via de fediverse', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres (bv. @jij@mastodon.social):', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is onderweg naar de fediverse en verschijnt zo bij de ontvanger.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'tl.title': 'Tijdlijn', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.pending': 'in afwachting', 'tl.feed': 'Berichten', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →',
     111    'fedi.remote_title': 'Reageer via de fediverse', 'fedi.follow_heading': 'Volgen via de fediverse', 'fedi.follow_intro': 'Je staat op het punt te volgen:', 'fedi.follow_btn': 'Volgen', 'fedi.followed_title': 'Volgverzoek verstuurd ✅', 'fedi.followed_done': 'Je volgverzoek is onderweg. Zodra de andere kant het accepteert, verschijnen hun berichten in je tijdlijn.', 'fedi.view_profile': 'Bekijk profiel →', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres (bv. @jij@mastodon.social):', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is onderweg naar de fediverse en verschijnt zo bij de ontvanger.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'tl.title': 'Tijdlijn', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.pending': 'in afwachting', 'tl.feed': 'Berichten', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →',
    112112    'comments.to_start': 'om de conversatie te starten.',
    113113    'comments.reply': 'Reageer', 'comments.delete': 'Verwijder', 'comments.cancel': 'Annuleren',
     
    11181118    'fedi.heading': 'From the fediverse', 'fedi.likes': 'favourites', 'fedi.boosts': 'boosts', 'fedi.replies': 'Replies from the fediverse',
    11191119    'fedi.reply': 'Reply', 'fedi.reply_ph': 'Your reply to the fediverse…', 'fedi.send': 'Send', 'fedi.you': 'You',
    1120     'fedi.remote_title': 'Reply via the fediverse', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address (e.g. @you@mastodon.social):', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply is on its way to the fediverse and will appear for the recipient shortly.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'tl.title': 'Timeline', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.pending': 'pending', 'tl.feed': 'Posts', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →',
     1120    'fedi.remote_title': 'Reply via the fediverse', 'fedi.follow_heading': 'Follow via the fediverse', 'fedi.follow_intro': 'You are about to follow:', 'fedi.follow_btn': 'Follow', 'fedi.followed_title': 'Follow request sent ✅', 'fedi.followed_done': 'Your follow request is on its way. Once accepted, their posts show up in your timeline.', 'fedi.view_profile': 'View profile →', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address (e.g. @you@mastodon.social):', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply is on its way to the fediverse and will appear for the recipient shortly.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'tl.title': 'Timeline', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.pending': 'pending', 'tl.feed': 'Posts', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →',
    11211121    'comments.to_start': 'to start the conversation.',
    11221122    'comments.reply': 'Reply', 'comments.delete': 'Delete', 'comments.cancel': 'Cancel',
     
    21252125    'fedi.heading': 'Aus dem Fediverse', 'fedi.likes': 'Favoriten', 'fedi.boosts': 'Boosts', 'fedi.replies': 'Antworten aus dem Fediverse',
    21262126    'fedi.reply': 'Antworten', 'fedi.reply_ph': 'Deine Antwort an das Fediverse…', 'fedi.send': 'Senden', 'fedi.you': 'Du',
    2127     'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse (z.B. @du@mastodon.social):', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort ist auf dem Weg ins Fediverse und erscheint gleich beim Empfänger.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'tl.title': 'Timeline', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.pending': 'ausstehend', 'tl.feed': 'Beiträge', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →',
     2127    'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.follow_heading': 'Über das Fediverse folgen', 'fedi.follow_intro': 'Du folgst gleich:', 'fedi.follow_btn': 'Folgen', 'fedi.followed_title': 'Folge-Anfrage gesendet ✅', 'fedi.followed_done': 'Deine Folge-Anfrage ist unterwegs. Sobald sie akzeptiert wird, erscheinen ihre Beiträge in deiner Timeline.', 'fedi.view_profile': 'Profil ansehen →', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse (z.B. @du@mastodon.social):', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort ist auf dem Weg ins Fediverse und erscheint gleich beim Empfänger.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'tl.title': 'Timeline', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.pending': 'ausstehend', 'tl.feed': 'Beiträge', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →',
    21282128    'comments.to_start': 'um das Gespräch zu starten.',
    21292129    'comments.reply': 'Antworten', 'comments.delete': 'Löschen', 'comments.cancel': 'Abbrechen',
  • src/views/pages/authorize-interaction.ejs

    r4c12783 r8ad1784  
    2727      <a class="btn btn-primary" href="/"><%= t('fedi.remote_back') %></a>
    2828    </p>
     29  <% } else if (typeof followed !== 'undefined' && followed) { %>
     30    <h1 class="auth-interact-title"><%= t('fedi.followed_title') %></h1>
     31    <p class="auth-interact-note"><%= t('fedi.followed_done') %></p>
     32    <p class="auth-interact-actions">
     33      <% if (uri) { %><a class="btn" href="<%= uri %>" rel="nofollow noopener"><%= t('fedi.view_profile') %></a><% } %>
     34      <a class="btn btn-primary" href="/"><%= t('fedi.remote_back') %></a>
     35    </p>
    2936  <% } else if (typeof sent !== 'undefined' && sent) { %>
    3037    <h1 class="auth-interact-title"><%= t('fedi.remote_sent_title') %></h1>
     
    3441      <a class="btn btn-primary" href="/"><%= t('fedi.remote_back') %></a>
    3542    </p>
     43  <% } else if (typeof followTarget !== 'undefined' && followTarget) { %>
     44  <h1 class="auth-interact-title"><%= t('fedi.follow_heading') %></h1>
     45  <p class="auth-interact-meta">
     46    <%= t('fedi.follow_intro') %>
     47    <a href="<%= followTarget.actor_url %>" rel="nofollow noopener" target="_blank"><strong><%= followTarget.actor_name %></strong></a>
     48    <span class="fedi-handle"><%= followTarget.actor_handle %></span>
     49  </p>
     50  <form method="post" action="/authorize_interaction/follow" class="auth-interact-form">
     51    <input type="hidden" name="uri" value="<%= followTarget.actor_uri %>">
     52    <div class="auth-interact-actions">
     53      <button type="submit" class="btn btn-primary"><%= t('fedi.follow_btn') %></button>
     54      <a href="/" class="btn"><%= t('comments.cancel') %></a>
     55    </div>
     56  </form>
     57  <p class="auth-interact-note"><%= t('fedi.remote_as', { site: siteTitle }) %></p>
    3658  <% } else { %>
    3759  <h1 class="auth-interact-title"><%= t('fedi.remote_title') %></h1>
Note: See TracChangeset for help on using the changeset viewer.