matterbridge icon indicating copy to clipboard operation
matterbridge copied to clipboard

nctalk: Images are not embedded in message when send from other messengers

Open mpietruschka opened this issue 1 year ago • 2 comments

Describe the bug Matterbridge: I'm using the master branch. Nextcloud: 27.1.8

When I post an image to whatsapp its not embedded into the nctalk message. Instead the message holds a link to the matterbridge mediaserver. Same behaviour when send from telegram.

When I post an image in nctalk, then its transfered to whatsapp nicely.

Sending images between telegram and whatsapp works great.

Text messages are bridged perfectly.

UPDATE I tested v1.26.0 (from source) and sending images from telegram to nctalk. It doesn't work.

To Reproduce

Nothings special.

Expected behavior Send embedded images/media from whatsapp/other messengers to nctalk, too.

Screenshots/debug logs image

image

Environment (please complete the following information):

I'm running Matterbridge in Docker build with the served Dockerfile_whatsappmulti

LOG


matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="Receiving message &proto.Message{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0006b67c0)}, sizeCache:0, unknownFields:[]uint8(nil), Conversation:(*string)(nil), SenderKeyDistributionMessage:(*proto.SenderKeyDistributionMessage)(nil), ImageMessage:(*proto.ImageMessage)(0xc00044d500), ContactMessage:(*proto.ContactMessage)(nil), LocationMessage:(*proto.LocationMessage)(nil), ExtendedTextMessage:(*proto.ExtendedTextMessage)(nil), DocumentMessage:(*proto.DocumentMessage)(nil), AudioMessage:(*proto.AudioMessage)(nil), VideoMessage:(*proto.VideoMessage)(nil), Call:(*proto.Call)(nil), Chat:(*proto.Chat)(nil), ProtocolMessage:(*proto.ProtocolMessage)(nil), ContactsArrayMessage:(*proto.ContactsArrayMessage)(nil), HighlyStructuredMessage:(*proto.HighlyStructuredMessage)(nil), FastRatchetKeySenderKeyDistributionMessage:(*proto.SenderKeyDistributionMessage)(nil), SendPaymentMessage:(*proto.SendPaymentMessage)(nil), LiveLocationMessage:(*proto.LiveLocationMessage)(nil), RequestPaymentMessage:(*proto.RequestPaymentMessage)(nil), DeclinePaymentRequestMessage:(*proto.DeclinePaymentRequestMessage)(nil), CancelPaymentRequestMessage:(*proto.CancelPaymentRequestMessage)(nil), TemplateMessage:(*proto.TemplateMessage)(nil), StickerMessage:(*proto.StickerMessage)(nil), GroupInviteMessage:(*proto.GroupInviteMessage)(nil), TemplateButtonReplyMessage:(*proto.TemplateButtonReplyMessage)(nil), ProductMessage:(*proto.ProductMessage)(nil), DeviceSentMessage:(*proto.DeviceSentMessage)(nil), MessageContextInfo:(*proto.MessageContextInfo)(nil), ListMessage:(*proto.ListMessage)(nil), ViewOnceMessage:(*proto.FutureProofMessage)(nil), OrderMessage:(*proto.OrderMessage)(nil), ListResponseMessage:(*proto.ListResponseMessage)(nil), EphemeralMessage:(*proto.FutureProofMessage)(nil), InvoiceMessage:(*proto.InvoiceMessage)(nil), ButtonsMessage:(*proto.ButtonsMessage)(nil), ButtonsResponseMessage:(*proto.ButtonsResponseMessage)(nil), PaymentInviteMessage:(*proto.PaymentInviteMessage)(nil), InteractiveMessage:(*proto.InteractiveMessage)(nil), ReactionMessage:(*proto.ReactionMessage)(nil), StickerSyncRmrMessage:(*proto.StickerSyncRMRMessage)(nil), InteractiveResponseMessage:(*proto.InteractiveResponseMessage)(nil), PollCreationMessage:(*proto.PollCreationMessage)(nil), PollUpdateMessage:(*proto.PollUpdateMessage)(nil), KeepInChatMessage:(*proto.KeepInChatMessage)(nil), DocumentWithCaptionMessage:(*proto.FutureProofMessage)(nil), RequestPhoneNumberMessage:(*proto.RequestPhoneNumberMessage)(nil), ViewOnceMessageV2:(*proto.FutureProofMessage)(nil), EncReactionMessage:(*proto.EncReactionMessage)(nil), EditedMessage:(*proto.FutureProofMessage)(nil), ViewOnceMessageV2Extension:(*proto.FutureProofMessage)(nil), PollCreationMessageV2:(*proto.PollCreationMessage)(nil), ScheduledCallCreationMessage:(*proto.ScheduledCallCreationMessage)(nil), GroupMentionedMessage:(*proto.FutureProofMessage)(nil), PinInChatMessage:(*proto.PinInChatMessage)(nil), PollCreationMessageV3:(*proto.PollCreationMessage)(nil), ScheduledCallEditMessage:(*proto.ScheduledCallEditMessage)(nil), PtvMessage:(*proto.VideoMessage)(nil), BotInvokeMessage:(*proto.FutureProofMessage)(nil), EncCommentMessage:(*proto.EncCommentMessage)(nil)}" func=handleMessage file="bridge/whatsappmulti/handlers.go:107" prefix=whatsapp
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="Trying to download 3A8D508A4F3211EC3799.jpg with type image/jpeg" func=handleImageMessage file="bridge/whatsappmulti/handlers.go:242" prefix=whatsapp
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="Download OK "3A8D508A4F3211EC3799.jpg" 176946" func=HandleDownloadData2 file="bridge/helper/helper.go:183" prefix=whatsapp
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="<= Sending message from [email protected] on whatsapp.XXX to gateway" func=handleImageMessage file="bridge/whatsappmulti/handlers.go:254" prefix=whatsapp
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="<= Message is config.Message{Text:"", Channel:"XXX", Username:"XXX P", UserID:"[email protected]", Avatar:"xxx", Account:"whatsapp.XXX", Event:"", Protocol:"whatsapp", Gateway:"", ParentID:"", Timestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), ID:"[email protected]/3A8D508A4F3211EC3799", Extra:map[string][]interface {}{"file":[]interface {}{config.FileInfo{Name:"3A8D508A4F3211EC3799.jpg", Data:(*[]uint8)(0xc0000fe000), Comment:"", URL:"", Size:0, Avatar:false, SHA:"", NativeID:""}}}}" func=handleImageMessage file="bridge/whatsappmulti/handlers.go:255" prefix=whatsapp
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="mediaserver path placing file: /mediaserver/104d0511/3A8D508A4F3211EC3799.jpg" func=handleFilesLocal file="gateway/handlers.go:158" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="mediaserver download URL = XXX/3A8D508A4F3211EC3799.jpg" func=handleFiles file="gateway/handlers.go:112" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="=> Sending config.Message{Text:"", Channel:"1xxx", Username:"XXX P", UserID:"[email protected]", Avatar:"xxx", Account:"whatsapp.XXX", Event:"", Protocol:"whatsapp", Gateway:"matterbottest", ParentID:"", Timestamp:time.Date(2024, time.May, 5, 13, 23, 1, 899666261, time.Local), ID:"[email protected]/3A8D508A4F3211EC3799", Extra:map[string][]interface {}{"file":[]interface {}{config.FileInfo{Name:"3A8D508A4F3211EC3799.jpg", Data:(*[]uint8)(0xc0000fe000), Comment:"", URL:"XXX/3A8D508A4F3211EC3799.jpg", Size:0, Avatar:false, SHA:"104d0511", NativeID:""}}}} from whatsapp.matterbot (xxx) to telegram.XXX (XXX)" func=SendMessage file="gateway/gateway.go:499" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="=> Receiving config.Message{Text:"", Channel:"XXX", Username:"[whatsapp] &lt;XXX P&gt; ", UserID:"[email protected]", Avatar:"xxx", Account:"whatsapp.XXX", Event:"", Protocol:"whatsapp", Gateway:"matterbottest", ParentID:"", Timestamp:time.Date(2024, time.May, 5, 13, 23, 1, 899666261, time.Local), ID:"", Extra:map[string][]interface {}{"file":[]interface {}{config.FileInfo{Name:"3A8D508A4F3211EC3799.jpg", Data:(*[]uint8)(0xc0000fe000), Comment:"", URL:"XXX/3A8D508A4F3211EC3799.jpg", Size:0, Avatar:false, SHA:"104d0511", NativeID:""}}}}" func=Send file="bridge/telegram/telegram.go:121" prefix=telegram
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="mID telegram.XXX: 44" func=SendMessage file="gateway/gateway.go:518" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="=> Send from whatsapp.matterbot (XXX) to telegram.XXX (-XXX) took 71.451016ms" func=func1 file="gateway/gateway.go:508" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:01Z" level=debug msg="=> Sending config.Message{Text:"", Channel:"XXX", Username:"XXX P", UserID:"[email protected]", Avatar:"xxx", Account:"whatsapp.matterbot", Event:"", Protocol:"whatsapp", Gateway:"matterbottest", ParentID:"", Timestamp:time.Date(2024, time.May, 5, 13, 23, 1, 899666261, time.Local), ID:"[email protected]/3A8D508A4F3211EC3799", Extra:map[string][]interface {}{"file":[]interface {}{config.FileInfo{Name:"3A8D508A4F3211EC3799.jpg", Data:(*[]uint8)(0xc0000fe000), Comment:"", URL:"XXX/3A8D508A4F3211EC3799.jpg", Size:0, Avatar:false, SHA:"104d0511", NativeID:""}}}} from whatsapp.XXX (XXX) to nctalk.0 (xzhmhowg)" func=SendMessage file="gateway/gateway.go:499" prefix=gateway
matterbridge-1  | time="2024-05-05T13:23:05Z" level=error msg="Could not send message to room xzhmhowg from [whatsapp] <XXX P> : unexpected return code" func=Send file="bridge/nctalk/nctalk.go:128" prefix=nctalk
matterbridge-1  | time="2024-05-05T13:23:05Z" level=debug msg="=> Send from whatsapp.matterbot (XXX) to nctalk.0 (xzhmhowg) took 3.112660696s" func=func1 file="gateway/gateway.go:508" prefix=gateway

