Changeset 667fb41 in Klonkt for src/services/ActivityPubService.js
- Timestamp:
- 07/01/2026 01:32:22 PM (2 months ago)
- Branches:
- main
- Children:
- c45c187
- Parents:
- bb3f67c
- File:
-
- 1 edited
-
src/services/ActivityPubService.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/ActivityPubService.js
rbb3f67c r667fb41 1543 1543 threadInboxes, // every ancestor author's inbox 1544 1544 localPostId: localTgt ? localTgt.post_id : '', // our post this belongs to (if any) 1545 poll: parsePoll(note), // a Question → its options/counts (else null) 1545 1546 preview: HtmlSanitizerService.toPlainText(note.content || '').slice(0, 240), 1546 1547 }; … … 2039 2040 } 2040 2041 2042 // Vote on ANY fediverse poll by URL (the interact page) — no timeline cache needed. Fetches 2043 // the Question fresh, validates the choice(s), and casts the Mastodon-standard ballot (a 2044 // Create(Note) with `name` + inReplyTo) straight to the poll's author. Used for polls you find 2045 // by URL, not just ones from accounts you follow (which go through voteOnPoll via /news). 2046 export async function voteOnRemotePoll(site, questionUrl, choices) { 2047 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 2048 if (!base || !site || !site.slug || !/^https?:\/\//i.test(String(questionUrl || ''))) return { error: 'config' }; 2049 const q = await fetchActor(questionUrl).catch(() => null); // AP GET (SSRF-guarded) 2050 if (!q || q.type !== 'Question' || !q.id) return { error: 'not_found' }; 2051 const poll = parsePoll(q); 2052 if (!poll) return { error: 'not_found' }; 2053 if (poll.closed) return { error: 'closed' }; 2054 const valid = new Set(poll.options.map((o) => o.name)); 2055 const picks = (Array.isArray(choices) ? choices : [choices]).map(String).filter((c) => valid.has(c)); 2056 if (!picks.length) return { error: 'invalid' }; 2057 const chosen = poll.multiple ? [...new Set(picks)] : [picks[0]]; 2058 const authorUri = actorUriOf(q.attributedTo); 2059 const author = authorUri ? await fetchActor(authorUri).catch(() => null) : null; 2060 const inbox = author && (author.inbox || (author.endpoints && author.endpoints.sharedInbox)); 2061 if (!inbox) return { error: 'unreachable' }; 2062 const me = actorId(base, site.slug); 2063 const keys = getOrCreateKeys(site.slug); 2064 for (const name of chosen) { 2065 const nid = `${me}/votes/${Date.now()}-${rid()}`; 2066 const note = { id: nid, type: 'Note', attributedTo: me, to: [authorUri], name, inReplyTo: q.id, published: new Date().toISOString() }; 2067 const create = { '@context': AP_CONTEXT, id: `${nid}/activity`, type: 'Create', actor: me, to: note.to, object: note }; 2068 deliverWithRetry(site.slug, inbox, create, `${me}#main-key`, keys.private_pem); 2069 } 2070 return { ok: true }; 2071 } 2072 2041 2073 export function isBlockedAny(actorUri) { 2042 2074 if (!actorUri) return false; … … 2093 2125 getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, setMyReaction, getMyReactions, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote, 2094 2126 listOutbox, deliverOutboxDelete, deliverOutboxUpdate, 2095 webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, sendInteraction, voteOnPoll, 2127 webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, sendInteraction, voteOnPoll, voteOnRemotePoll, 2096 2128 parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, 2097 2129 autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
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)