gomuks/matrix.go

289 lines
6.5 KiB
Go
Raw Normal View History

// 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 (
"fmt"
2018-03-16 15:24:11 +01:00
"strings"
2018-03-14 23:14:39 +01:00
"time"
2018-03-15 17:25:16 +01:00
"maunium.net/go/gomatrix"
)
type MatrixContainer struct {
2018-03-13 20:58:43 +01:00
client *gomatrix.Client
gmx Gomuks
ui *GomuksUI
debug DebugPrinter
config *Config
running bool
stop chan bool
2018-03-14 23:14:39 +01:00
typing int64
}
2018-03-13 20:58:43 +01:00
func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
c := &MatrixContainer{
config: gmx.Config(),
debug: gmx.Debug(),
ui: gmx.UI(),
gmx: gmx,
}
2018-03-13 20:58:43 +01:00
return c
}
func (c *MatrixContainer) InitClient() error {
if len(c.config.HS) == 0 {
return fmt.Errorf("no homeserver in config")
}
2018-03-13 20:58:43 +01:00
if c.client != nil {
c.Stop()
c.client = nil
}
var mxid, accessToken string
if c.config.Session != nil {
accessToken = c.config.Session.AccessToken
mxid = c.config.MXID
}
var err error
2018-03-13 20:58:43 +01:00
c.client, err = gomatrix.NewClient(c.config.HS, mxid, accessToken)
if err != nil {
return err
}
c.stop = make(chan bool, 1)
if c.config.Session != nil {
go c.Start()
}
return nil
}
2018-03-13 20:58:43 +01:00
func (c *MatrixContainer) Initialized() bool {
return c.client != nil
}
func (c *MatrixContainer) Login(user, password string) error {
2018-03-13 20:58:43 +01:00
resp, err := c.client.Login(&gomatrix.ReqLogin{
Type: "m.login.password",
User: user,
Password: password,
})
if err != nil {
return err
}
2018-03-13 20:58:43 +01:00
c.client.SetCredentials(resp.UserID, resp.AccessToken)
c.config.MXID = resp.UserID
c.config.Save()
c.config.Session = c.config.NewSession(resp.UserID)
c.config.Session.AccessToken = resp.AccessToken
c.config.Session.Save()
go c.Start()
return nil
}
func (c *MatrixContainer) Stop() {
2018-03-15 20:28:21 +01:00
if c.running {
c.stop <- true
c.client.StopSync()
}
2018-03-13 20:58:43 +01:00
}
func (c *MatrixContainer) UpdateRoomList() {
rooms, err := c.client.JoinedRooms()
if err != nil {
c.debug.Print(err)
}
2018-03-15 19:53:04 +01:00
c.ui.MainView().SetRoomList(rooms.JoinedRooms)
}
func (c *MatrixContainer) Start() {
2018-03-15 17:21:14 +01:00
defer c.gmx.Recover()
2018-03-13 20:58:43 +01:00
c.client.Store = c.config.Session
syncer := gomatrix.NewDefaultSyncer(c.config.Session.MXID, c.config.Session)
syncer.OnEventType("m.room.message", c.HandleMessage)
2018-03-16 15:24:11 +01:00
syncer.OnEventType("m.room.member", c.HandleMembership)
2018-03-14 23:14:39 +01:00
syncer.OnEventType("m.typing", c.HandleTyping)
c.client.Syncer = syncer
c.UpdateRoomList()
c.debug.Print("Starting sync...")
c.running = true
c.ui.SetView(ViewMain)
for {
select {
case <-c.stop:
2018-03-13 20:58:43 +01:00
c.debug.Print("Stopping sync...")
c.running = false
return
default:
2018-03-13 20:58:43 +01:00
if err := c.client.Sync(); err != nil {
c.debug.Print("Sync() errored", err)
} else {
c.debug.Print("Sync() returned without error")
}
}
}
}
func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
2018-03-13 20:58:43 +01:00
message, _ := evt.Content["body"].(string)
room := c.ui.MainView().GetRoom(evt.RoomID)
if room != nil {
room.AddMessage(evt.ID, evt.Sender, message, unixToTime(evt.Timestamp))
}
}
func unixToTime(unix int64) time.Time {
timestamp := time.Now()
if unix != 0 {
timestamp = time.Unix(unix/1000, unix%1000*1000)
}
return timestamp
2018-03-13 20:58:43 +01:00
}
2018-03-16 15:24:11 +01:00
func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
membership, _ := evt.Content["membership"].(string)
2018-03-16 15:24:11 +01:00
if evt.StateKey != nil && *evt.StateKey == c.config.Session.MXID {
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
}
room := c.ui.MainView().GetRoom(evt.RoomID)
// TODO this shouldn't be necessary
room.room.UpdateState(evt)
if room != nil {
var message, sender string
if membership == "invite" {
sender = "---"
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.AddMessage(evt.ID, sender, message, unixToTime(evt.Timestamp))
2018-03-16 15:24:11 +01:00
}
}
2018-03-14 23:14:39 +01:00
func (c *MatrixContainer) HandleTyping(evt *gomatrix.Event) {
2018-03-15 17:21:14 +01:00
users := evt.Content["user_ids"].([]interface{})
strUsers := make([]string, len(users))
for i, user := range users {
strUsers[i] = user.(string)
}
2018-03-15 19:53:04 +01:00
c.ui.MainView().SetTyping(evt.RoomID, strUsers)
2018-03-14 23:14:39 +01:00
}
2018-03-13 20:58:43 +01:00
func (c *MatrixContainer) SendMessage(roomID, message string) {
2018-03-16 15:24:11 +01:00
c.gmx.Recover()
2018-03-15 18:45:52 +01:00
c.SendTyping(roomID, false)
2018-03-13 20:58:43 +01:00
c.client.SendText(roomID, message)
}
2018-03-14 23:14:39 +01:00
2018-03-15 18:45:52 +01:00
func (c *MatrixContainer) SendTyping(roomID string, typing bool) {
2018-03-16 15:24:11 +01:00
c.gmx.Recover()
2018-03-14 23:14:39 +01:00
time := time.Now().Unix()
2018-03-15 18:45:52 +01:00
if c.typing > time && typing {
2018-03-14 23:14:39 +01:00
return
}
2018-03-15 18:45:52 +01:00
if typing {
c.client.UserTyping(roomID, true, 5000)
c.typing = time + 5
} else {
c.client.UserTyping(roomID, false, 0)
c.typing = 0
}
2018-03-14 23:14:39 +01:00
}
2018-03-16 15:24:11 +01:00
func (c *MatrixContainer) JoinRoom(roomID string) error {
if len(roomID) == 0 {
return fmt.Errorf("invalid room ID")
}
server := ""
if roomID[0] == '!' {
server = roomID[strings.Index(roomID, ":")+1:]
}
resp, err := c.client.JoinRoom(roomID, server, nil)
if err != nil {
return err
}
c.ui.MainView().AddRoom(resp.RoomID)
return nil
}
func (c *MatrixContainer) getState(roomID string) []*gomatrix.Event {
2018-03-15 18:45:52 +01:00
content := make([]*gomatrix.Event, 0)
err := c.client.StateEvent(roomID, "", "", &content)
if err != nil {
c.debug.Print(err)
2018-03-14 23:14:39 +01:00
return nil
}
2018-03-15 18:45:52 +01:00
return content
2018-03-14 23:14:39 +01:00
}
2018-03-16 15:24:11 +01:00
func (c *MatrixContainer) GetRoom(roomID string) *gomatrix.Room {
room := c.config.Session.LoadRoom(roomID)
if room != nil && len(room.State) == 0 {
2018-03-16 15:24:11 +01:00
events := c.getState(room.ID)
if events != nil {
for _, event := range events {
room.UpdateState(event)
}
2018-03-15 18:45:52 +01:00
}
2018-03-14 23:14:39 +01:00
}
2018-03-16 15:24:11 +01:00
return room
2018-03-14 23:14:39 +01:00
}