Move syncer/room store changes from gomatrix fork to here, refactor and improve stuff
This commit is contained in:
parent
b536064882
commit
0509b19562
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user