From 2b6c435e5020535a916e23b09d47608d788eaf05 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Apr 2019 18:42:49 +0300 Subject: Refactor message parsing things --- ui/messages/parser.go | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 ui/messages/parser.go (limited to 'ui/messages/parser.go') diff --git a/ui/messages/parser.go b/ui/messages/parser.go new file mode 100644 index 0000000..ae0606d --- /dev/null +++ b/ui/messages/parser.go @@ -0,0 +1,288 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2019 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package messages + +import ( + "fmt" + "html" + "strings" + "time" + + "maunium.net/go/mautrix" + "maunium.net/go/tcell" + + "maunium.net/go/gomuks/debug" + "maunium.net/go/gomuks/interface" + "maunium.net/go/gomuks/matrix/rooms" + "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 { + switch evt.Type { + case mautrix.EventSticker: + evt.Content.MsgType = mautrix.MsgImage + fallthrough + case mautrix.EventMessage: + return ParseMessage(matrix, room, evt) + case mautrix.StateTopic, mautrix.StateRoomName, mautrix.StateAliases, mautrix.StateCanonicalAlias: + return ParseStateEvent(matrix, room, evt) + case mautrix.StateMember: + return ParseMembershipEvent(room, evt) + } + return nil +} + +func unixToTime(unix int64) time.Time { + timestamp := time.Now() + if unix != 0 { + timestamp = time.Unix(unix/1000, unix%1000*1000) + } + return timestamp +} + +func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { + displayname := evt.Sender + member := room.GetMember(evt.Sender) + if member != nil { + displayname = member.Displayname + } + text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender)) + switch evt.Type { + case mautrix.StateTopic: + if len(evt.Content.Topic) == 0 { + text = text.AppendColor(" removed the topic.", tcell.ColorGreen) + } else { + text = text.AppendColor(" changed the topic to ", tcell.ColorGreen). + AppendStyle(evt.Content.Topic, tcell.StyleDefault.Underline(true)). + AppendColor(".", tcell.ColorGreen) + } + case mautrix.StateRoomName: + if len(evt.Content.Name) == 0 { + text = text.AppendColor(" removed the room name.", tcell.ColorGreen) + } else { + text = text.AppendColor(" changed the room name to ", tcell.ColorGreen). + AppendStyle(evt.Content.Name, tcell.StyleDefault.Underline(true)). + AppendColor(".", tcell.ColorGreen) + } + case mautrix.StateCanonicalAlias: + if len(evt.Content.Alias) == 0 { + text = text.AppendColor(" removed the main address of the room.", tcell.ColorGreen) + } else { + text = text.AppendColor(" changed the main address of the room to ", tcell.ColorGreen). + AppendStyle(evt.Content.Alias, tcell.StyleDefault.Underline(true)). + AppendColor(".", tcell.ColorGreen) + } + case mautrix.StateAliases: + text = ParseAliasEvent(evt, displayname) + } + ts := unixToTime(evt.Timestamp) + return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, mautrix.MessageType(evt.Type.Type), text, ts) +} + +func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { + displayname := evt.Sender + member := room.GetMember(evt.Sender) + if member != nil { + displayname = member.Displayname + } + if len(evt.Content.GetReplyTo()) > 0 { + evt.Content.RemoveReplyFallback() + roomID := evt.Content.RelatesTo.InReplyTo.RoomID + if len(roomID) == 0 { + roomID = room.ID + } + replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo()) + if replyToEvt != nil { + replyToEvt.Content.RemoveReplyFallback() + if len(replyToEvt.Content.FormattedBody) == 0 { + replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body) + } + evt.Content.FormattedBody = fmt.Sprintf( + "In reply to %[1]s
%[2]s


%[3]s", + replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody) + } else { + evt.Content.FormattedBody = fmt.Sprintf( + "In reply to unknown event https://matrix.to/#/%[1]s/%[2]s
%[3]s", + roomID, evt.Content.GetReplyTo(), evt.Content.FormattedBody) + } + } + ts := unixToTime(evt.Timestamp) + switch evt.Content.MsgType { + case "m.text", "m.notice", "m.emote": + if evt.Content.Format == mautrix.FormatHTML { + return NewHTMLMessage(evt, displayname, htmlp.Parse(room, evt, displayname)) + } + evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1) + return NewTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, ts) + case "m.image": + data, hs, id, err := matrix.Download(evt.Content.URL) + if err != nil { + debug.Printf("Failed to download %s: %v", evt.Content.URL, err) + } + return NewImageMessage(matrix, evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, hs, id, data, ts) + } + return nil +} + +func getMembershipChangeMessage(evt *mautrix.Event, membership, prevMembership mautrix.Membership, senderDisplayname, displayname, prevDisplayname string) (sender string, text tstring.TString) { + switch membership { + case "invite": + sender = "---" + text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", senderDisplayname, displayname), tcell.ColorGreen) + text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender)) + text.Colorize(len(senderDisplayname)+len(" invited "), len(displayname), widget.GetHashColor(*evt.StateKey)) + case "join": + sender = "-->" + text = tstring.NewColorTString(fmt.Sprintf("%s joined the room.", displayname), tcell.ColorGreen) + text.Colorize(0, len(displayname), widget.GetHashColor(*evt.StateKey)) + case "leave": + sender = "<--" + if evt.Sender != *evt.StateKey { + if prevMembership == mautrix.MembershipBan { + text = tstring.NewColorTString(fmt.Sprintf("%s unbanned %s", senderDisplayname, displayname), tcell.ColorGreen) + text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(*evt.StateKey)) + } else { + text = tstring.NewColorTString(fmt.Sprintf("%s kicked %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed) + text.Colorize(len(senderDisplayname)+len(" kicked "), len(displayname), widget.GetHashColor(*evt.StateKey)) + } + text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender)) + } else { + if displayname == *evt.StateKey { + displayname = prevDisplayname + } + text = tstring.NewColorTString(fmt.Sprintf("%s left the room.", displayname), tcell.ColorRed) + text.Colorize(0, len(displayname), widget.GetHashColor(*evt.StateKey)) + } + case "ban": + text = tstring.NewColorTString(fmt.Sprintf("%s banned %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed) + text.Colorize(len(senderDisplayname)+len(" banned "), len(displayname), widget.GetHashColor(*evt.StateKey)) + text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender)) + } + return +} + +func getMembershipEventContent(room *rooms.Room, evt *mautrix.Event) (sender string, text tstring.TString) { + member := room.GetMember(evt.Sender) + senderDisplayname := evt.Sender + if member != nil { + senderDisplayname = member.Displayname + } + + membership := evt.Content.Membership + displayname := evt.Content.Displayname + if len(displayname) == 0 { + displayname = *evt.StateKey + } + + prevMembership := mautrix.MembershipLeave + prevDisplayname := *evt.StateKey + if evt.Unsigned.PrevContent != nil { + prevMembership = evt.Unsigned.PrevContent.Membership + prevDisplayname = evt.Unsigned.PrevContent.Displayname + if len(prevDisplayname) == 0 { + prevDisplayname = *evt.StateKey + } + } + + if membership != prevMembership { + sender, text = getMembershipChangeMessage(evt, membership, prevMembership, senderDisplayname, displayname, prevDisplayname) + } else if displayname != prevDisplayname { + sender = "---" + color := widget.GetHashColor(*evt.StateKey) + text = tstring.NewBlankTString(). + AppendColor(prevDisplayname, color). + AppendColor(" changed their display name to ", tcell.ColorGreen). + AppendColor(displayname, color). + AppendColor(".", tcell.ColorGreen) + } + return +} + +func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage { + displayname, text := getMembershipEventContent(room, evt) + if len(text) == 0 { + return nil + } + + ts := unixToTime(evt.Timestamp) + return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, "m.room.member", text, ts) +} + +func ParseAliasEvent(evt *mautrix.Event, displayname string) tstring.TString { + var prevAliases []string + if evt.Unsigned.PrevContent != nil { + prevAliases = evt.Unsigned.PrevContent.Aliases + } + aliases := evt.Content.Aliases + var added, removed []tstring.TString +Outer1: + for _, oldAlias := range prevAliases { + for _, newAlias := range aliases { + if oldAlias == newAlias { + continue Outer1 + } + } + removed = append(removed, tstring.NewStyleTString(oldAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(oldAlias)).Underline(true))) + } +Outer2: + for _, newAlias := range aliases { + for _, oldAlias := range prevAliases { + if oldAlias == newAlias { + continue Outer2 + } + } + added = append(added, tstring.NewStyleTString(newAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(newAlias)).Underline(true))) + } + var addedStr, removedStr tstring.TString + if len(added) == 1 { + addedStr = added[0] + } else if len(added) > 1 { + addedStr = tstring. + Join(added[:len(added)-1], ", "). + Append(" and "). + AppendTString(added[len(added)-1]) + } + if len(removed) == 1 { + removedStr = removed[0] + } else if len(removed) > 1 { + removedStr = tstring. + Join(removed[:len(removed)-1], ", "). + Append(" and "). + AppendTString(removed[len(removed)-1]) + } + text := tstring.NewBlankTString() + if len(addedStr) > 0 && len(removedStr) > 0 { + text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen). + AppendTString(addedStr). + AppendColor(" and removed ", tcell.ColorGreen). + AppendTString(removedStr). + AppendColor(" as addresses for this room.", tcell.ColorGreen) + } else if len(addedStr) > 0 { + text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen). + AppendTString(addedStr). + AppendColor(" as addresses for this room.", tcell.ColorGreen) + } else if len(removedStr) > 0 { + text = text.AppendColor(fmt.Sprintf("%s removed ", displayname), tcell.ColorGreen). + AppendTString(removedStr). + AppendColor(" as addresses for this room.", tcell.ColorGreen) + } else { + return nil + } + return text +} -- cgit v1.2.3