diff --git a/bridge/config/config.go b/bridge/config/config.go
index 08579fbac..dc4fed1df 100644
--- a/bridge/config/config.go
+++ b/bridge/config/config.go
@@ -223,6 +223,7 @@ type Protocol struct {
UseFirstName bool // telegram
UseUserName bool // discord, matrix, mattermost
UseInsecureURL bool // telegram
+ UseMSC4144 bool // matrix
UserName string // IRC
UseRelayFallback bool // IRC, controls whether RelayFallbackNick is used, defaults to true
UseRelayMsg bool // IRC
diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go
index 45fd0ee24..4d28d5475 100644
--- a/bridge/matrix/matrix.go
+++ b/bridge/matrix/matrix.go
@@ -9,6 +9,7 @@ import (
"io"
"mime"
"net/http"
+ "net/url"
"path"
"regexp"
"slices"
@@ -223,11 +224,32 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
// Make a action /me of the message
if msg.Event == config.EventUserAction {
- content := event.MessageEventContent{
- MsgType: event.MsgEmote,
- Body: body,
- FormattedBody: formattedBody,
- Format: event.FormatHTML,
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ body, _ = strings.CutPrefix(body, username.plain)
+ body = username.plain + ": " + body
+ formattedBody, _ = strings.CutPrefix(formattedBody, username.formatted)
+ formattedBody = "" + username.formatted + ": " + formattedBody
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ MsgType: event.MsgEmote,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgEmote,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ }
}
if b.GetBool("HTMLDisable") {
@@ -294,21 +316,58 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
// Edit message if we have an ID
if msg.ID != "" {
- content := event.MessageEventContent{
- Body: "* " + body,
- FormattedBody: "* " + formattedBody,
- MsgType: event.MsgText,
- Format: event.FormatHTML,
- NewContent: &event.MessageEventContent{
- Body: body,
- FormattedBody: formattedBody,
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ body, _ = strings.CutPrefix(body, username.plain)
+ body = username.plain + ": " + body
+ formattedBody, _ = strings.CutPrefix(formattedBody, username.formatted)
+ formattedBody = "" + username.formatted + ": " + formattedBody
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ Body: "* " + body,
+ FormattedBody: "* " + formattedBody,
+ MsgType: event.MsgText,
Format: event.FormatHTML,
+ NewContent: &event.MessageEventContent{
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ MsgType: event.MsgText,
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ },
+ RelatesTo: &event.RelatesTo{
+ EventID: id.EventID(msg.ID),
+ Type: event.RelReplace,
+ },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ }
+ } else {
+ content = event.MessageEventContent{
+ Body: "* " + body,
+ FormattedBody: "* " + formattedBody,
MsgType: event.MsgText,
- },
- RelatesTo: &event.RelatesTo{
- EventID: id.EventID(msg.ID),
- Type: event.RelReplace,
- },
+ Format: event.FormatHTML,
+ NewContent: &event.MessageEventContent{
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ MsgType: event.MsgText,
+ },
+ RelatesTo: &event.RelatesTo{
+ EventID: id.EventID(msg.ID),
+ Type: event.RelReplace,
+ },
+ }
}
if b.GetBool("HTMLDisable") {
@@ -365,17 +424,45 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
// Reply to parent if message has a parent id
if msg.ParentValid() {
- content := event.MessageEventContent{
- MsgType: event.MsgText,
- Body: body,
- FormattedBody: formattedBody,
- Format: event.FormatHTML,
- RelatesTo: &event.RelatesTo{
- Type: "m.reply",
- InReplyTo: &event.InReplyTo{
- EventID: id.EventID(msg.ParentID),
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ body, _ = strings.CutPrefix(body, username.plain)
+ body = username.plain + ": " + body
+ formattedBody, _ = strings.CutPrefix(formattedBody, username.formatted)
+ formattedBody = "" + username.formatted + ": " + formattedBody
+ avatar := b.handleAvatar(msg.Avatar)
+
+ content = event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ RelatesTo: &event.RelatesTo{
+ Type: "m.reply",
+ InReplyTo: &event.InReplyTo{
+ EventID: id.EventID(msg.ParentID),
+ },
},
- },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ RelatesTo: &event.RelatesTo{
+ Type: "m.reply",
+ InReplyTo: &event.InReplyTo{
+ EventID: id.EventID(msg.ParentID),
+ },
+ },
+ }
}
if b.GetBool("HTMLDisable") {
@@ -399,9 +486,8 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
return resp.EventID.String(), err
}
-
// Send a normal message
- msgID, err := b.sendNormalMessage(roomID, body, formattedBody)
+ msgID, err := b.sendNormalMessage(roomID, body, formattedBody, username, &msg)
if err != nil {
return "", err
}
@@ -884,7 +970,7 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, roomID id.RoomID) (stri
}
// TODO: message ID
- _, err := b.sendNormalMessage(roomID, body, formattedBody)
+ _, err := b.sendNormalMessage(roomID, body, formattedBody, username, msg)
if err != nil {
// Assume if there was an error sending a simple text message,
// sending the attachments will not be possible.
@@ -909,27 +995,29 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
sp := strings.Split(fi.Name, ".")
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
// image and video uploads send no username, we have to do this ourself here #715
- err := b.retry(func() error {
- content := event.MessageEventContent{
- MsgType: event.MsgText,
- Body: username.plain + fi.Comment,
- FormattedBody: username.formatted + fi.Comment,
- Format: event.FormatHTML,
- }
+ if !b.GetBool("UseMSC4144") {
+ err := b.retry(func() error {
+ content := event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: username.plain + fi.Comment,
+ FormattedBody: username.formatted + fi.Comment,
+ Format: event.FormatHTML,
+ }
- _, err2 := b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
+ _, err2 := b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
- return err2
- })
- if err != nil {
- b.Log.Errorf("file comment failed: %#v", err)
+ return err2
+ })
+ if err != nil {
+ b.Log.Errorf("file comment failed: %#v", err)
+ }
}
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
var res *mautrix.RespMediaUpload
- err = b.retry(func() error {
+ err := b.retry(func() error {
media := mautrix.ReqUploadMedia{
Content: content,
ContentType: mtype,
@@ -952,14 +1040,37 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
case strings.Contains(mtype, "video"):
b.Log.Debugf("sendVideo %s", res.ContentURI)
err = b.retry(func() error {
- content := event.MessageEventContent{
- MsgType: event.MsgVideo,
- FileName: fi.Name,
- URL: id.ContentURIString(res.ContentURI.String()),
- Info: &event.FileInfo{
- MimeType: mtype,
- Size: len(*fi.Data),
- },
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ MsgType: event.MsgVideo,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ Format: event.FormatHTML,
+ FormattedBody: "" + username.formatted + ": " + fi.Name,
+ Body: username.plain + ": " + fi.Name,
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgVideo,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ }
}
_, err2 := b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
@@ -980,16 +1091,43 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
b.Log.Debugf("Image format detected: %s (%dx%d)", format, cfg.Width, cfg.Height)
- img := event.MessageEventContent{
- MsgType: event.MsgImage,
- Body: fi.Name,
- URL: id.ContentURIString(res.ContentURI.String()),
- Info: &event.FileInfo{
- MimeType: mtype,
- Size: len(*fi.Data),
- Width: cfg.Width, // #nosec G115 -- go std will not returned negative size
- Height: cfg.Height, // #nosec G115 -- go std will not returned negative size
- },
+ var img event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ avatar := b.handleAvatar(msg.Avatar)
+ img = event.MessageEventContent{
+ MsgType: event.MsgImage,
+ Body: username.plain + ": " + fi.Name,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ Width: cfg.Width, // #nosec G115 -- go std will not returned negative size
+ Height: cfg.Height, // #nosec G115 -- go std will not returned negative size
+ },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ Format: event.FormatHTML,
+ FormattedBody: "" + username.formatted + ": " + fi.Name,
+ }
+
+ img.AddPerMessageProfileFallback()
+ } else {
+ img = event.MessageEventContent{
+ MsgType: event.MsgImage,
+ Body: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ Width: cfg.Width, // #nosec G115 -- go std will not returned negative size
+ Height: cfg.Height, // #nosec G115 -- go std will not returned negative size
+ },
+ }
}
err = b.retry(func() error {
@@ -1009,14 +1147,37 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
}():
b.Log.Debugf("sendAudio %s", res.ContentURI)
err = b.retry(func() error {
- content := event.MessageEventContent{
- MsgType: event.MsgAudio,
- FileName: fi.Name,
- URL: id.ContentURIString(res.ContentURI.String()),
- Info: &event.FileInfo{
- MimeType: mtype,
- Size: len(*fi.Data),
- },
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ MsgType: event.MsgAudio,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ Format: event.FormatHTML,
+ FormattedBody: "" + username.formatted + ": " + fi.Name,
+ Body: username.plain + ": " + fi.Name,
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgAudio,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ }
}
_, err2 := b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
return err2
@@ -1027,14 +1188,37 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
default:
b.Log.Debugf("sendFile %s", res.ContentURI)
err = b.retry(func() error {
- content := event.MessageEventContent{
- MsgType: event.MsgFile,
- FileName: fi.Name,
- URL: id.ContentURIString(res.ContentURI.String()),
- Info: &event.FileInfo{
- MimeType: mtype,
- Size: len(*fi.Data),
- },
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ MsgType: event.MsgFile,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ Format: event.FormatHTML,
+ FormattedBody: "" + username.formatted + ": " + fi.Name,
+ Body: username.plain + ": " + fi.Name,
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgFile,
+ FileName: fi.Name,
+ URL: id.ContentURIString(res.ContentURI.String()),
+ Info: &event.FileInfo{
+ MimeType: mtype,
+ Size: len(*fi.Data),
+ },
+ }
}
_, err2 := b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
@@ -1048,25 +1232,41 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, roomID id.RoomID, fi *co
b.Log.Debugf("result: %#v", res)
}
-func (b *Bmatrix) sendNormalMessage(roomID id.RoomID, body string, formattedBody string) (string, error) {
+func (b *Bmatrix) sendNormalMessage(roomID id.RoomID, body string, formattedBody string, username *matrixUsername, msg *config.Message) (string, error) {
if b.GetBool("HTMLDisable") {
// Send a plain text message if html is disabled
- return b.sendNormalMessagePlaintext(roomID, body)
+ return b.sendNormalMessagePlaintext(roomID, body, username, msg)
} else {
// Post normal message with HTML support (eg riot.im)
- return b.sendNormalMessageHTML(roomID, body, formattedBody)
+ return b.sendNormalMessageHTML(roomID, body, formattedBody, username, msg)
}
}
-func (b *Bmatrix) sendNormalMessagePlaintext(roomID id.RoomID, body string) (string, error) {
+func (b *Bmatrix) sendNormalMessagePlaintext(roomID id.RoomID, body string, username *matrixUsername, msg *config.Message) (string, error) {
var (
resp *mautrix.RespSendEvent
err error
)
err = b.retry(func() error {
- resp, err = b.mc.SendText(context.TODO(), roomID, body)
-
+ if b.GetBool("UseMSC4144") {
+ avatar := b.handleAvatar(msg.Avatar)
+ body, _ = strings.CutPrefix(body, username.plain)
+ body = username.plain + ": " + body
+ content := event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: body,
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ }
+ resp, err = b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
+ } else {
+ resp, err = b.mc.SendText(context.TODO(), roomID, body)
+ }
return err
})
if err != nil {
@@ -1076,18 +1276,39 @@ func (b *Bmatrix) sendNormalMessagePlaintext(roomID id.RoomID, body string) (str
return resp.EventID.String(), err
}
-func (b *Bmatrix) sendNormalMessageHTML(roomID id.RoomID, body string, formattedBody string) (string, error) {
+func (b *Bmatrix) sendNormalMessageHTML(roomID id.RoomID, body string, formattedBody string, username *matrixUsername, msg *config.Message) (string, error) {
var (
resp *mautrix.RespSendEvent
err error
)
err = b.retry(func() error {
- content := event.MessageEventContent{
- MsgType: event.MsgText,
- Body: body,
- FormattedBody: formattedBody,
- Format: event.FormatHTML,
+ var content event.MessageEventContent
+ if b.GetBool("UseMSC4144") {
+ body, _ = strings.CutPrefix(body, username.plain)
+ body = username.plain + ": " + body
+ formattedBody, _ = strings.CutPrefix(formattedBody, username.formatted)
+ formattedBody = "" + username.formatted + ": " + formattedBody
+ avatar := b.handleAvatar(msg.Avatar)
+ content = event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ BeeperPerMessageProfile: &event.BeeperPerMessageProfile{
+ ID: msg.UserID + "/" + username.plain,
+ Displayname: username.plain,
+ AvatarURL: &avatar,
+ HasFallback: true,
+ },
+ }
+ } else {
+ content = event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: body,
+ FormattedBody: formattedBody,
+ Format: event.FormatHTML,
+ }
}
resp, err = b.mc.SendMessageEvent(context.TODO(), roomID, event.EventMessage, content)
@@ -1100,3 +1321,46 @@ func (b *Bmatrix) sendNormalMessageHTML(roomID id.RoomID, body string, formatted
return resp.EventID.String(), err
}
+
+func (b *Bmatrix) handleAvatar(urlS string) id.ContentURIString {
+ u, err := url.Parse(urlS)
+ if err != nil {
+ b.Log.Debugf("URL parse for avatar error: %#v", err)
+ return ""
+ }
+ req, err2 := http.NewRequest(http.MethodGet, u.String(), nil)
+ if err2 != nil {
+ b.Log.Debugf("HTTP GET for avatar error: %#v", err)
+ return ""
+ }
+ client := &http.Client{}
+ resp, err3 := client.Do(req)
+ if err3 != nil {
+ b.Log.Debugf("HTTP GET for avatar error: %#v", err)
+ return ""
+ }
+ defer func() {
+ if err4 := resp.Body.Close(); err4 != nil {
+ b.Log.Debugf("Error closing HTTP body: %#v", err)
+ }
+ }()
+
+ if resp.StatusCode != http.StatusOK {
+ b.Log.Debugf("HTTP GET for avatar error: status code %#v", resp.StatusCode)
+ return ""
+ }
+ sp := strings.Split(urlS, ".")
+ mtype := mime.TypeByExtension("." + sp[len(sp)-1])
+ media := mautrix.ReqUploadMedia{
+ Content: resp.Body,
+ ContentType: mtype,
+ ContentLength: resp.ContentLength,
+ }
+
+ res, err5 := b.mc.UploadMedia(context.TODO(), media)
+ if err5 != nil {
+ b.Log.Debugf("error uploading avatar to matrix homeserver: %#v", err)
+ return ""
+ }
+ return id.ContentURIString(res.ContentURI.String())
+}
diff --git a/changelog.md b/changelog.md
index a74b67f01..61ecbe88a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -35,6 +35,8 @@
- matterbridge is now built with whatsappmulti backend enabled by default, unless the `nowhatsappmulti` build tag is passed
- Docker images are now automatically built and published to `ghcr.io/matterbridge-org/matterbridge` ([#86](https://github.com/matterbridge-org/matterbridge/pull/86))
- matterbridge will now apply a default `RemoteNickFormat` setting of `"[{PROTOCOL}] <{NICK}> "` which may be overridden by individual bridge settings, environment variables, or the `General` section of the config file, fulfilling the enhancement requested at ([#162](https://github.com/matterbridge-org/matterbridge/issues/162))
+- matrix
+ - Supports MSC4144/puppeting ([#232](https://github.com/matterbridge-org/matterbridge/pulls/232)). See also [MSC4144](https://github.com/matrix-org/matrix-spec-proposals/pulls/4144). Note that this is useless unless you have a client that can display these. Clients that don't will fall back to displaying e.g. `Nick: msg`.
- the Viper configuration functions have been updated to defer a panic-handling function instead of deferring their RWMutex RUnlock calls. This became necessary due to the new "SetVal" function, which may be used to override a configuration setting; this is now the first time a write lock has been used within the config package. Otherwise, obtaining a write lock could have caused matterbridge to behave as a single-threaded application, due to the numerous RLock calls made from multiple bridges during runtime.
- a new bridge function "SanitizeNick" has been made available to any bridge that chooses to implement it. This is useful for puppeting support when certain characters are disallowed in the puppeted nicks. Only the irc bridge has an implementation of this so far. ([#239](https://github.com/matterbridge-org/matterbridge/pull/239))
- new bridge functions "SetBool", "SetString", "SetInt", etc. have been added, which provide override values for the Viper config settings for that bridge. These settings do not persist upon restart.
diff --git a/docs/protocols/matrix/settings.md b/docs/protocols/matrix/settings.md
index 1e6ad7f8e..37af393d3 100644
--- a/docs/protocols/matrix/settings.md
+++ b/docs/protocols/matrix/settings.md
@@ -148,3 +148,14 @@ Shows the username instead of the displayname
```toml
UseUserName=true
```
+
+## UseMSC4144
+
+Use MSC4144 to set nick per-message. See https://github.com/matrix-org/matrix-spec-proposals/pull/4144. At the moment this is an open proposal and is subject to change. Clients that don't support this will display e.g. `Nick: msg` with the nick in bold.
+
+- Setting: **OPTIONAL**, **RELOADABLE**
+- Format: *boolean*
+- Example:
+ ```toml
+ UseMSC4144=true
+ ```
diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample
index 2e63ca16e..88c3fb060 100644
--- a/matterbridge.toml.sample
+++ b/matterbridge.toml.sample
@@ -1390,6 +1390,12 @@ ShowJoinPart=false
#OPTIONAL (default false)
SpoofUsername=false
+#Use MSC4144 to set nick per-message, like IRC RELAYMSG. See https://github.com/matrix-org/matrix-spec-proposals/pull/4144
+#This has less support scross clients but makes less API requests compared to SpoofUsername.
+#Clients that don't support this will display e.g. Nick: msg, with the nick in bold
+#OPTIONAL (default false)
+UseMSC4144=false
+
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
#It will strip other characters from the nick
#OPTIONAL (default false)