Captioned images support#249
Open
eebette wants to merge 9 commits into
Open
Conversation
Markdown image references in the body that point at http(s) URLs (``) now cause the bot to fetch the URL, upload to the homeserver's media repo, and emit a single `m.image` event whose `url` is the resulting `mxc://` URI. The remaining body — with all markdown image links stripped — becomes the caption (`body` and `formatted_body`). The `filename` field carries the URL's basename so that MSC4193-aware clients render the message as image-with-caption rather than treating `body` as the file name. Bodies without image links continue to send as `m.text` events unchanged. If the fetch or upload fails, the original body is sent as `m.text` and a warning is logged, so a flaky upstream CDN never drops the message. The previous inline `<img src="mxc://...">` approach in m.text formatted_body rendered correctly on Element Web but was a known no-op on Element X Android (missing feature, see element-hq/element-x-android#1874). MSC2530/MSC4193-style captioned m.image is now the standard way both clients render image-with-text in a single event. New module `matrix_webhook/media.py` exposes: - `upload_from_url(url)` — async fetch + media-repo upload, returns the mxc URI plus mimetype, size, and filename. - `captioned_image_or_text(body)` — returns an m.image event content dict if `body` has any markdown image references, else None. `handler.py` calls `captioned_image_or_text` between body parse and send; if it returns content, that is what is sent. Otherwise the existing m.text composition is used. The `formatted_body` escape hatch is unchanged. Tests cover four cases: image-with-caption, image-only-no-caption, no-image-falls-back-to-text, and failed-upload-falls-back-to-text. Tests use a stdlib threaded HTTPServer fixture to avoid event-loop deadlocks with the bot's async client. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5f42590 to
0ac346e
Compare
When `body` contains a markdown image reference whose URL is empty or
non-http (e.g. `![poster]()` produced by a templating engine like
Jellyseerr's `{{image}}` resolving to empty for events with no
associated media), the fallback m.text path would otherwise emit a
broken `<img>` tag. The new `strip_orphan_image_links` helper drops
those references before the markdown-to-HTML render. http(s) refs are
preserved so the upload-failure fallback path still surfaces the
attempted URL.
The helper only normalizes whitespace if it actually stripped
something — bodies without orphan refs (including formatter outputs
with intentional trailing newlines) pass through unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously `upload_from_url` only raised `ValueError` on HTTP-status failure; transport-level errors (DNS resolution failure, connection refused, TLS errors) propagated as `aiohttp.ClientError` subclasses straight to the request handler, crashing it with HTTP 500 and dropping the entire message — even the text-only fallback `captioned_image_or_text` was meant to provide. Wrap the fetch in a `try`/`except aiohttp.ClientError` that re-raises as `ValueError`. The existing `captioned_image_or_text` handler already catches `ValueError` and logs a warning + falls back to `m.text`, so any network-level failure now degrades the same way an HTTP 4xx/5xx already does. Adds an integration test against an unreachable hostname (`http://this-host-does-not-exist.invalid/...`) that confirms the request returns 200 with the text body intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removed detailed description of captioned image support from CHANGELOG.
Updated the section on captioned images to clarify how to send images with captions in messages.
for more information, see https://pre-commit.ci
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
When payload includes
image_url, fetches the URL, uploads to the homeserver media repo, and emits anm.imageevent withbodyas the caption. Falls back tom.textofbodyobject whenimage_urlis missing or download fails.Tests
tests/test_image.py: image-with-caption, noimage_url, emptyimage_url, 404 fallback, DNS-fail fallback. Test PNG generated inline; fixture server runs in a stdlib thread.