commit be614b1e41fb9a9a2f28f7ef5eca71e329d0a699
Author: selfhoster1312 <selfhoster1312@kl.netlib.re>
Date: Thu Dec 11 11:38:08 2025 +0000
diff --git a/changelog.md b/changelog.md
index d725271..64a71e8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -186 +187 @@
- general
- matterbridge output now colors log level for easier log reading ([#25](https://github.com/matterbridge-org/matterbridge/pull/25))
+ - new HTTP helpers are common to all bridges, and allow overriding specific settings ([#59](https://github.com/matterbridge-org/matterbridge/pull/59))
- mastodon
- Add new Mastodon bridge ([#14](https://github.com/matterbridge-org/matterbridge/pull/14)/[#16](https://github.com/matterbridge-org/matterbridge/pull/16), thanks @lil5)
- Supports public messages and private messages
diff --git a/docs/development/protocol.md b/docs/development/protocol.md
index 167ae4e..566a85d 100644
--- a/docs/development/protocol.md
+++ b/docs/development/protocol.md
@@ -694 +69155 @@ Check if you:
**Sending message to the bridge don't work**
-- [ ] Channels must match. While sending the message to the bridge make sure that you set the `config.Message.Channel` field to channel as it is mentioned in the config file.
\ No newline at end of file
+- [ ] Channels must match. While sending the message to the bridge make sure that you set the `config.Message.Channel` field to channel as it is mentioned in the config file.
+
+### Handling HTTP requests
+
+> [!TIP]
+> If your protocol doesn't do HTTP requests at all, you do not have to read this section.
+
+Every matterbridge bridge instance as defined in the config has its own dedicated HTTP client initiated when the program starts. It is used by
+HTTP helpers (explained below) but may also be used directly as `b.HttpClient`.
+
+#### Custom HTTP client
+
+The HTTP client is initiated in the `NewHttpClient` method defined in [bridge/bridge.go](../../bridge/bridge.go), and can be overridden in your bridge class.
+
+For example, if your protocol `foo` requires custom settings, such as going through tor, you would do something like:
+
+```go
+func (b *Bfoo) NewHttpClient(http_proxy string) (*http.Client, error) {
+ // Create a new custom client here
+}
+```
+
+> [!WARNING]
+> Unless your customization requires to override the `http_proxy` passed as first argument to the constructor, don't forget to respect the defined proxy setting.
+
+#### Custom HTTP requests
+
+Every HTTP request emitted by your bridge is initiated in the `NewHttpRequest` method defined in [bridge/bridge.go](../../bridge/bridge.go), and can be overridden in your bridge class:
+
+```go
+func (b *Bfoo) NewHttpRequest(method, uri string, body io.Reader) (*http.Request, error) {
+ // Create a new custom request here
+}
+```
+
+This is useful for protocols which require setting custom HTTP headers, such as cookies or `Authorization` headers.
+
+> [!INFO]
+> This constructor is used by matterbridge's internal HTTP helpers, so by setting your custom headers in your bridge's
+> `NewHttpRequest` method, they will be respected when using the helpers.
+
+#### Downloading remote files
+
+If your bridge needs to download files over HTTP, you can use matterbridge's internal helpers.
+In the most common cases, you can use the two helpers `AddAvatarFromURL` (for user avatars) and `AddAttachmentFromURL`.
+
+> [!WARNING]
+> In all cases, it's very important to perform such HTTP operations in the background so you don't block
+> matterbridge on a response that may succeed or timeout.
+>
+> ```go
+> if hasAttachments(m) {
+> go func() {
+> err := handleAttachments(rmsg, m)
+> if err != nil {
+> b.Log.WithError(err).Errorf("Downloading attachment failed")
+> return
+> }
+> // Spreading the message (with the attachment) to other bridges takes
+> // place in the background goroutine
+> b.Remote <- rmsg
+> }()
+> // That entire message is being handled in the background, skip to the next message
+> continue
+> }
+> ```
+
+TODO: what happens when the filename is not set? can we guess it from the URL/content-type? should we error if
+ it's not explicit and cannot be inferred?
+TODO: how is the ID used? is this param used at all or can we safely remove it?
+TODO: should we take an optional hash to avoid useless requests for files we already have? since hash is already
+ calculated later in the helpers
+
+If you need to somehow inspect or treat the raw data bytes from a successful HTTP GET request before inserting it
+as an attachment to the received message, you may use the `HttpGetBytes` method, which wll only succeed if the
+returned HTTP status code is 200.
+
+If you need more custom logic, such as a custom HTTP verb or headers specific to this request, you may use
+the `HttpClient` field directly, along with the `NewHttpRequest` method:
+
+```go
+// If you willingly want to avoid `http_proxy` settings and/or your bridge's request constructor,
+// use http.NewRequest here.
+req, err := b.NewHttpRequest("GET", uri, "")
+if err != nil {
+ continue
+}
+
+// Customise the http request
+...
+
+// Send the request
+resp, err := b.HttpClient.Do(req)
+...
+```
+
+If your protocol can know in advance the size of the remote attachment, you can compare it with the maximum
+download size to avoid too big requests altogether:
+
+```go
+for _, attach := range m.Attachments {
+ if int64(attach.Size) > b.General.MediaDownloadSize {
+ // Ignore this specific attachment (file too big)
+ b.Log.Warnf("Attachment too big to download: %s has size %#v (MediaDownloadSize is %#v)", name, size, b.General.MediaDownloadSize)
+ continue
+ }
+ ...
+}
+```
+
+#### Uploading files to a remote server
+
+If you need to upload files to a web server, you can use the `HttpUpload` helper method. It's similar to the `HttpGetBytes` method, but takes
+two additional arguments:
+
+- `headers` (`map[string][string]`), because you may need to set a specific `Content-Type` or `Authorization` header to perform the upload
+- `ok_status` (`[]int`), because the remote server may have different success codes, eg. `200`/`201`, or even `302` for duplicate files
+
+> [!WARNING]
+> Just like with HTTP downloads, it's **very important** to perform upload operations in the background.
+
+### Handling file attachments
+
+Most protocols support sending files, such as images and other documents. How they are displayed, and how they are transferred changes
+in every case, but there's two main approaches:
+
+- in-band attachments (mumble): raw content bytes are sent within a protocol message
+- out-of-band attachments (XMPP/matrix/etc): content is uploaded to a different server, and a URL is passed along in the protocol
+
+For out-of-band attachments, see the HTTP upload/download section above.
+
+#### In-band attachments
+
+To receive raw bytes from in-band attachments, you can use the `AddAttachmentFromBytes` and `AddAvatarFromBytes` helper methods. They
+both expect that you provide a filename in advance.
+
+> [!NOTE]
+> All protocols currently support importing data bytes into matterbridge, but not all of them support sending raw
+> bytes to their own network. See issue [#50](https://github.com/matterbridge-org/matterbridge/issues/50) for a comparison table
+> and broader discussion about these limitations.
+
+TODO: what happens when no filename is set? Do we try to guess the mimetype to figure out an extension, and use the SHA hash as a basename?
+
+To send in-band attachments, you can use the `FileInfo.Data` field which contains the raw attachment bytes.
+
+#### Handling attachment errors
+
+In the upstream past, matterbridge produced a message with `msg.Event = config.EventFileFailureSize`. However, this was not really documented,
+especially how to handle mixed successful/errored attachments. It apparently was just discarded in `gateway/handlers.go` in the `handleMessage`
+function and not handled gracefully by specific bridges.
+
+At the moment, it is recommended to simply log errors from attachments, and proceed with further attachments ignoring failed ones.