diff --git a/interface/matrix.go b/interface/matrix.go index bfce8b5..8c80c6d 100644 --- a/interface/matrix.go +++ b/interface/matrix.go @@ -34,8 +34,8 @@ type MatrixContainer interface { Logout() SendPreferencesToMatrix() - SendMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error) - SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, message string) (string, error) + PrepareMarkdownMessage(roomID string, msgtype mautrix.MessageType, message string) *mautrix.Event + SendEvent(event *mautrix.Event) (string, error) SendTyping(roomID string, typing bool) MarkRead(roomID, eventID string) JoinRoom(roomID, server string) (*rooms.Room, error) diff --git a/interface/ui.go b/interface/ui.go index c64c748..12aabbe 100644 --- a/interface/ui.go +++ b/interface/ui.go @@ -53,14 +53,6 @@ type MainView interface { InitialSyncDone() } -type MessageDirection int - -const ( - AppendMessage MessageDirection = iota - PrependMessage - IgnoreMessage -) - type RoomView interface { MxRoom() *rooms.Room @@ -69,8 +61,7 @@ type RoomView interface { UpdateUserList() ParseEvent(evt *mautrix.Event) Message - AppendMessage(message Message) - MarkMessageFailed(message Message) + AddMessage(message Message) AddServiceMessage(message string) } @@ -81,4 +72,8 @@ type Message interface { Timestamp() time.Time NotificationSenderName() string NotificationContent() string + + SetState(state mautrix.OutgoingEventState) + SetIsHighlight(highlight bool) + SetID(id string) } diff --git a/matrix/matrix.go b/matrix/matrix.go index 960e92b..ef0e349 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -304,9 +304,9 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) { } // TODO switch to roomView.AddEvent - message := mainView.ParseEvent(roomView, evt) + message := roomView.ParseEvent(evt) if message != nil { - roomView.AddMessage(message, ifc.AppendMessage) + roomView.AddMessage(message) roomView.MxRoom().LastReceivedMessage = message.Timestamp() if c.syncer.FirstSyncDone { pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should() @@ -480,25 +480,10 @@ func (c *Container) MarkRead(roomID, eventID string) { c.client.MakeRequest("POST", urlPath, struct{}{}, nil) } -// SendMessage sends a message with the given text to the given room. -func (c *Container) SendMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) { - defer debug.Recover() - c.SendTyping(roomID, false) - resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, - mautrix.Content{MsgType: msgtype, Body: text}) - if err != nil { - return "", err - } - return resp.EventID, nil -} - var mentionRegex = regexp.MustCompile("\\[(.+?)]\\(https://matrix.to/#/@.+?:.+?\\)") var roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\\)") -// SendMarkdownMessage sends a message with the given markdown text to the given room. -func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) { - defer debug.Recover() - +func (c *Container) PrepareMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) *mautrix.Event { content := format.RenderMarkdown(text) content.MsgType = msgtype @@ -506,10 +491,9 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageTy content.Body = mentionRegex.ReplaceAllString(content.Body, "$1") content.Body = roomRegex.ReplaceAllString(content.Body, "$1") - c.SendTyping(roomID, false) - roomView := c.ui.MainView().GetRoom(roomID) txnID := c.client.TxnID() - localEcho := roomView.ParseEvent(&mautrix.Event{ + localEcho := &mautrix.Event{ + ID: txnID, Sender: c.config.UserID, Type: mautrix.EventMessage, Timestamp: time.Now().UnixNano() / 1e6, @@ -519,11 +503,17 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageTy TransactionID: txnID, OutgoingState: mautrix.EventStateLocalEcho, }, - }) - roomView.AppendMessage(localEcho) - resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, content, mautrix.ReqSendEvent{TransactionID: txnID}) + } + return localEcho +} + +// SendMarkdownMessage sends a message with the given markdown text to the given room. +func (c *Container) SendEvent(event *mautrix.Event) (string, error) { + defer debug.Recover() + + c.SendTyping(event.RoomID, false) + resp, err := c.client.SendMessageEvent(event.RoomID, event.Type, event.Content, mautrix.ReqSendEvent{TransactionID: event.Unsigned.TransactionID}) if err != nil { - roomView.MarkMessageFailed(localEcho, err) return "", err } return resp.EventID, nil @@ -592,6 +582,7 @@ func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*mautrix.Event, e } } room.PrevBatch = resp.End + c.config.PutRoom(room) debug.Printf("Loaded %d events for %s from server from %s to %s", len(resp.Chunk), room.ID, resp.Start, resp.End) return resp.Chunk, nil } diff --git a/ui/commands.go b/ui/commands.go index a652518..3a24836 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -27,16 +27,13 @@ import ( "github.com/lucasb-eyer/go-colorful" - "maunium.net/go/mautrix" - "maunium.net/go/mautrix/format" - "maunium.net/go/gomuks/debug" + "maunium.net/go/mautrix" ) func cmdMe(cmd *Command) { text := strings.Join(cmd.Args, " ") - tempMessage := cmd.Room.NewTempMessage("m.emote", text) - go cmd.MainView.sendTempMessage(cmd.Room, tempMessage, text) + go cmd.Room.SendMessage(mautrix.MsgEmote, text) cmd.UI.Render() } @@ -97,8 +94,7 @@ func cmdRainbow(cmd *Command) { color := rainbow.GetInterpolatedColorFor(float64(i) / float64(len(text))).Hex() fmt.Fprintf(&html, "%c", color, char) } - tempMessage := cmd.Room.NewTempMessage("m.text", format.HTMLToText(html.String())) - go cmd.MainView.sendTempMessage(cmd.Room, tempMessage, html.String()) + go cmd.Room.SendMessage("m.text", html.String()) cmd.UI.Render() } diff --git a/ui/message-view.go b/ui/message-view.go index 5eedabc..87889fb 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -89,7 +89,7 @@ func (view *MessageView) updateWidestSender(sender string) { } } -func (view *MessageView) UpdateMessageID(ifcMessage ifc.Message, newID string) { +/*func (view *MessageView) UpdateMessageID(ifcMessage ifc.Message, newID string) { message, ok := ifcMessage.(messages.UIMessage) if !ok { debug.Print("[Warning] Passed non-UIMessage ifc.Message object to UpdateMessageID().") @@ -99,9 +99,17 @@ func (view *MessageView) UpdateMessageID(ifcMessage ifc.Message, newID string) { delete(view.messageIDs, message.ID()) message.SetID(newID) view.messageIDs[message.ID()] = message -} +}*/ -func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.MessageDirection) { +type MessageDirection int + +const ( + AppendMessage MessageDirection = iota + PrependMessage + IgnoreMessage +) + +func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDirection) { if ifcMessage == nil { return } @@ -117,11 +125,11 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag var messageExists bool if oldMsg, messageExists = view.messageIDs[message.ID()]; messageExists { view.replaceMessage(oldMsg, message) - direction = ifc.IgnoreMessage + direction = IgnoreMessage } else if oldMsg, messageExists = view.messageIDs[message.TxnID()]; messageExists { view.replaceMessage(oldMsg, message) delete(view.messageIDs, message.TxnID()) - direction = ifc.IgnoreMessage + direction = IgnoreMessage } view.updateWidestSender(message.Sender()) @@ -133,13 +141,13 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag } message.CalculateBuffer(view.config.Preferences, width) - if direction == ifc.AppendMessage { + if direction == AppendMessage { if view.ScrollOffset > 0 { view.ScrollOffset += message.Height() } view.messages = append(view.messages, message) view.appendBuffer(message) - } else if direction == ifc.PrependMessage { + } else if direction == PrependMessage { view.messages = append([]messages.UIMessage{message}, view.messages...) } else if oldMsg != nil { view.replaceBuffer(oldMsg, message) diff --git a/ui/messages/base.go b/ui/messages/base.go index b8b1694..d9db866 100644 --- a/ui/messages/base.go +++ b/ui/messages/base.go @@ -192,6 +192,9 @@ func (msg *BaseMessage) FormatDate() string { } func (msg *BaseMessage) ID() string { + if len(msg.MsgID) == 0 { + return msg.MsgTxnID + } return msg.MsgID } @@ -199,6 +202,10 @@ func (msg *BaseMessage) SetID(id string) { msg.MsgID = id } +func (msg *BaseMessage) TxnID() string { + return msg.MsgTxnID +} + func (msg *BaseMessage) Type() mautrix.MessageType { return msg.MsgType } @@ -219,14 +226,6 @@ func (msg *BaseMessage) SetIsHighlight(isHighlight bool) { msg.MsgIsHighlight = isHighlight } -func (msg *BaseMessage) IsService() bool { - return msg.MsgIsService -} - -func (msg *BaseMessage) SetIsService(isService bool) { - msg.MsgIsService = isService -} - func (msg *BaseMessage) Source() json.RawMessage { return msg.MsgSource } diff --git a/ui/messages/message.go b/ui/messages/message.go index 634bd11..bd78e5d 100644 --- a/ui/messages/message.go +++ b/ui/messages/message.go @@ -19,6 +19,7 @@ package messages import ( "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/interface" + "maunium.net/go/mautrix" "maunium.net/go/mauview" "maunium.net/go/tcell" ) @@ -27,6 +28,7 @@ import ( type UIMessage interface { ifc.Message + Type() mautrix.MessageType Sender() string SenderColor() tcell.Color TextColor() tcell.Color diff --git a/ui/messages/parser.go b/ui/messages/parser.go index f45eea2..7b76a69 100644 --- a/ui/messages/parser.go +++ b/ui/messages/parser.go @@ -29,7 +29,6 @@ import ( "maunium.net/go/gomuks/ui/messages/html" "maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/widget" - htmlp "maunium.net/go/gomuks/ui/messages/html" ) func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go index f7c54ca..588dee4 100644 --- a/ui/messages/textmessage.go +++ b/ui/messages/textmessage.go @@ -18,6 +18,7 @@ package messages import ( "fmt" + "time" "maunium.net/go/mautrix" @@ -39,6 +40,18 @@ func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMes } } +func NewServiceMessage(text string) UIMessage { + return &TextMessage{ + BaseMessage: BaseMessage{ + MsgSenderID: "*", + MsgSender: "*", + MsgTimestamp: time.Now(), + MsgIsService: true, + }, + MsgText: text, + } +} + func (msg *TextMessage) getCache() tstring.TString { if msg.cache == nil { switch msg.MsgType { @@ -57,11 +70,6 @@ func (msg *TextMessage) SetIsHighlight(isHighlight bool) { msg.cache = nil } -func (msg *TextMessage) SetIsService(isService bool) { - msg.BaseMessage.SetIsService(isService) - msg.cache = nil -} - func (msg *TextMessage) NotificationContent() string { return msg.MsgText } diff --git a/ui/room-view.go b/ui/room-view.go index 2711338..e37760e 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -20,7 +20,6 @@ import ( "fmt" "path/filepath" "sort" - "strconv" "strings" "time" @@ -251,7 +250,7 @@ func (view *RoomView) OnKeyEvent(event mauview.KeyEvent) bool { msgView.AddScrollOffset(-msgView.Height() / 2) return true case tcell.KeyEnter: - if event.Modifiers() & tcell.ModShift == 0 && event.Modifiers() & tcell.ModCtrl == 0 && view.inputSubmitFunc != nil { + if event.Modifiers()&tcell.ModShift == 0 && event.Modifiers()&tcell.ModCtrl == 0 && view.inputSubmitFunc != nil { view.inputSubmitFunc(view, view.input.GetText()) return true } @@ -379,6 +378,44 @@ func (view *RoomView) InputTabComplete(text string, cursorOffset int) { view.SetCompletions(strCompletions) } +func (view *RoomView) InputSubmit(text string) { + if len(text) == 0 { + return + } else if cmd := view.parent.cmdProcessor.ParseCommand(view, text); cmd != nil { + go view.parent.cmdProcessor.HandleCommand(cmd) + } else { + go view.SendMessage(mautrix.MsgText, text) + } + view.SetInputText("") +} + +func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) { + defer debug.Recover() + debug.Print("Sending message", msgtype, text, "to", view.Room.ID) + if !view.config.Preferences.DisableEmojis { + text = emoji.Sprint(text) + } + evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text) + msg := view.ParseEvent(evt) + view.AddMessage(msg) + eventID, err := view.parent.matrix.SendEvent(evt) + if err != nil { + msg.SetState(mautrix.EventStateSendFail) + // Show shorter version if available + if httpErr, ok := err.(mautrix.HTTPError); ok { + err = httpErr + if respErr := httpErr.RespError; respErr != nil { + err = respErr + } + } + view.AddServiceMessage(fmt.Sprintf("Failed to send message: %v", err)) + view.parent.parent.Render() + } else { + debug.Print("Event ID received:", eventID) + //view.MessageView().UpdateMessageID(msg, eventID) + } +} + func (view *RoomView) MessageView() *MessageView { return view.content } @@ -406,37 +443,12 @@ func (view *RoomView) UpdateUserList() { } } -func (view *RoomView) newUIMessage(id, sender string, msgtype mautrix.MessageType, text string, timestamp time.Time) messages.UIMessage { - member := view.Room.GetMember(sender) - displayname := sender - if member != nil { - displayname = member.Displayname - } - msg := messages.NewTextMessage(id, sender, displayname, msgtype, text, timestamp) - return msg -} - -func (view *RoomView) NewTempMessage(msgtype mautrix.MessageType, text string) ifc.Message { - now := time.Now() - id := strconv.FormatInt(now.UnixNano(), 10) - sender := "" - if ownerMember := view.Room.GetMember(view.Room.GetSessionOwner()); ownerMember != nil { - sender = ownerMember.Displayname - } - message := view.newUIMessage(id, sender, msgtype, text, now) - message.SetState(ifc.MessageStateSending) - view.AddMessage(message, ifc.AppendMessage) - return message -} - func (view *RoomView) AddServiceMessage(text string) { - message := view.newUIMessage(view.parent.matrix.Client().TxnID(), "*", "gomuks.service", text, time.Now()) - message.SetIsService(true) - view.AddMessage(message, ifc.AppendMessage) + view.content.AddMessage(messages.NewServiceMessage(text), AppendMessage) } -func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) { - view.content.AddMessage(message, direction) +func (view *RoomView) AddMessage(message ifc.Message) { + view.content.AddMessage(message, AppendMessage) } func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message { diff --git a/ui/view-main.go b/ui/view-main.go index d9d9247..5b6efa8 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -23,9 +23,6 @@ import ( "time" "unicode" - "github.com/kyokomi/emoji" - - "maunium.net/go/mautrix" "maunium.net/go/mauview" "maunium.net/go/tcell" @@ -152,45 +149,6 @@ func findWordToTabComplete(text string) string { return output } -func (view *MainView) InputSubmit(roomView *RoomView, text string) { - if len(text) == 0 { - return - } else if text[0] == '/' { - cmd := view.cmdProcessor.ParseCommand(roomView, text) - go view.cmdProcessor.HandleCommand(cmd) - } else { - view.SendMessage(roomView, text) - } - roomView.SetInputText("") -} - -func (view *MainView) SendMessage(roomView *RoomView, text string) { - go view.goSendMessage(roomView, text) -} - -func (view *MainView) goSendMessage(roomView *RoomView, text string) { - defer debug.Recover() - debug.Print("Sending message", tempMessage.Type(), text) - if !roomView.config.Preferences.DisableEmojis { - text = emoji.Sprint(text) - } - eventID, err := view.matrix.SendMarkdownMessage(roomView.Room.ID, tempMessage.Type(), text) - if err != nil { - tempMessage.SetState(ifc.MessageStateFailed) - if httpErr, ok := err.(mautrix.HTTPError); ok { - if respErr := httpErr.RespError; respErr != nil { - // Show shorter version if available - err = respErr - } - } - roomView.AddServiceMessage(fmt.Sprintf("Failed to send message: %v", err)) - view.parent.Render() - } else { - debug.Print("Event ID received:", eventID) - roomView.MessageView().UpdateMessageID(tempMessage, eventID) - } -} - func (view *MainView) ShowBare(roomView *RoomView) { if roomView == nil { return @@ -204,7 +162,7 @@ func (view *MainView) ShowBare(roomView *RoomView) { fmt.Println(roomView.MessageView().CapturePlaintext(height)) fmt.Println("Press enter to return to normal mode.") reader := bufio.NewReader(os.Stdin) - reader.ReadRune() + _, _, _ = reader.ReadRune() print("\033[2J\033[0;0H") }) } @@ -310,7 +268,6 @@ func (view *MainView) SwitchRoom(tag string, room *rooms.Room) { func (view *MainView) addRoomPage(room *rooms.Room) { if _, ok := view.rooms[room.ID]; !ok { roomView := NewRoomView(view, room). - SetInputSubmitFunc(view.InputSubmit). SetInputChangedFunc(view.InputChanged) view.rooms[room.ID] = roomView roomView.UpdateUserList() @@ -428,8 +385,8 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul sendNotification(room, message.NotificationSenderName(), message.NotificationContent(), should.Highlight, shouldPlaySound) } - // TODO Make sure this happens somewhere else - //message.SetIsHighlight(should.Highlight) + // TODO this should probably happen somewhere else + message.SetIsHighlight(should.Highlight) } func (view *MainView) InitialSyncDone() { @@ -443,15 +400,16 @@ func (view *MainView) InitialSyncDone() { func (view *MainView) LoadHistory(room string) { defer debug.Recover() roomView := view.rooms[room] + msgView := roomView.MessageView() batch := roomView.Room.PrevBatch lockTime := time.Now().Unix() + 1 roomView.Room.LockHistory() - roomView.MessageView().LoadingMessages = true + msgView.LoadingMessages = true defer func() { roomView.Room.UnlockHistory() - roomView.MessageView().LoadingMessages = false + msgView.LoadingMessages = false }() // There's no clean way to try to lock a mutex, so we just check if we still @@ -468,16 +426,9 @@ func (view *MainView) LoadHistory(room string) { return } for _, evt := range history { - message := roomView.ParseEvent(evt) - if message != nil { - roomView.AddMessage(message, ifc.PrependMessage) + if message := roomView.ParseEvent(evt); message != nil { + msgView.AddMessage(message, PrependMessage) } } - // TODO? - /*err = roomView.SaveHistory(view.config.HistoryDir) - if err != nil { - debug.Printf("Failed to save history of %s: %v", roomView.Room.GetTitle(), err) - }*/ - view.config.PutRoom(roomView.Room) view.parent.Render() }