Additional context

[general]
MediaDownloadPath="xxx"
MediaServerDownload="xxx"

[telegram]
    [telegram.xxx]
    Token = "xxx"
    PrefixMessagesWithNick = true
    RemoteNickFormat = "[{PROTOCOL}] <{NICK}> "
    UseFullName=true

[nctalk]
    [nctalk.0]
    SeparateDisplayName = true
    Server = "xxx"
    Login = "bridge-bot"
    Password = "xxx"
    PrefixMessagesWithNick = true
    RemoteNickFormat="[{PROTOCOL}] <{NICK}> "

[whatsapp]
    [whatsapp.xxx]
    Number="xxx"
    RemoteNickFormat="[{PROTOCOL}] @{NICK}: "
    SessionFile="/data/xxx"

[[gateway]]
    name = "matterbottest"
    enable = true

    [[gateway.inout]]
    account = "telegram.xxx"
    channel = "xxx"

    [[gateway.inout]]
    account = "nctalk.0"
    channel = "xxxx"

    [[gateway.inout]]
    account = "whatsapp.xxx"
    channel = "xxx"

mpietruschka avatar May 05 '24 14:05 mpietruschka

We've been having this same issue, though with Discord and Matrix instead. Figured out that images below 1MB show fine, but anything above just spits out an invalid link.

