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.

Activity

  1. changed the title [-]Allow session.publish() / AuthorizedMessage.update() to set name, summary, and url[/-] [+]Allow `session.publish()`/`AuthorizedMessage.update()` to set name, summary, and url[/+] on Aug 23, 2026
  2. dahlia commented on Aug 23, 2026

    @dahlia
    Member

    Thanks for the detailed report. I agree that this is a gap in BotKit's public API. I'll implement option 1. BotKit will expose these three ActivityStreams properties directly rather than accept arbitrary constructor options, since BotKit still needs to control object identity, attribution, and addressing.

    The API will have this shape:

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

    Leaving url unset when publishing will retain the current default, which points to BotKit's message page. For updates, an omitted field will preserve the stored value, null will remove it, and any other value will replace it. Fedify's clone() does not clear these singular properties when given null, so the implementation will translate removal to empty names, summaries, or urls arrays.

    Activity Vocabulary defines summary as an HTML fragment, and its examples use paragraphless inline markup. I'll also add an inline tagged template that returns Text<"inline", TContextData>, making summaries composable without treating strings as trusted HTML:

    summary: inline`A simple ${em("note")}`,

    Literal text and string summaries will be escaped. The inline template will render line breaks as <br> and reject interpolated block Text values. Tags produced by a summary, including mentions and custom emoji, will be collected along with the content tags. Mentions will participate in addressing in the same way as mentions in content.

    When language is supplied at publication time, it will apply to name and summary as well as content, allowing the corresponding language maps to be emitted.

    One correction to the Mastodon description: current Mastodon converts Article and the other converted object types from name, summary, and url, falling back to id for the link. It does not use content in that path. This does not change the requested API, but it makes name and summary more important for this use case.

  3. self-assigned this
    on Aug 23, 2026
  4. added theissue type on Aug 23, 2026
  5. added this to the BotKit 0.6 milestone on Aug 23, 2026
  6. added a commit that references this issue on Aug 24, 2026
  7. dahlia commented on Aug 24, 2026

    @dahlia
    Member

    Implemented in #42 and will be shipped with the next minor release, BotKit 0.6.0. Meanwhile, you can try v0.6.0-dev.345+20d0a60b, a pre-release build.

  8. moreal commented on Aug 24, 2026

    @moreal
    ContributorAuthor

    @dahlia Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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