diff --git a/docs/PLUGIN_AUTHOR_GUIDE.md b/docs/PLUGIN_AUTHOR_GUIDE.md index 1cc749b..fb6c2cc 100644 --- a/docs/PLUGIN_AUTHOR_GUIDE.md +++ b/docs/PLUGIN_AUTHOR_GUIDE.md @@ -187,7 +187,7 @@ module.exports = definePlugin({ /* {actor, target: {url}} */ }, onFediverseQuote(event) { - /* {actor, target: {url}}; target is the locally authored quoted post */ + /* FediverseQuote; target is local, url is the remote quote post */ }, // Fediverse, inbound posts (with content) @@ -266,7 +266,23 @@ privately to whoever sent a message, pass `msg` (or `msg.clientId`) to ### Fediverse engagement and raw activity -`onFediverseFollow` receives `{actor}`. `onFediverseLike`, `onFediverseRepost`, and `onFediverseQuote` receive `{actor, target}`. `actor` uses the SDK's `FediverseActor` shape. `target` is `{url: string}`. For a quote, it identifies the locally authored post that the remote actor quoted. +`onFediverseFollow` receives `{actor}`. `onFediverseLike` and `onFediverseRepost` receive `{actor, target}`. `actor` uses the SDK's `FediverseActor` shape. `target` is `{url: string}`. + +`onFediverseQuote` receives a `FediverseQuote`. `target.url` identifies the locally authored post being quoted. `url` identifies the remote quote post. Content metadata is included when the requesting server embeds its quote `Note` in the `QuoteRequest`. Servers may send only the quote post IRI, so the content fields are optional. + +```ts +interface FediverseQuote { + actor: FediverseActor; + target: { url: string }; + content?: string; + contentText?: string; + url: string; + postedAt?: string; + inReplyTo?: string; + attachments?: { url: string; mediaType: string; alt?: string }[]; + language?: string; +} +``` `onFediverse` receives the verified inbound ActivityPub activity after the host validates the HTTP signature and actor origin. In JavaScript, the payload is the raw JSON object. It fires in addition to a specialized handler when one applies. It also fires for verified activity types that have no specialized handler. diff --git a/docs/WIRE_PROTOCOL.md b/docs/WIRE_PROTOCOL.md index f9ba5eb..d0dd9de 100644 --- a/docs/WIRE_PROTOCOL.md +++ b/docs/WIRE_PROTOCOL.md @@ -377,9 +377,10 @@ not external HTTP webhooks. `fediverse.activity` carries the verified inbound activity as a raw JSON object. It fires alongside a matching specialized event and also covers verified activity types without a specialized event. `fediverse.quote` carries -`{actor, target}`, where `target` is the quoted local post. Mentions and replies -are verified public `Create(Note)` activities tied to the local account or a -locally authored post. +a `FediverseQuote`. Its `target` is the quoted local post and its `url` is the +remote quote post. Content fields are present when the `QuoteRequest` embeds +the quote `Note`. Mentions and replies are verified public `Create(Note)` +activities tied to the local account or a locally authored post. ### ambient (no permission) @@ -919,6 +920,18 @@ type FediverseInboundPost = { language?: string; }; +type FediverseQuote = { + actor: FediverseActor; + target: FediverseTarget; + content?: string; + contentText?: string; + url: string; + postedAt?: string; + inReplyTo?: string; + attachments?: FediverseAttachment[]; + language?: string; +}; + type BrowserPushPayload = { title: string; body?: string; @@ -934,9 +947,9 @@ type FediversePayload = { ``` `fediverse.activity` carries the verified ActivityPub object as an unrestricted -`JSONValue`. Follow carries `FediverseEngagement` without `target`. Like, -repost, and quote carry it with `target`. Mention and reply carry -`FediverseInboundPost`. +`JSONValue`. Follow carries `FediverseEngagement` without `target`. Like and +repost carry it with `target`. Quote carries `FediverseQuote`. Mention and +reply carry `FediverseInboundPost`. ### Storage and remaining host results diff --git a/examples/js/engagement-bot/__tests__/mod.test.json b/examples/js/engagement-bot/__tests__/mod.test.json index c5ca426..bc10fd7 100644 --- a/examples/js/engagement-bot/__tests__/mod.test.json +++ b/examples/js/engagement-bot/__tests__/mod.test.json @@ -144,13 +144,15 @@ }, "target": { "url": "https://demo.owncast.example/@demo/102" - } + }, + "contentText": "Great stream", + "url": "https://social.example/@dana/quotes/102" } } ], "expect": { "discordPosts": [ - "quote from @dana@social.example: https://demo.owncast.example/@demo/102" + "quote from @dana@social.example: Great stream\nhttps://social.example/@dana/quotes/102" ] } }, diff --git a/examples/js/engagement-bot/src/plugin.js b/examples/js/engagement-bot/src/plugin.js index 517bb97..2bfb7ab 100644 --- a/examples/js/engagement-bot/src/plugin.js +++ b/examples/js/engagement-bot/src/plugin.js @@ -46,8 +46,9 @@ module.exports = definePlugin({ }, onFediverseQuote(event) { + const caption = event.contentText || "quoted your post"; owncast.notifications.discord( - `quote from ${event.actor.handle}: ${event.target.url}`, + `quote from ${event.actor.handle}: ${caption}\n${event.url}`, ); }, diff --git a/examples/python/engagement-bot/__tests__/mod.test.json b/examples/python/engagement-bot/__tests__/mod.test.json index c5ca426..bc10fd7 100644 --- a/examples/python/engagement-bot/__tests__/mod.test.json +++ b/examples/python/engagement-bot/__tests__/mod.test.json @@ -144,13 +144,15 @@ }, "target": { "url": "https://demo.owncast.example/@demo/102" - } + }, + "contentText": "Great stream", + "url": "https://social.example/@dana/quotes/102" } } ], "expect": { "discordPosts": [ - "quote from @dana@social.example: https://demo.owncast.example/@demo/102" + "quote from @dana@social.example: Great stream\nhttps://social.example/@dana/quotes/102" ] } }, diff --git a/examples/python/engagement-bot/src/plugin.py b/examples/python/engagement-bot/src/plugin.py index 349a2ce..161edb0 100644 --- a/examples/python/engagement-bot/src/plugin.py +++ b/examples/python/engagement-bot/src/plugin.py @@ -51,8 +51,9 @@ def on_fediverse_repost(event): @plugin.on_fediverse_quote def on_fediverse_quote(event): + caption = event.content_text or "quoted your post" owncast.notifications.discord( - f"quote from {event.actor.handle}: {event.target.url}" + f"quote from {event.actor.handle}: {caption}\n{event.url}" ) diff --git a/sdks/js/index.d.ts b/sdks/js/index.d.ts index d6ca825..26279a6 100644 --- a/sdks/js/index.d.ts +++ b/sdks/js/index.d.ts @@ -147,6 +147,23 @@ export interface FediverseTargetedEngagement extends FediverseEngagement { target: { url: string }; } +/** An accepted quote request. `target` identifies the local post being quoted, + * while `url` identifies the remote quote post. Content metadata is present + * when the requesting server embeds the quote Note in its request. */ +export interface FediverseQuote extends FediverseTargetedEngagement { + content?: string; // HTML from the source instance + contentText?: string; // HTML stripped to plain text + url: string; // permalink to the remote quote post + postedAt?: string; // ISO-8601 + inReplyTo?: string; + attachments?: { + url: string; + mediaType: string; + alt?: string; + }[]; + language?: string; +} + /** Inbound fediverse post, a mention or reply that contains content the * plugin can act on. Carries both the rendered content (which has the * source instance's HTML) and a plain-text version (HTML stripped). */ @@ -446,8 +463,8 @@ export interface PluginDef { onFediverseLike?(event: FediverseTargetedEngagement): void | Promise; /** Someone on the fediverse boosted (reposted) a streamer post. Requires `fediverse.inbound`. */ onFediverseRepost?(event: FediverseTargetedEngagement): void | Promise; - /** Someone on the fediverse quoted a locally authored post. `target.url` identifies that quoted post. Requires `fediverse.inbound`. */ - onFediverseQuote?(event: FediverseTargetedEngagement): void | Promise; + /** Someone on the fediverse quoted a locally authored post. `target.url` identifies the local post and `url` identifies the remote quote post. Requires `fediverse.inbound`. */ + onFediverseQuote?(event: FediverseQuote): void | Promise; /** Someone @-mentioned the streamer in a public post. Requires `fediverse.inbound`. */ onFediverseMention?(post: FediverseInboundPost): void | Promise; /** Someone replied to one of the streamer's federated posts. Requires `fediverse.inbound`. */