khoin343 avatar Aug 31 '24 11:08 khoin343

Hi everyone,

I’ve added basic file upload support for the nctalk.go bridge. Please excuse any rough edges in the code – I used AI assistance to help me with the implementation. 🙂

To test it:

  1. Clone the repo
  2. Copy and paste the modified nctalk.go into bridge/nctalk/ (I can't upload the .go-file directly)
  3. Rebuild the Docker image with:
   docker build -t matterbridge .
  1. Run it with your config

I’ve tested it successfully with Telegram ↔︎ Nctalk, and file transfers are working for me.

Show modified nctalk.go
package nctalk

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/monaco-io/request"

	"gomod.garykim.dev/nc-talk/ocs"
	"gomod.garykim.dev/nc-talk/room"
	"gomod.garykim.dev/nc-talk/user"
)

type Btalk struct {
	user  *user.TalkUser
	rooms []Broom
	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Btalk{Config: cfg}
}

type Broom struct {
	room      *room.TalkRoom
	ctx       context.Context
	ctxCancel context.CancelFunc
}

func (b *Btalk) Connect() error {
	b.Log.Info("Connecting")
	tconfig := &user.TalkUserConfig{
		TLSConfig: &tls.Config{
			InsecureSkipVerify: b.GetBool("SkipTLSVerify"), //nolint:gosec
		},
	}
	var err error
	b.user, err = user.NewUser(b.GetString("Server"), b.GetString("Login"), b.GetString("Password"), tconfig)
	if err != nil {
		b.Log.Error("Config could not be used")
		return err
	}
	_, err = b.user.Capabilities()
	if err != nil {
		b.Log.Error("Cannot Connect")
		return err
	}
	b.Log.Info("Connected")
	return nil
}

