Skip to content

Allow session.publish()/AuthorizedMessage.update() to set name, summary, and url #40

Description

@moreal

Written with Claude Code


Background

ActivityStreams 2.0 objects (which Note/Article/etc. extend) have several standard properties that BotKit currently never lets a bot set:

  • name — a title.
  • summary — a short teaser, distinct from content.
  • url — a human-facing permalink for the object, distinct from id. Per the spec, id must be a dereferenceable AS2 document on the publishing server, while url may point anywhere (e.g. an external site the object represents).

Mastodon treats Note as a first-class type (renders content inline) but converts everything else, including Article, "as best as possible": it uses content (or name if content is absent) as the status text and appends url to it. url is also documented as the value Mastodon uses for a status's permalink. So for any non-Note object — and for url on Note too — these three fields are the only way to control how Mastodon (and similarly-behaved implementations) render and link a bridged post.

Looking at the current implementation:

  • SessionImpl.publish() (session-impl.ts) builds the message with new cls({ ..., url: this.bot.instance.getMessageWebUrl(...) }). url is always the bot's own message page, with no way to override it. name and summary aren't passed to the constructor at all, regardless of cls.
  • SessionPublishOptions (session.ts) only exposes language, visibility, attachments, quoteTarget, and quotePolicy.
  • AuthorizedMessageImpl.update() (message-impl.ts) — the officially supported way to edit an already-published message — clones the stored object with contents, tags, tos, ccs, updated, and interactionPolicy. name, summary, and url are not part of the patch here either.
  • AuthorizedMessageUpdateOptions only exposes quotePolicy.
  • Fedify's Object base class (which Note/Article extend) already supports name, summary, and url in both its constructor and clone() — this is purely a gap in what BotKit exposes, not a Fedify limitation.

Motivation

Any bot that mirrors or bridges existing content — RSS/Atom bridges, blog cross-posters, link-sharing bots — needs to attach a title, a teaser, and a link back to the source when it isn't the canonical content. Right now that's impossible through the public API, and this isn't a hypothetical bot category: BotKit's own examples/rss-bot tutorial hits exactly this. Its pollFeed() never uses Article at all and publishes a plain Note with the title and link crammed into content as text:

await session.publish(
  text`${item.title ?? "(untitled)"}

${link(item.url ?? feed.url)}`,
);

That's a reasonable fallback given what's available today, but it means the item's own link never becomes the post's url (the permalink stays the bot's own message page), and there's no way for this tutorial to grow into publishing Articles with a real title/teaser instead of a two-line Note. The same shape shows up in RSS-to-Mastodon bridges outside the BotKit ecosystem too — e.g. RSS Parrot, Mastofeeder — which is the kind of bot this issue is about, independent of BotKit specifically.

As a workaround in our own bot, we (an RSS→ActivityPub bridge) publish the message, then immediately reach into Repository.updateMessage() ourselves to clone the stored Create's object with name/summary/url set, and hand-roll an Update activity to broadcast it to followers. This duplicates most of what AuthorizedMessage.update() already does internally — we only bypass it because it doesn't expose the fields we need.

Two concrete costs of the current gap:

  1. Extra round trip. Every post needs a Create followed immediately by an Update, doubling outgoing federation traffic, purely because the fields can't be set at publish time.
  2. Reimplemented internals. Bots are forced to depend on Repository.updateMessage() and hand-build Update activities instead of using the public AuthorizedMessage.update() API, because that API's option surface doesn't cover this case.

Related Issues / PRs

None found — searched name, summary, title, url, Article and publish across open and closed issues.

Proposed Solutions

💡 If you have a better approach, please comment. Below are the options currently under consideration.

Option 1: Add name/summary/url as explicit fields

Add three new optional fields, mirroring the AS2 property names directly:

interface SessionPublishOptions<TContextData> {
  // ...existing fields...
  readonly name?: string;
  readonly summary?: Text<"inline", TContextData> | string;
  readonly url?: URL; // defaults to the current behavior (bot's own message page) when omitted
}

interface AuthorizedMessageUpdateOptions {
  readonly quotePolicy?: QuotePolicyOption;
  readonly name?: string;
  readonly summary?: Text<"inline", TContextData> | string;
  readonly url?: URL;
}

session-impl.ts's publish() and message-impl.ts's update() would pass these straight into the new cls({...}) / object.clone({...}) calls they already make, the same way attachments or interactionPolicy are handled today.

await session.publish(text, {
  class: Article,
  name: "Original post title",
  summary: teaserHtml,
  url: new URL("https://original-blog.example/post-slug"),
});

Pros:

  • Discoverable and type-checked; matches how the rest of SessionPublishOptions is shaped.
  • No new concepts to document — each field maps 1:1 to a well-known AS2 property.
  • Small, additive, backward-compatible diff confined to two functions.

Cons:

  • Every future AS2/Fedify Object field a bot might want (e.g. attributedTo overrides, sensitive, tag beyond mentions) would need its own dedicated addition later.

Option 2: A generic object-field passthrough

Instead of naming fields one at a time, accept a partial patch of the underlying Fedify object's constructor options:

interface SessionPublishOptions<TContextData> {
  // ...existing fields...
  readonly objectOptions?: Partial<ConstructorParameters<typeof Note>[0]>;
}

which is merged into the object BotKit already builds.

Pros:

  • Future-proof: any AS2 field Fedify's Object supports becomes available without further BotKit API changes.
  • One escape hatch instead of a growing list of named options.

Cons:

  • Weaker type safety/discoverability — callers need to know Fedify's Object constructor shape, not just BotKit's docs.
  • Exposes internal object construction as public surface, which could make it easier to accidentally clobber fields BotKit itself relies on (id, attribution, tos/ccs, etc.) unless carefully guarded/filtered.
  • Couples the option's exact type to whichever Fedify version BotKit currently depends on.

Already Answered

Q: Why not just put the title/link in content instead?

For Note, that's already what bots typically do (see the examples/rss-bot snippet above), and it works well enough for the body text. The gap is specifically Article and other non-Note types, which Mastodon renders from content/name plus a separately-appended url — and separately, url also drives the status permalink for any object type. Putting the link only inside content still leaves the permalink pointing at the bot's own message page instead of the source, regardless of what either option above ends up looking like.

Q: Should url always point back to the bot's own page by default?

Worth discussing — this issue isn't proposing a specific default, just a way to override url at all. Per the AS2 spec, url is explicitly allowed to point elsewhere, which is how bridges, cross-posters, and read-more links are meant to work, so whatever the default ends up being, it should be overridable.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

Priority

None yet

Effort

None yet

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions