Move syncer/room store changes from gomatrix fork to here, refactor and improve stuff
This commit is contained in:
		
							
								
								
									
										2
									
								
								debug.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								debug.go
									
									
									
									
									
								
							@@ -22,7 +22,7 @@ import (
 | 
				
			|||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DebugPaneHeight = 40
 | 
					const DebugPaneHeight = 35
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DebugPrinter interface {
 | 
					type DebugPrinter interface {
 | 
				
			||||||
	Printf(text string, args ...interface{})
 | 
						Printf(text string, args ...interface{})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,5 +126,6 @@ func (gmx *gomuks) UI() *GomuksUI {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	NewGomuks(true).Start()
 | 
						debug := os.Getenv("DEBUG")
 | 
				
			||||||
 | 
						NewGomuks(len(debug) > 0).Start()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										87
									
								
								matrix.go
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								matrix.go
									
									
									
									
									
								
							@@ -113,23 +113,29 @@ func (c *MatrixContainer) Stop() {
 | 
				
			|||||||
func (c *MatrixContainer) UpdateRoomList() {
 | 
					func (c *MatrixContainer) UpdateRoomList() {
 | 
				
			||||||
	rooms, err := c.client.JoinedRooms()
 | 
						rooms, err := c.client.JoinedRooms()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.debug.Print(err)
 | 
							c.debug.Print("Error fetching room list:", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.ui.MainView().SetRoomList(rooms.JoinedRooms)
 | 
						c.ui.MainView().SetRoomList(rooms.JoinedRooms)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) Start() {
 | 
					func (c *MatrixContainer) OnLogin() {
 | 
				
			||||||
	defer c.gmx.Recover()
 | 
					 | 
				
			||||||
	c.client.Store = c.config.Session
 | 
						c.client.Store = c.config.Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	syncer := gomatrix.NewDefaultSyncer(c.config.Session.MXID, c.config.Session)
 | 
						syncer := NewGomuksSyncer(c.config.Session)
 | 
				
			||||||
	syncer.OnEventType("m.room.message", c.HandleMessage)
 | 
						syncer.OnEventType("m.room.message", c.HandleMessage)
 | 
				
			||||||
	syncer.OnEventType("m.room.member", c.HandleMembership)
 | 
						syncer.OnEventType("m.room.member", c.HandleMembership)
 | 
				
			||||||
	syncer.OnEventType("m.typing", c.HandleTyping)
 | 
						syncer.OnEventType("m.typing", c.HandleTyping)
 | 
				
			||||||
	c.client.Syncer = syncer
 | 
						c.client.Syncer = syncer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.UpdateRoomList()
 | 
						c.UpdateRoomList()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *MatrixContainer) Start() {
 | 
				
			||||||
 | 
						defer c.gmx.Recover()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.OnLogin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.debug.Print("Starting sync...")
 | 
						c.debug.Print("Starting sync...")
 | 
				
			||||||
	c.running = true
 | 
						c.running = true
 | 
				
			||||||
@@ -151,65 +157,26 @@ func (c *MatrixContainer) Start() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
 | 
					func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
 | 
				
			||||||
	message, _ := evt.Content["body"].(string)
 | 
						room, message := c.ui.MainView().ProcessMessageEvent(evt)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	room := c.ui.MainView().GetRoom(evt.RoomID)
 | 
					 | 
				
			||||||
	if room != nil {
 | 
						if room != nil {
 | 
				
			||||||
		room.AddMessage(evt.ID, evt.Sender, message, unixToTime(evt.Timestamp))
 | 
							room.AddMessage(message, AppendMessage)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func unixToTime(unix int64) time.Time {
 | 
					 | 
				
			||||||
	timestamp := time.Now()
 | 
					 | 
				
			||||||
	if unix != 0 {
 | 
					 | 
				
			||||||
		timestamp = time.Unix(unix/1000, unix%1000*1000)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return timestamp
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
 | 
					func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
 | 
				
			||||||
	membership, _ := evt.Content["membership"].(string)
 | 
						const Hour = 1 * 60 * 60 * 1000
 | 
				
			||||||
	if evt.StateKey != nil && *evt.StateKey == c.config.Session.MXID {
 | 
						if evt.Unsigned.Age > Hour {
 | 
				
			||||||
		prevMembership := "leave"
 | 
					 | 
				
			||||||
		if evt.Unsigned.PrevContent != nil {
 | 
					 | 
				
			||||||
			prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if membership == prevMembership {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if membership == "join" {
 | 
					 | 
				
			||||||
			c.ui.MainView().AddRoom(evt.RoomID)
 | 
					 | 
				
			||||||
		} else if membership == "leave" {
 | 
					 | 
				
			||||||
			c.ui.MainView().RemoveRoom(evt.RoomID)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	room := c.ui.MainView().GetRoom(evt.RoomID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO this shouldn't be necessary
 | 
					 | 
				
			||||||
	room.room.UpdateState(evt)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						room, message := c.ui.MainView().ProcessMembershipEvent(evt, true)
 | 
				
			||||||
	if room != nil {
 | 
						if room != nil {
 | 
				
			||||||
		var message, sender string
 | 
							// TODO this shouldn't be necessary
 | 
				
			||||||
		if membership == "invite" {
 | 
							room.room.UpdateState(evt)
 | 
				
			||||||
			sender = "---"
 | 
							// TODO This should probably also be in a different place
 | 
				
			||||||
			message = fmt.Sprintf("%s invited %s.", evt.Sender, *evt.StateKey)
 | 
					 | 
				
			||||||
		} else if membership == "join" {
 | 
					 | 
				
			||||||
			sender = "-->"
 | 
					 | 
				
			||||||
			message = fmt.Sprintf("%s joined the room.", *evt.StateKey)
 | 
					 | 
				
			||||||
		} else if membership == "leave" {
 | 
					 | 
				
			||||||
			sender = "<--"
 | 
					 | 
				
			||||||
			if evt.Sender != *evt.StateKey {
 | 
					 | 
				
			||||||
				reason, _ := evt.Content["reason"].(string)
 | 
					 | 
				
			||||||
				message = fmt.Sprintf("%s kicked %s: %s", evt.Sender, *evt.StateKey, reason)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				message = fmt.Sprintf("%s left the room.", *evt.StateKey)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		room.UpdateUserList()
 | 
							room.UpdateUserList()
 | 
				
			||||||
		room.AddMessage(evt.ID, sender, message, unixToTime(evt.Timestamp))
 | 
					
 | 
				
			||||||
 | 
							room.AddMessage(message, AppendMessage)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -268,14 +235,22 @@ func (c *MatrixContainer) getState(roomID string) []*gomatrix.Event {
 | 
				
			|||||||
	content := make([]*gomatrix.Event, 0)
 | 
						content := make([]*gomatrix.Event, 0)
 | 
				
			||||||
	err := c.client.StateEvent(roomID, "", "", &content)
 | 
						err := c.client.StateEvent(roomID, "", "", &content)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.debug.Print(err)
 | 
							c.debug.Print("Error getting state of", roomID, err)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return content
 | 
						return content
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) GetRoom(roomID string) *gomatrix.Room {
 | 
					func (c *MatrixContainer) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
 | 
				
			||||||
	room := c.config.Session.LoadRoom(roomID)
 | 
						resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return resp.Chunk, resp.End, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *MatrixContainer) GetRoom(roomID string) *Room {
 | 
				
			||||||
 | 
						room := c.config.Session.GetRoom(roomID)
 | 
				
			||||||
	if room != nil && len(room.State) == 0 {
 | 
						if room != nil && len(room.State) == 0 {
 | 
				
			||||||
		events := c.getState(room.ID)
 | 
							events := c.getState(room.ID)
 | 
				
			||||||
		if events != nil {
 | 
							if events != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										202
									
								
								message-view.go
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								message-view.go
									
									
									
									
									
								
							@@ -17,6 +17,7 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -27,16 +28,27 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Message struct {
 | 
					type Message struct {
 | 
				
			||||||
	ID           string
 | 
						ID        string
 | 
				
			||||||
	Sender       string
 | 
						Sender    string
 | 
				
			||||||
	Text         string
 | 
						Text      string
 | 
				
			||||||
	Timestamp    string
 | 
						Timestamp string
 | 
				
			||||||
	RenderSender bool
 | 
						Date      string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	buffer      []string
 | 
						buffer      []string
 | 
				
			||||||
	senderColor tcell.Color
 | 
						senderColor tcell.Color
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewMessage(id, sender, text, timestamp, date string, senderColor tcell.Color) *Message {
 | 
				
			||||||
 | 
						return &Message{
 | 
				
			||||||
 | 
							ID:          id,
 | 
				
			||||||
 | 
							Sender:      sender,
 | 
				
			||||||
 | 
							Text:        text,
 | 
				
			||||||
 | 
							Timestamp:   timestamp,
 | 
				
			||||||
 | 
							Date:        date,
 | 
				
			||||||
 | 
							senderColor: senderColor,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
 | 
						boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
 | 
				
			||||||
	spacePattern    = regexp.MustCompile(`\s+`)
 | 
						spacePattern    = regexp.MustCompile(`\s+`)
 | 
				
			||||||
@@ -80,6 +92,7 @@ type MessageView struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ScrollOffset    int
 | 
						ScrollOffset    int
 | 
				
			||||||
	MaxSenderWidth  int
 | 
						MaxSenderWidth  int
 | 
				
			||||||
 | 
						DateFormat      string
 | 
				
			||||||
	TimestampFormat string
 | 
						TimestampFormat string
 | 
				
			||||||
	TimestampWidth  int
 | 
						TimestampWidth  int
 | 
				
			||||||
	Separator       rune
 | 
						Separator       rune
 | 
				
			||||||
@@ -92,18 +105,23 @@ type MessageView struct {
 | 
				
			|||||||
	lastDisplayMessage  int
 | 
						lastDisplayMessage  int
 | 
				
			||||||
	totalHeight         int
 | 
						totalHeight         int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	messages []*Message
 | 
						messageIDs map[string]bool
 | 
				
			||||||
 | 
						messages   []*Message
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewMessageView() *MessageView {
 | 
					func NewMessageView() *MessageView {
 | 
				
			||||||
	return &MessageView{
 | 
						return &MessageView{
 | 
				
			||||||
		Box:             tview.NewBox(),
 | 
							Box:             tview.NewBox(),
 | 
				
			||||||
		MaxSenderWidth:  20,
 | 
							MaxSenderWidth:  20,
 | 
				
			||||||
 | 
							DateFormat:      "January _2, 2006",
 | 
				
			||||||
		TimestampFormat: "15:04:05",
 | 
							TimestampFormat: "15:04:05",
 | 
				
			||||||
		TimestampWidth:  8,
 | 
							TimestampWidth:  8,
 | 
				
			||||||
		Separator:       '|',
 | 
							Separator:       '|',
 | 
				
			||||||
		ScrollOffset:    0,
 | 
							ScrollOffset:    0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							messages:   make([]*Message, 0),
 | 
				
			||||||
 | 
							messageIDs: make(map[string]bool),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		widestSender:        5,
 | 
							widestSender:        5,
 | 
				
			||||||
		prevWidth:           -1,
 | 
							prevWidth:           -1,
 | 
				
			||||||
		prevHeight:          -1,
 | 
							prevHeight:          -1,
 | 
				
			||||||
@@ -114,62 +132,94 @@ func NewMessageView() *MessageView {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) recalculateBuffers(width int) {
 | 
					func (view *MessageView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
 | 
				
			||||||
	width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
						return NewMessage(id, sender, text,
 | 
				
			||||||
	for _, message := range view.messages {
 | 
							timestamp.Format(view.TimestampFormat),
 | 
				
			||||||
		message.calculateBuffer(width)
 | 
							timestamp.Format(view.DateFormat),
 | 
				
			||||||
	}
 | 
							getColor(sender))
 | 
				
			||||||
	view.prevWidth = width
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) AddMessage(id, sender, text string, timestamp time.Time) {
 | 
					func (view *MessageView) recalculateBuffers() {
 | 
				
			||||||
 | 
						_, _, width, _ := view.GetInnerRect()
 | 
				
			||||||
 | 
						width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
				
			||||||
 | 
						if width != view.prevWidth {
 | 
				
			||||||
 | 
							for _, message := range view.messages {
 | 
				
			||||||
 | 
								message.calculateBuffer(width)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							view.prevWidth = width
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MessageView) updateWidestSender(sender string) {
 | 
				
			||||||
	if len(sender) > view.widestSender {
 | 
						if len(sender) > view.widestSender {
 | 
				
			||||||
		view.widestSender = len(sender)
 | 
							view.widestSender = len(sender)
 | 
				
			||||||
		if view.widestSender > view.MaxSenderWidth {
 | 
							if view.widestSender > view.MaxSenderWidth {
 | 
				
			||||||
			view.widestSender = view.MaxSenderWidth
 | 
								view.widestSender = view.MaxSenderWidth
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	message := &Message{
 | 
					 | 
				
			||||||
		ID:           id,
 | 
					 | 
				
			||||||
		Sender:       sender,
 | 
					 | 
				
			||||||
		RenderSender: true,
 | 
					 | 
				
			||||||
		Text:         text,
 | 
					 | 
				
			||||||
		Timestamp:    timestamp.Format(view.TimestampFormat),
 | 
					 | 
				
			||||||
		senderColor:  getColor(sender),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	_, _, width, height := view.GetInnerRect()
 | 
					 | 
				
			||||||
	width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
					 | 
				
			||||||
	message.calculateBuffer(width)
 | 
					 | 
				
			||||||
	if view.ScrollOffset > 0 {
 | 
					 | 
				
			||||||
		view.ScrollOffset += len(message.buffer)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(view.messages) > 0 && view.messages[len(view.messages)-1].Sender == message.Sender {
 | 
					 | 
				
			||||||
		message.RenderSender = false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	view.messages = append(view.messages, message)
 | 
					 | 
				
			||||||
	view.recalculateHeight(height)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) recalculateHeight(height int) {
 | 
					const (
 | 
				
			||||||
	view.firstDisplayMessage = -1
 | 
						AppendMessage  int = iota
 | 
				
			||||||
	view.lastDisplayMessage = -1
 | 
						PrependMessage
 | 
				
			||||||
	view.totalHeight = 0
 | 
					)
 | 
				
			||||||
	for i := len(view.messages) - 1; i >= 0; i-- {
 | 
					 | 
				
			||||||
		prevTotalHeight := view.totalHeight
 | 
					 | 
				
			||||||
		view.totalHeight += len(view.messages[i].buffer)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if view.totalHeight < view.ScrollOffset {
 | 
					func (view *MessageView) AddMessage(message *Message, direction int) {
 | 
				
			||||||
			continue
 | 
						_, messageExists := view.messageIDs[message.ID]
 | 
				
			||||||
		} else if view.firstDisplayMessage == -1 {
 | 
						if messageExists {
 | 
				
			||||||
			view.lastDisplayMessage = i
 | 
							return
 | 
				
			||||||
			view.firstDisplayMessage = i
 | 
						}
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
 | 
						view.updateWidestSender(message.Sender)
 | 
				
			||||||
		if prevTotalHeight < height+view.ScrollOffset {
 | 
					
 | 
				
			||||||
			view.lastDisplayMessage = i
 | 
						_, _, width, _ := view.GetInnerRect()
 | 
				
			||||||
		}
 | 
						width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
				
			||||||
 | 
						message.calculateBuffer(width)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if direction == AppendMessage {
 | 
				
			||||||
 | 
							if view.ScrollOffset > 0 {
 | 
				
			||||||
 | 
								view.ScrollOffset += len(message.buffer)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							view.messages = append(view.messages, message)
 | 
				
			||||||
 | 
						} else if direction == PrependMessage {
 | 
				
			||||||
 | 
							view.messages = append([]*Message{message}, view.messages...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						view.messageIDs[message.ID] = true
 | 
				
			||||||
 | 
						view.recalculateHeight()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MessageView) recalculateHeight() {
 | 
				
			||||||
 | 
						_, _, width, height := view.GetInnerRect()
 | 
				
			||||||
 | 
						if height != view.prevHeight || width != view.prevWidth || view.ScrollOffset != view.prevScrollOffset {
 | 
				
			||||||
 | 
							view.firstDisplayMessage = -1
 | 
				
			||||||
 | 
							view.lastDisplayMessage = -1
 | 
				
			||||||
 | 
							view.totalHeight = 0
 | 
				
			||||||
 | 
							prevDate := ""
 | 
				
			||||||
 | 
							for i := len(view.messages) - 1; i >= 0; i-- {
 | 
				
			||||||
 | 
								prevTotalHeight := view.totalHeight
 | 
				
			||||||
 | 
								message := view.messages[i]
 | 
				
			||||||
 | 
								view.totalHeight += len(message.buffer)
 | 
				
			||||||
 | 
								if message.Date != prevDate {
 | 
				
			||||||
 | 
									if len(prevDate) != 0 {
 | 
				
			||||||
 | 
										view.totalHeight++
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									prevDate = message.Date
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if view.totalHeight < view.ScrollOffset {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								} else if view.firstDisplayMessage == -1 {
 | 
				
			||||||
 | 
									view.lastDisplayMessage = i
 | 
				
			||||||
 | 
									view.firstDisplayMessage = i
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if prevTotalHeight < height+view.ScrollOffset {
 | 
				
			||||||
 | 
									view.lastDisplayMessage = i
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							view.prevScrollOffset = view.ScrollOffset
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	view.prevScrollOffset = view.ScrollOffset
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) PageUp() {
 | 
					func (view *MessageView) PageUp() {
 | 
				
			||||||
@@ -230,43 +280,67 @@ const (
 | 
				
			|||||||
func (view *MessageView) Draw(screen tcell.Screen) {
 | 
					func (view *MessageView) Draw(screen tcell.Screen) {
 | 
				
			||||||
	view.Box.Draw(screen)
 | 
						view.Box.Draw(screen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	x, y, width, height := view.GetInnerRect()
 | 
						x, y, _, height := view.GetInnerRect()
 | 
				
			||||||
	if width != view.prevWidth {
 | 
						view.recalculateBuffers()
 | 
				
			||||||
		view.recalculateBuffers(width)
 | 
						view.recalculateHeight()
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if height != view.prevHeight || width != view.prevWidth || view.ScrollOffset != view.prevScrollOffset {
 | 
						if view.firstDisplayMessage == -1 || view.lastDisplayMessage == -1 {
 | 
				
			||||||
		view.recalculateHeight(height)
 | 
							view.writeLine(screen, "It's quite empty in here.", x, y+height, tcell.ColorDefault)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	usernameOffsetX := view.TimestampWidth + TimestampSenderGap
 | 
						usernameOffsetX := view.TimestampWidth + TimestampSenderGap
 | 
				
			||||||
	messageOffsetX := usernameOffsetX + view.widestSender + SenderMessageGap
 | 
						messageOffsetX := usernameOffsetX + view.widestSender + SenderMessageGap
 | 
				
			||||||
 | 
					 | 
				
			||||||
	separatorX := x + usernameOffsetX + view.widestSender + SenderSeparatorGap
 | 
						separatorX := x + usernameOffsetX + view.widestSender + SenderSeparatorGap
 | 
				
			||||||
	for separatorY := y; separatorY < y+height; separatorY++ {
 | 
						for separatorY := y; separatorY < y+height; separatorY++ {
 | 
				
			||||||
		screen.SetContent(separatorX, separatorY, view.Separator, nil, tcell.StyleDefault)
 | 
							screen.SetContent(separatorX, separatorY, view.Separator, nil, tcell.StyleDefault)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if view.firstDisplayMessage == -1 || view.lastDisplayMessage == -1 {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	writeOffset := 0
 | 
						writeOffset := 0
 | 
				
			||||||
 | 
						prevDate := ""
 | 
				
			||||||
 | 
						prevSender := ""
 | 
				
			||||||
 | 
						prevSenderLine := -1
 | 
				
			||||||
	for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
 | 
						for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
 | 
				
			||||||
		message := view.messages[i]
 | 
							message := view.messages[i]
 | 
				
			||||||
		messageHeight := len(message.buffer)
 | 
							messageHeight := len(message.buffer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Show message when the date changes.
 | 
				
			||||||
 | 
							if message.Date != prevDate {
 | 
				
			||||||
 | 
								if len(prevDate) > 0 {
 | 
				
			||||||
 | 
									writeOffset++
 | 
				
			||||||
 | 
									view.writeLine(
 | 
				
			||||||
 | 
										screen, fmt.Sprintf("Date changed to %s", prevDate),
 | 
				
			||||||
 | 
										x+messageOffsetX, y+height-writeOffset, tcell.ColorGreen)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								prevDate = message.Date
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		senderAtLine := y + height - writeOffset - messageHeight
 | 
							senderAtLine := y + height - writeOffset - messageHeight
 | 
				
			||||||
 | 
							// The message may be only partially on screen, so we need to make sure the sender
 | 
				
			||||||
 | 
							// is on screen even when the message is not shown completely.
 | 
				
			||||||
		if senderAtLine < y {
 | 
							if senderAtLine < y {
 | 
				
			||||||
			senderAtLine = y
 | 
								senderAtLine = y
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		view.writeLine(screen, message.Timestamp, x, senderAtLine, tcell.ColorDefault)
 | 
							view.writeLine(screen, message.Timestamp, x, senderAtLine, tcell.ColorDefault)
 | 
				
			||||||
		if message.RenderSender || i == view.lastDisplayMessage {
 | 
							view.writeLineRight(screen, message.Sender,
 | 
				
			||||||
			view.writeLineRight(screen, message.Sender,
 | 
								x+usernameOffsetX, senderAtLine,
 | 
				
			||||||
				x+usernameOffsetX, senderAtLine,
 | 
								view.widestSender, message.senderColor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if message.Sender == prevSender {
 | 
				
			||||||
 | 
								// Sender is same as previous. We're looping from bottom to top, and we want the
 | 
				
			||||||
 | 
								// sender name only on the topmost message, so clear out the duplicate sender name
 | 
				
			||||||
 | 
								// below.
 | 
				
			||||||
 | 
								view.writeLineRight(screen, strings.Repeat(" ", view.widestSender),
 | 
				
			||||||
 | 
									x+usernameOffsetX, prevSenderLine,
 | 
				
			||||||
				view.widestSender, message.senderColor)
 | 
									view.widestSender, message.senderColor)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							prevSender = message.Sender
 | 
				
			||||||
 | 
							prevSenderLine = senderAtLine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for num, line := range message.buffer {
 | 
							for num, line := range message.buffer {
 | 
				
			||||||
			offsetY := height - messageHeight - writeOffset + num
 | 
								offsetY := height - messageHeight - writeOffset + num
 | 
				
			||||||
 | 
								// Only render message if it's within the message view.
 | 
				
			||||||
			if offsetY >= 0 {
 | 
								if offsetY >= 0 {
 | 
				
			||||||
				view.writeLine(screen, line, x+messageOffsetX, y+offsetY, tcell.ColorDefault)
 | 
									view.writeLine(screen, line, x+messageOffsetX, y+offsetY, tcell.ColorDefault)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								room-view.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								room-view.go
									
									
									
									
									
								
							@@ -24,7 +24,6 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
					 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,7 +34,7 @@ type RoomView struct {
 | 
				
			|||||||
	content  *MessageView
 | 
						content  *MessageView
 | 
				
			||||||
	status   *tview.TextView
 | 
						status   *tview.TextView
 | 
				
			||||||
	userList *tview.TextView
 | 
						userList *tview.TextView
 | 
				
			||||||
	room     *gomatrix.Room
 | 
						room     *Room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	parent *MainView
 | 
						parent *MainView
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -52,7 +51,7 @@ func init() {
 | 
				
			|||||||
	sort.Sort(sort.StringSlice(colorNames))
 | 
						sort.Sort(sort.StringSlice(colorNames))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewRoomView(parent *MainView, room *gomatrix.Room) *RoomView {
 | 
					func NewRoomView(parent *MainView, room *Room) *RoomView {
 | 
				
			||||||
	view := &RoomView{
 | 
						view := &RoomView{
 | 
				
			||||||
		Box:      tview.NewBox(),
 | 
							Box:      tview.NewBox(),
 | 
				
			||||||
		topic:    tview.NewTextView(),
 | 
							topic:    tview.NewTextView(),
 | 
				
			||||||
@@ -166,11 +165,15 @@ func (view *RoomView) UpdateUserList() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *RoomView) AddMessage(id, sender, message string, timestamp time.Time) {
 | 
					func (view *RoomView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
 | 
				
			||||||
	member := view.room.GetMember(sender)
 | 
						member := view.room.GetMember(sender)
 | 
				
			||||||
	if member != nil {
 | 
						if member != nil {
 | 
				
			||||||
		sender = member.DisplayName
 | 
							sender = member.DisplayName
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	view.content.AddMessage(id, sender, message, timestamp)
 | 
						return view.content.NewMessage(id, sender, text, timestamp)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *RoomView) AddMessage(message *Message, direction int) {
 | 
				
			||||||
 | 
						view.content.AddMessage(message, direction)
 | 
				
			||||||
	view.parent.Render()
 | 
						view.parent.Render()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										175
									
								
								room.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								room.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
				
			|||||||
 | 
					// gomuks - A terminal Matrix client written in Go.
 | 
				
			||||||
 | 
					// Copyright (C) 2018 Tulir Asokan
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					// it under the terms of the GNU 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 General Public License for more details.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"maunium.net/go/gomatrix"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Room represents a single Matrix room.
 | 
				
			||||||
 | 
					type Room struct {
 | 
				
			||||||
 | 
						*gomatrix.Room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PrevBatch   string
 | 
				
			||||||
 | 
						memberCache map[string]*RoomMember
 | 
				
			||||||
 | 
						nameCache   string
 | 
				
			||||||
 | 
						topicCache  string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateState updates the room's current state with the given Event. This will clobber events based
 | 
				
			||||||
 | 
					// on the type/state_key combination.
 | 
				
			||||||
 | 
					func (room *Room) UpdateState(event *gomatrix.Event) {
 | 
				
			||||||
 | 
						_, exists := room.State[event.Type]
 | 
				
			||||||
 | 
						if !exists {
 | 
				
			||||||
 | 
							room.State[event.Type] = make(map[string]*gomatrix.Event)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch event.Type {
 | 
				
			||||||
 | 
						case "m.room.member":
 | 
				
			||||||
 | 
							room.memberCache = nil
 | 
				
			||||||
 | 
						case "m.room.name":
 | 
				
			||||||
 | 
						case "m.room.canonical_alias":
 | 
				
			||||||
 | 
						case "m.room.alias":
 | 
				
			||||||
 | 
							room.nameCache = ""
 | 
				
			||||||
 | 
						case "m.room.topic":
 | 
				
			||||||
 | 
							room.topicCache = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						room.State[event.Type][*event.StateKey] = event
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetStateEvent returns the state event for the given type/state_key combo, or nil.
 | 
				
			||||||
 | 
					func (room *Room) GetStateEvent(eventType string, stateKey string) *gomatrix.Event {
 | 
				
			||||||
 | 
						stateEventMap, _ := room.State[eventType]
 | 
				
			||||||
 | 
						event, _ := stateEventMap[stateKey]
 | 
				
			||||||
 | 
						return event
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetStateEvents returns the state events for the given type.
 | 
				
			||||||
 | 
					func (room *Room) GetStateEvents(eventType string) map[string]*gomatrix.Event {
 | 
				
			||||||
 | 
						stateEventMap, _ := room.State[eventType]
 | 
				
			||||||
 | 
						return stateEventMap
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTopic returns the topic of the room.
 | 
				
			||||||
 | 
					func (room *Room) GetTopic() string {
 | 
				
			||||||
 | 
						if len(room.topicCache) == 0 {
 | 
				
			||||||
 | 
							topicEvt := room.GetStateEvent("m.room.topic", "")
 | 
				
			||||||
 | 
							if topicEvt != nil {
 | 
				
			||||||
 | 
								room.topicCache, _ = topicEvt.Content["topic"].(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return room.topicCache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTitle returns the display title of the room.
 | 
				
			||||||
 | 
					func (room *Room) GetTitle() string {
 | 
				
			||||||
 | 
						if len(room.nameCache) == 0 {
 | 
				
			||||||
 | 
							nameEvt := room.GetStateEvent("m.room.name", "")
 | 
				
			||||||
 | 
							if nameEvt != nil {
 | 
				
			||||||
 | 
								room.nameCache, _ = nameEvt.Content["name"].(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(room.nameCache) == 0 {
 | 
				
			||||||
 | 
							canonicalAliasEvt := room.GetStateEvent("m.room.canonical_alias", "")
 | 
				
			||||||
 | 
							if canonicalAliasEvt != nil {
 | 
				
			||||||
 | 
								room.nameCache, _ = canonicalAliasEvt.Content["alias"].(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(room.nameCache) == 0 {
 | 
				
			||||||
 | 
							// TODO the spec says clients should not use m.room.aliases for room names.
 | 
				
			||||||
 | 
							//      However, Riot also uses m.room.aliases, so this is here now.
 | 
				
			||||||
 | 
							aliasEvents := room.GetStateEvents("m.room.aliases")
 | 
				
			||||||
 | 
							for _, event := range aliasEvents {
 | 
				
			||||||
 | 
								aliases, _ := event.Content["aliases"].([]interface{})
 | 
				
			||||||
 | 
								if len(aliases) > 0 {
 | 
				
			||||||
 | 
									room.nameCache, _ = aliases[0].(string)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(room.nameCache) == 0 {
 | 
				
			||||||
 | 
							// TODO follow other title rules in spec
 | 
				
			||||||
 | 
							room.nameCache = room.ID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return room.nameCache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RoomMember struct {
 | 
				
			||||||
 | 
						UserID      string `json:"-"`
 | 
				
			||||||
 | 
						Membership  string `json:"membership"`
 | 
				
			||||||
 | 
						DisplayName string `json:"displayname"`
 | 
				
			||||||
 | 
						AvatarURL   string `json:"avatar_url"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func eventToRoomMember(userID string, event *gomatrix.Event) *RoomMember {
 | 
				
			||||||
 | 
						if event == nil {
 | 
				
			||||||
 | 
							return &RoomMember{
 | 
				
			||||||
 | 
								UserID:     userID,
 | 
				
			||||||
 | 
								Membership: "leave",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						membership, _ := event.Content["membership"].(string)
 | 
				
			||||||
 | 
						avatarURL, _ := event.Content["avatar_url"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						displayName, _ := event.Content["displayname"].(string)
 | 
				
			||||||
 | 
						if len(displayName) == 0 {
 | 
				
			||||||
 | 
							displayName = userID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &RoomMember{
 | 
				
			||||||
 | 
							UserID:      userID,
 | 
				
			||||||
 | 
							Membership:  membership,
 | 
				
			||||||
 | 
							DisplayName: displayName,
 | 
				
			||||||
 | 
							AvatarURL:   avatarURL,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) createMemberCache() map[string]*RoomMember {
 | 
				
			||||||
 | 
						cache := make(map[string]*RoomMember)
 | 
				
			||||||
 | 
						events := room.GetStateEvents("m.room.member")
 | 
				
			||||||
 | 
						if events != nil {
 | 
				
			||||||
 | 
							for userID, event := range events {
 | 
				
			||||||
 | 
								member := eventToRoomMember(userID, event)
 | 
				
			||||||
 | 
								if member.Membership != "leave" {
 | 
				
			||||||
 | 
									cache[member.UserID] = member
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						room.memberCache = cache
 | 
				
			||||||
 | 
						return cache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) GetMembers() map[string]*RoomMember {
 | 
				
			||||||
 | 
						if len(room.memberCache) == 0 {
 | 
				
			||||||
 | 
							room.createMemberCache()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return room.memberCache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) GetMember(userID string) *RoomMember {
 | 
				
			||||||
 | 
						if len(room.memberCache) == 0 {
 | 
				
			||||||
 | 
							room.createMemberCache()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						member, _ := room.memberCache[userID]
 | 
				
			||||||
 | 
						return member
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewRoom creates a new Room with the given ID
 | 
				
			||||||
 | 
					func NewRoom(roomID string) *Room {
 | 
				
			||||||
 | 
						return &Room{
 | 
				
			||||||
 | 
							Room: gomatrix.NewRoom(roomID),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								session.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								session.go
									
									
									
									
									
								
							@@ -30,7 +30,7 @@ type Session struct {
 | 
				
			|||||||
	AccessToken string
 | 
						AccessToken string
 | 
				
			||||||
	NextBatch   string
 | 
						NextBatch   string
 | 
				
			||||||
	FilterID    string
 | 
						FilterID    string
 | 
				
			||||||
	Rooms       map[string]*gomatrix.Room
 | 
						Rooms       map[string]*Room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	debug DebugPrinter `json:"-"`
 | 
						debug DebugPrinter `json:"-"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -44,11 +44,18 @@ func (config *Config) NewSession(mxid string) *Session {
 | 
				
			|||||||
	return &Session{
 | 
						return &Session{
 | 
				
			||||||
		MXID:  mxid,
 | 
							MXID:  mxid,
 | 
				
			||||||
		path:  filepath.Join(config.dir, mxid+".session"),
 | 
							path:  filepath.Join(config.dir, mxid+".session"),
 | 
				
			||||||
		Rooms: make(map[string]*gomatrix.Room),
 | 
							Rooms: make(map[string]*Room),
 | 
				
			||||||
		debug: config.debug,
 | 
							debug: config.debug,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Session) Clear() {
 | 
				
			||||||
 | 
						s.Rooms = make(map[string]*Room)
 | 
				
			||||||
 | 
						s.NextBatch = ""
 | 
				
			||||||
 | 
						s.FilterID = ""
 | 
				
			||||||
 | 
						s.Save()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) Load() {
 | 
					func (s *Session) Load() {
 | 
				
			||||||
	data, err := ioutil.ReadFile(s.path)
 | 
						data, err := ioutil.ReadFile(s.path)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -85,15 +92,20 @@ func (s *Session) LoadNextBatch(_ string) string {
 | 
				
			|||||||
	return s.NextBatch
 | 
						return s.NextBatch
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) LoadRoom(mxid string) *gomatrix.Room {
 | 
					func (s *Session) GetRoom(mxid string) *Room {
 | 
				
			||||||
	room, _ := s.Rooms[mxid]
 | 
						room, _ := s.Rooms[mxid]
 | 
				
			||||||
	if room == nil {
 | 
						if room == nil {
 | 
				
			||||||
		room = gomatrix.NewRoom(mxid)
 | 
							room = NewRoom(mxid)
 | 
				
			||||||
		s.SaveRoom(room)
 | 
							s.Rooms[room.ID] = room
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return room
 | 
						return room
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Session) PutRoom(room *Room) {
 | 
				
			||||||
 | 
						s.Rooms[room.ID] = room
 | 
				
			||||||
 | 
						s.Save()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) SaveFilterID(_, filterID string) {
 | 
					func (s *Session) SaveFilterID(_, filterID string) {
 | 
				
			||||||
	s.FilterID = filterID
 | 
						s.FilterID = filterID
 | 
				
			||||||
	s.Save()
 | 
						s.Save()
 | 
				
			||||||
@@ -104,7 +116,11 @@ func (s *Session) SaveNextBatch(_, nextBatch string) {
 | 
				
			|||||||
	s.Save()
 | 
						s.Save()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Session) LoadRoom(mxid string) *gomatrix.Room {
 | 
				
			||||||
 | 
						return s.GetRoom(mxid).Room
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) SaveRoom(room *gomatrix.Room) {
 | 
					func (s *Session) SaveRoom(room *gomatrix.Room) {
 | 
				
			||||||
	s.Rooms[room.ID] = room
 | 
						s.GetRoom(room.ID).Room = room
 | 
				
			||||||
	s.Save()
 | 
						s.Save()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										125
									
								
								sync.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								sync.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"runtime/debug"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"maunium.net/go/gomatrix"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
 | 
				
			||||||
 | 
					// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
 | 
				
			||||||
 | 
					// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
 | 
				
			||||||
 | 
					type GomuksSyncer struct {
 | 
				
			||||||
 | 
						Session *Session
 | 
				
			||||||
 | 
						listeners map[string][]gomatrix.OnEventListener // event type to listeners array
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewGomuksSyncer returns an instantiated GomuksSyncer
 | 
				
			||||||
 | 
					func NewGomuksSyncer(session *Session) *GomuksSyncer {
 | 
				
			||||||
 | 
						return &GomuksSyncer{
 | 
				
			||||||
 | 
							Session:    session,
 | 
				
			||||||
 | 
							listeners: make(map[string][]gomatrix.OnEventListener),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
 | 
				
			||||||
 | 
						if !s.shouldProcessResponse(res, since) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// gdebug.Print("Processing sync response", since, res)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if r := recover(); r != nil {
 | 
				
			||||||
 | 
								err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.Session.MXID, since, r, debug.Stack())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, event := range res.Presence.Events {
 | 
				
			||||||
 | 
							s.notifyListeners(&event)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for roomID, roomData := range res.Rooms.Join {
 | 
				
			||||||
 | 
							room := s.Session.GetRoom(roomID)
 | 
				
			||||||
 | 
							for _, event := range roomData.State.Events {
 | 
				
			||||||
 | 
								event.RoomID = roomID
 | 
				
			||||||
 | 
								room.UpdateState(&event)
 | 
				
			||||||
 | 
								s.notifyListeners(&event)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, event := range roomData.Timeline.Events {
 | 
				
			||||||
 | 
								event.RoomID = roomID
 | 
				
			||||||
 | 
								s.notifyListeners(&event)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, event := range roomData.Ephemeral.Events {
 | 
				
			||||||
 | 
								event.RoomID = roomID
 | 
				
			||||||
 | 
								s.notifyListeners(&event)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(room.PrevBatch) == 0 {
 | 
				
			||||||
 | 
								room.PrevBatch = roomData.Timeline.PrevBatch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for roomID, roomData := range res.Rooms.Invite {
 | 
				
			||||||
 | 
							room := s.Session.GetRoom(roomID)
 | 
				
			||||||
 | 
							for _, event := range roomData.State.Events {
 | 
				
			||||||
 | 
								event.RoomID = roomID
 | 
				
			||||||
 | 
								room.UpdateState(&event)
 | 
				
			||||||
 | 
								s.notifyListeners(&event)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for roomID, roomData := range res.Rooms.Leave {
 | 
				
			||||||
 | 
							room := s.Session.GetRoom(roomID)
 | 
				
			||||||
 | 
							for _, event := range roomData.Timeline.Events {
 | 
				
			||||||
 | 
								if event.StateKey != nil {
 | 
				
			||||||
 | 
									event.RoomID = roomID
 | 
				
			||||||
 | 
									room.UpdateState(&event)
 | 
				
			||||||
 | 
									s.notifyListeners(&event)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(room.PrevBatch) == 0 {
 | 
				
			||||||
 | 
								room.PrevBatch = roomData.Timeline.PrevBatch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OnEventType allows callers to be notified when there are new events for the given event type.
 | 
				
			||||||
 | 
					// There are no duplicate checks.
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) OnEventType(eventType string, callback gomatrix.OnEventListener) {
 | 
				
			||||||
 | 
						_, exists := s.listeners[eventType]
 | 
				
			||||||
 | 
						if !exists {
 | 
				
			||||||
 | 
							s.listeners[eventType] = []gomatrix.OnEventListener{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.listeners[eventType] = append(s.listeners[eventType], callback)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
 | 
				
			||||||
 | 
					// stuff that shouldn't be processed.
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) shouldProcessResponse(resp *gomatrix.RespSync, since string) bool {
 | 
				
			||||||
 | 
						if since == "" {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) notifyListeners(event *gomatrix.Event) {
 | 
				
			||||||
 | 
						listeners, exists := s.listeners[event.Type]
 | 
				
			||||||
 | 
						if !exists {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, fn := range listeners {
 | 
				
			||||||
 | 
							fn(event)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) OnFailedSync(res *gomatrix.RespSync, err error) (time.Duration, error) {
 | 
				
			||||||
 | 
						return 10 * time.Second, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetFilterJSON returns a filter with a timeline limit of 50.
 | 
				
			||||||
 | 
					func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
 | 
				
			||||||
 | 
						return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								view-main.go
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								view-main.go
									
									
									
									
									
								
							@@ -112,10 +112,6 @@ func findWordToTabComplete(text string) string {
 | 
				
			|||||||
	return output
 | 
						return output
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) GetRoom(id string) *RoomView {
 | 
					 | 
				
			||||||
	return view.rooms[id]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (view *MainView) InputTabComplete(text string, cursorOffset int) string {
 | 
					func (view *MainView) InputTabComplete(text string, cursorOffset int) string {
 | 
				
			||||||
	roomView, _ := view.rooms[view.CurrentRoomID()]
 | 
						roomView, _ := view.rooms[view.CurrentRoomID()]
 | 
				
			||||||
	if roomView != nil {
 | 
						if roomView != nil {
 | 
				
			||||||
@@ -156,10 +152,7 @@ func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
				
			|||||||
	case "/quit":
 | 
						case "/quit":
 | 
				
			||||||
		view.gmx.Stop()
 | 
							view.gmx.Stop()
 | 
				
			||||||
	case "/clearcache":
 | 
						case "/clearcache":
 | 
				
			||||||
		view.config.Session.Rooms = make(map[string]*gomatrix.Room)
 | 
							view.config.Session.Clear()
 | 
				
			||||||
		view.config.Session.NextBatch = ""
 | 
					 | 
				
			||||||
		view.config.Session.FilterID = ""
 | 
					 | 
				
			||||||
		view.config.Session.Save()
 | 
					 | 
				
			||||||
		view.gmx.Stop()
 | 
							view.gmx.Stop()
 | 
				
			||||||
	case "/part":
 | 
						case "/part":
 | 
				
			||||||
		fallthrough
 | 
							fallthrough
 | 
				
			||||||
@@ -167,12 +160,12 @@ func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
				
			|||||||
		view.matrix.client.LeaveRoom(room)
 | 
							view.matrix.client.LeaveRoom(room)
 | 
				
			||||||
	case "/join":
 | 
						case "/join":
 | 
				
			||||||
		if len(args) == 0 {
 | 
							if len(args) == 0 {
 | 
				
			||||||
			view.AddMessage(room, "Usage: /join <room>")
 | 
								view.AddServiceMessage(room, "Usage: /join <room>")
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		view.debug.Print(view.matrix.JoinRoom(args[0]))
 | 
							view.debug.Print(view.matrix.JoinRoom(args[0]))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		view.AddMessage(room, "Unknown command.")
 | 
							view.AddServiceMessage(room, "Unknown command.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -229,9 +222,14 @@ func (view *MainView) addRoom(index int, room string) {
 | 
				
			|||||||
		view.rooms[room] = roomView
 | 
							view.rooms[room] = roomView
 | 
				
			||||||
		view.roomView.AddPage(room, roomView, true, false)
 | 
							view.roomView.AddPage(room, roomView, true, false)
 | 
				
			||||||
		roomView.UpdateUserList()
 | 
							roomView.UpdateUserList()
 | 
				
			||||||
 | 
							view.GetHistory(room)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) GetRoom(id string) *RoomView {
 | 
				
			||||||
 | 
						return view.rooms[id]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) HasRoom(room string) bool {
 | 
					func (view *MainView) HasRoom(room string) bool {
 | 
				
			||||||
	for _, existingRoom := range view.roomIDs {
 | 
						for _, existingRoom := range view.roomIDs {
 | 
				
			||||||
		if existingRoom == room {
 | 
							if existingRoom == room {
 | 
				
			||||||
@@ -263,6 +261,7 @@ func (view *MainView) RemoveRoom(room string) {
 | 
				
			|||||||
	view.roomList.RemoveItem(removeIndex)
 | 
						view.roomList.RemoveItem(removeIndex)
 | 
				
			||||||
	view.roomIDs = append(view.roomIDs[:removeIndex], view.roomIDs[removeIndex+1:]...)
 | 
						view.roomIDs = append(view.roomIDs[:removeIndex], view.roomIDs[removeIndex+1:]...)
 | 
				
			||||||
	view.roomView.RemovePage(room)
 | 
						view.roomView.RemovePage(room)
 | 
				
			||||||
 | 
						delete(view.rooms, room)
 | 
				
			||||||
	view.Render()
 | 
						view.Render()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -270,6 +269,7 @@ func (view *MainView) SetRoomList(rooms []string) {
 | 
				
			|||||||
	view.roomIDs = rooms
 | 
						view.roomIDs = rooms
 | 
				
			||||||
	view.roomList.Clear()
 | 
						view.roomList.Clear()
 | 
				
			||||||
	view.roomView.Clear()
 | 
						view.roomView.Clear()
 | 
				
			||||||
 | 
						view.rooms = make(map[string]*RoomView)
 | 
				
			||||||
	for index, room := range rooms {
 | 
						for index, room := range rooms {
 | 
				
			||||||
		view.addRoom(index, room)
 | 
							view.addRoom(index, room)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -284,10 +284,12 @@ func (view *MainView) SetTyping(room string, users []string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) AddMessage(room, message string) {
 | 
					func (view *MainView) AddServiceMessage(room, message string) {
 | 
				
			||||||
	roomView, ok := view.rooms[room]
 | 
						roomView, ok := view.rooms[room]
 | 
				
			||||||
	if ok {
 | 
						if ok {
 | 
				
			||||||
		roomView.content.AddMessage("", "*", message, time.Now())
 | 
							messageView := roomView.MessageView()
 | 
				
			||||||
 | 
							message := messageView.NewMessage("", "*", message, time.Now())
 | 
				
			||||||
 | 
							messageView.AddMessage(message, AppendMessage)
 | 
				
			||||||
		view.parent.Render()
 | 
							view.parent.Render()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -295,3 +297,89 @@ func (view *MainView) AddMessage(room, message string) {
 | 
				
			|||||||
func (view *MainView) Render() {
 | 
					func (view *MainView) Render() {
 | 
				
			||||||
	view.parent.Render()
 | 
						view.parent.Render()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) GetHistory(room string) {
 | 
				
			||||||
 | 
						roomView := view.rooms[room]
 | 
				
			||||||
 | 
						history, _, err := view.matrix.GetHistory(roomView.room.ID, view.config.Session.NextBatch, 50)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							view.debug.Print("Failed to fetch history for", roomView.room.ID, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, evt := range history {
 | 
				
			||||||
 | 
							var room *RoomView
 | 
				
			||||||
 | 
							var message *Message
 | 
				
			||||||
 | 
							if evt.Type == "m.room.message" {
 | 
				
			||||||
 | 
								room, message = view.ProcessMessageEvent(&evt)
 | 
				
			||||||
 | 
							} else if evt.Type == "m.room.member" {
 | 
				
			||||||
 | 
								room, message = view.ProcessMembershipEvent(&evt, false)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if room != nil && message != nil {
 | 
				
			||||||
 | 
								room.AddMessage(message, PrependMessage)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) ProcessMessageEvent(evt *gomatrix.Event) (room *RoomView, message *Message) {
 | 
				
			||||||
 | 
						room = view.GetRoom(evt.RoomID)
 | 
				
			||||||
 | 
						if room != nil {
 | 
				
			||||||
 | 
							text := evt.Content["body"].(string)
 | 
				
			||||||
 | 
							message = room.NewMessage(evt.ID, evt.Sender, text, unixToTime(evt.Timestamp))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) processOwnMembershipChange(evt *gomatrix.Event) {
 | 
				
			||||||
 | 
						membership, _ := evt.Content["membership"].(string)
 | 
				
			||||||
 | 
						prevMembership := "leave"
 | 
				
			||||||
 | 
						if evt.Unsigned.PrevContent != nil {
 | 
				
			||||||
 | 
							prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if membership == prevMembership {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if membership == "join" {
 | 
				
			||||||
 | 
							view.AddRoom(evt.RoomID)
 | 
				
			||||||
 | 
						} else if membership == "leave" {
 | 
				
			||||||
 | 
							view.RemoveRoom(evt.RoomID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) ProcessMembershipEvent(evt *gomatrix.Event, new bool) (room *RoomView, message *Message) {
 | 
				
			||||||
 | 
						if new && evt.StateKey != nil && *evt.StateKey == view.config.Session.MXID {
 | 
				
			||||||
 | 
							view.processOwnMembershipChange(evt)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						room = view.GetRoom(evt.RoomID)
 | 
				
			||||||
 | 
						if room != nil {
 | 
				
			||||||
 | 
							membership, _ := evt.Content["membership"].(string)
 | 
				
			||||||
 | 
							var sender, text string
 | 
				
			||||||
 | 
							if membership == "invite" {
 | 
				
			||||||
 | 
								sender = "---"
 | 
				
			||||||
 | 
								text = fmt.Sprintf("%s invited %s.", evt.Sender, *evt.StateKey)
 | 
				
			||||||
 | 
							} else if membership == "join" {
 | 
				
			||||||
 | 
								sender = "-->"
 | 
				
			||||||
 | 
								text = fmt.Sprintf("%s joined the room.", *evt.StateKey)
 | 
				
			||||||
 | 
							} else if membership == "leave" {
 | 
				
			||||||
 | 
								sender = "<--"
 | 
				
			||||||
 | 
								if evt.Sender != *evt.StateKey {
 | 
				
			||||||
 | 
									reason, _ := evt.Content["reason"].(string)
 | 
				
			||||||
 | 
									text = fmt.Sprintf("%s kicked %s: %s", evt.Sender, *evt.StateKey, reason)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									text = fmt.Sprintf("%s left the room.", *evt.StateKey)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								room = nil
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							message = room.NewMessage(evt.ID, sender, text, unixToTime(evt.Timestamp))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func unixToTime(unix int64) time.Time {
 | 
				
			||||||
 | 
						timestamp := time.Now()
 | 
				
			||||||
 | 
						if unix != 0 {
 | 
				
			||||||
 | 
							timestamp = time.Unix(unix/1000, unix%1000*1000)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return timestamp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user