func (b *Btalk) Disconnect() error {
	for _, r := range b.rooms {
		r.ctxCancel()
	}
	return nil
}

func (b *Btalk) JoinChannel(channel config.ChannelInfo) error {
	tr, err := room.NewTalkRoom(b.user, channel.Name)
	if err != nil {
		return err
	}
	newRoom := Broom{
		room: tr,
	}
	newRoom.ctx, newRoom.ctxCancel = context.WithCancel(context.Background())
	c, err := newRoom.room.ReceiveMessages(newRoom.ctx)
	if err != nil {
		return err
	}
	b.rooms = append(b.rooms, newRoom)

	go func() {
		for msg := range c {
			msg := msg

			if msg.Error != nil {
				b.Log.Errorf("Fatal message poll error: %s\n", msg.Error)

				return
			}

			// Ignore messages that are from the bot user
			if msg.ActorID == b.user.User || msg.ActorType == "bridged" {
				continue
			}

			// Handle deleting messages
			if msg.MessageType == ocs.MessageSystem && msg.Parent != nil && msg.Parent.MessageType == ocs.MessageDelete {
				b.handleDeletingMessage(&msg, &newRoom)
				continue
			}

			// Handle sending messages
			if msg.MessageType == ocs.MessageComment {
				b.handleSendingMessage(&msg, &newRoom)
				continue
			}

		}
	}()
	return nil
}

func (b *Btalk) Send(msg config.Message) (string, error) {
	r := b.getRoom(msg.Channel)
	if r == nil {
		b.Log.Errorf("Could not find room for %v", msg.Channel)
		return "", nil
	}

	// Standard Message Send
	if msg.Event == "" {
		// Handle sending files if they are included
		err := b.handleSendingFile(&msg, r)
		if err != nil {
			b.Log.Errorf("Could not send files in message to room %v from %v: %v", msg.Channel, msg.Username, err)

			return "", nil
		}

		sentMessage, err := b.sendText(r, &msg, msg.Text)
		if err != nil {
			b.Log.Errorf("Could not send message to room %v from %v: %v", msg.Channel, msg.Username, err)

			return "", nil
		}
		return strconv.Itoa(sentMessage.ID), nil
	}

	// Message Deletion
	if msg.Event == config.EventMsgDelete {
		messageID, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		data, err := r.room.DeleteMessage(messageID)
		if err != nil {
			return "", err
		}
		return strconv.Itoa(data.ID), nil
	}

	// Message is not a type that is currently supported
	return "", nil
}

func (b *Btalk) getRoom(token string) *Broom {
	for _, r := range b.rooms {
		if r.room.Token == token {
			return &r
		}
	}
	return nil
}

func (b *Btalk) sendText(r *Broom, msg *config.Message, text string) (*ocs.TalkRoomMessageData, error) {
	messageToSend := &room.Message{Message: msg.Username + text}

	if b.GetBool("SeparateDisplayName") {
		messageToSend.Message = text
		messageToSend.ActorDisplayName = msg.Username
	}

	return r.room.SendComplexMessage(messageToSend)
}

func (b *Btalk) handleFiles(mmsg *config.Message, message *ocs.TalkRoomMessageData) error {
	for _, parameter := range message.MessageParameters {
		if parameter.Type == ocs.ROSTypeFile {
			// Get the file
			file, err := b.user.DownloadFile(parameter.Path)
			if err != nil {
				return err
			}

			if mmsg.Extra == nil {
				mmsg.Extra = make(map[string][]interface{})
			}

			mmsg.Extra["file"] = append(mmsg.Extra["file"], config.FileInfo{
				Name:   parameter.Name,
				Data:   file,
				Size:   int64(len(*file)),
				Avatar: false,
			})
		}
	}

	return nil
}

func (b *Btalk) handleSendingFile(msg *config.Message, r *Broom) error {
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		
		// Try to upload file first
		var filePath string
		var err error
		
		if fi.Data != nil {
			filePath, err = b.uploadFileToNextcloud(&fi)
			if err != nil {
				b.Log.Errorf("Failed to upload file %s: %v", fi.Name, err)
			}
		}
		
		// Create message with file information
		messageText := ""
		if fi.Comment != "" {
			messageText = fi.Comment + " "
		}
		
		// If upload was successful, share file to chat
		if filePath != "" {
			err = b.shareFileToChat(r, messageText, fi.Name, filePath, msg.Username)
			if err != nil {
				b.Log.Errorf("Failed to share file to chat: %v", err)
				// Fallback to simple message with file link
				fileLink := b.user.NextcloudURL + "/remote.php/dav/files/" + b.user.User + filePath
				messageText += "[" + fi.Name + "](" + fileLink + ")"
				messageToSend := &room.Message{
					Message: messageText,
				}
				if b.GetBool("SeparateDisplayName") {
					messageToSend.Message = messageText
					messageToSend.ActorDisplayName = msg.Username
				}
				_, err = r.room.SendComplexMessage(messageToSend)
				if err != nil {
					return err
				}
			}
		} else if fi.URL != "" {
			// If we have a URL (from media server), use it with markdown link
			messageText += "[" + fi.Name + "](" + fi.URL + ")"
			messageToSend := &room.Message{
				Message: messageText,
			}
			if b.GetBool("SeparateDisplayName") {
				messageToSend.Message = messageText
				messageToSend.ActorDisplayName = msg.Username
			}
			_, err = r.room.SendComplexMessage(messageToSend)
			if err != nil {
				return err
			}
		} else {
			// No data or URL, just send filename
			messageText += fi.Name
			messageToSend := &room.Message{
				Message: messageText,
			}
			if b.GetBool("SeparateDisplayName") {
				messageToSend.Message = messageText
				messageToSend.ActorDisplayName = msg.Username
			}
			_, err = r.room.SendComplexMessage(messageToSend)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// shareFileToChat shares a file to the chat using the Nextcloud Files Sharing API
func (b *Btalk) shareFileToChat(r *Broom, messageText, fileName, filePath, username string) error {
	url := b.user.NextcloudURL + "/ocs/v2.php/apps/files_sharing/api/v1/shares"
	
	// Prepare request data for file sharing
	requestData := map[string]interface{}{
		"shareWith": r.room.Token,
		"shareType": 10, // Talk room share type
		"path": filePath,
	}
	
	// Convert to JSON
	jsonData, err := json.Marshal(requestData)
	if err != nil {
		return err
	}
	
	b.Log.Debugf("Sharing file to chat: %s", string(jsonData))
	
	// Send request
	client := b.user.RequestClient(request.Client{
		URL:    url,
		Method: "POST",
		Body:   jsonData,
		Header: map[string]string{
			"Content-Type": "application/json",
		},
	})
	
	res, err := client.Do()
	if err != nil {
		return err
	}
	
	b.Log.Debugf("Share file response status: %d, body: %s", res.StatusCode(), string(res.Data))
	
	if res.StatusCode() != 200 {
		return fmt.Errorf("failed to share file to chat, status: %d", res.StatusCode())
	}
	
	// Send a message to the chat about the shared file
	messageText += fileName
	messageToSend := &room.Message{
		Message: messageText,
	}
	if b.GetBool("SeparateDisplayName") {
		messageToSend.Message = messageText
		messageToSend.ActorDisplayName = username
	}
	_, err = r.room.SendComplexMessage(messageToSend)
	if err != nil {
		return err
	}
	
	return nil
}

// uploadFileToNextcloud uploads a file to Nextcloud via WebDAV
func (b *Btalk) uploadFileToNextcloud(fi *config.FileInfo) (string, error) {
	// Generate a unique filename to avoid conflicts
	filename := fi.Name
	if filename == "" {
		filename = "uploaded_file"
	}

	b.Log.Debugf("Starting upload for file: %s, size: %d bytes", filename, len(*fi.Data))

	// Use the attachments folder from capabilities if available
	uploadPath := "/"
	capabilities, err := b.user.Capabilities()
	if err == nil && capabilities.AttachmentsFolder != "" {
		uploadPath = capabilities.AttachmentsFolder
		b.Log.Debugf("Using attachments folder: %s", uploadPath)
	} else {
		b.Log.Debugf("No attachments folder configured, using root: %s", uploadPath)
	}

	// Ensure the path ends with /
	if !strings.HasSuffix(uploadPath, "/") {
		uploadPath += "/"
	}

	// Create the full file path
	filePath := uploadPath + filename

	// Construct the WebDAV URL using the same pattern as DownloadFile
	webdavURL := b.user.NextcloudURL + "/remote.php/dav/files/" + b.user.User + filePath
	b.Log.Debugf("WebDAV URL: %s", webdavURL)

	// Create the HTTP request using the same client pattern as DownloadFile
	client := b.user.RequestClient(request.Client{
		URL:    webdavURL,
		Method: "PUT",
		Body:   *fi.Data,
		Header: map[string]string{
			"Content-Type": "application/octet-stream",
		},
	})

	// Execute the request
	res, err := client.Do()
	if err != nil {
		b.Log.Errorf("Upload request failed: %v", err)
		return "", err
	}

	b.Log.Debugf("Upload response status: %d", res.StatusCode())

	// Check if upload was successful
	if res.StatusCode() != 201 && res.StatusCode() != 204 {
		b.Log.Errorf("Upload failed with status code: %d, response: %s", res.StatusCode(), string(res.Data))
		return "", fmt.Errorf("upload failed with status code: %d", res.StatusCode())
	}

	b.Log.Debugf("Upload successful, file path: %s", filePath)
	return filePath, nil
}

func (b *Btalk) handleSendingMessage(msg *ocs.TalkRoomMessageData, r *Broom) {
	remoteMessage := config.Message{
		Text:     formatRichObjectString(msg.Message, msg.MessageParameters),
		Channel:  r.room.Token,
		Username: DisplayName(msg, b.guestSuffix()),
		UserID:   msg.ActorID,
		Account:  b.Account,
	}
	// It is possible for the ID to not be set on older versions of Talk so we only set it if
	// the ID is not blank
	if msg.ID != 0 {
		remoteMessage.ID = strconv.Itoa(msg.ID)
	}

	// Handle Files
	err := b.handleFiles(&remoteMessage, msg)
	if err != nil {
		b.Log.Errorf("Error handling file: %#v", msg)

		return
	}

	b.Log.Debugf("<= Message is %#v", remoteMessage)
	b.Remote <- remoteMessage
}

func (b *Btalk) handleDeletingMessage(msg *ocs.TalkRoomMessageData, r *Broom) {
	remoteMessage := config.Message{
		Event:   config.EventMsgDelete,
		Text:    config.EventMsgDelete,
		Channel: r.room.Token,
		ID:      strconv.Itoa(msg.Parent.ID),
		Account: b.Account,
	}
	b.Log.Debugf("<= Message being deleted is %#v", remoteMessage)
	b.Remote <- remoteMessage
}

func (b *Btalk) guestSuffix() string {
	guestSuffix := " (Guest)"
	if b.IsKeySet("GuestSuffix") {
		guestSuffix = b.GetString("GuestSuffix")
	}

	return guestSuffix
}

// Spec: https://github.com/nextcloud/server/issues/1706#issue-182308785
func formatRichObjectString(message string, parameters map[string]ocs.RichObjectString) string {
	for id, parameter := range parameters {
		text := parameter.Name

		switch parameter.Type {
		case ocs.ROSTypeUser, ocs.ROSTypeGroup:
			text = "@" + text
		case ocs.ROSTypeFile:
			if parameter.Link != "" {
				text = parameter.Name
			}
		}

		message = strings.ReplaceAll(message, "{"+id+"}", text)
	}

	return message
}

func DisplayName(msg *ocs.TalkRoomMessageData, suffix string) string {
	if msg.ActorType == ocs.ActorGuest {
		if msg.ActorDisplayName == "" {
			return "Guest"
		}

		return msg.ActorDisplayName + suffix
	}

	return msg.ActorDisplayName
}

alex0107 avatar Jul 28 '25 06:07 alex0107