324 lines
7.0 KiB
Go
324 lines
7.0 KiB
Go
// 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 matrix
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"maunium.net/go/gomatrix"
|
|
"maunium.net/go/gomuks/config"
|
|
"maunium.net/go/gomuks/interface"
|
|
rooms "maunium.net/go/gomuks/matrix/room"
|
|
"maunium.net/go/gomuks/notification"
|
|
"maunium.net/go/gomuks/ui/debug"
|
|
"maunium.net/go/gomuks/ui/types"
|
|
"maunium.net/go/gomuks/ui/widget"
|
|
)
|
|
|
|
type Container struct {
|
|
client *gomatrix.Client
|
|
gmx ifc.Gomuks
|
|
ui ifc.GomuksUI
|
|
config *config.Config
|
|
running bool
|
|
stop chan bool
|
|
|
|
typing int64
|
|
}
|
|
|
|
func NewMatrixContainer(gmx ifc.Gomuks) *Container {
|
|
c := &Container{
|
|
config: gmx.Config(),
|
|
ui: gmx.UI(),
|
|
gmx: gmx,
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Container) InitClient() error {
|
|
if len(c.config.HS) == 0 {
|
|
return fmt.Errorf("no homeserver in config")
|
|
}
|
|
|
|
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
|
|
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 && len(c.config.Session.AccessToken) > 0 {
|
|
go c.Start()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) Initialized() bool {
|
|
return c.client != nil
|
|
}
|
|
|
|
func (c *Container) Login(user, password string) error {
|
|
resp, err := c.client.Login(&gomatrix.ReqLogin{
|
|
Type: "m.login.password",
|
|
User: user,
|
|
Password: password,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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 *Container) Stop() {
|
|
if c.running {
|
|
c.stop <- true
|
|
c.client.StopSync()
|
|
}
|
|
}
|
|
|
|
func (c *Container) Client() *gomatrix.Client {
|
|
return c.client
|
|
}
|
|
|
|
func (c *Container) UpdatePushRules() {
|
|
resp, err := c.client.PushRules()
|
|
if err != nil {
|
|
debug.Print("Failed to fetch push rules:", err)
|
|
}
|
|
c.config.Session.PushRules = resp
|
|
}
|
|
|
|
func (c *Container) UpdateRoomList() {
|
|
resp, err := c.client.JoinedRooms()
|
|
if err != nil {
|
|
respErr, _ := err.(gomatrix.HTTPError).WrappedError.(gomatrix.RespError)
|
|
if respErr.ErrCode == "M_UNKNOWN_TOKEN" {
|
|
c.OnLogout()
|
|
return
|
|
}
|
|
debug.Print("Error fetching room list:", err)
|
|
return
|
|
}
|
|
|
|
c.ui.MainView().SetRooms(resp.JoinedRooms)
|
|
}
|
|
|
|
func (c *Container) OnLogout() {
|
|
c.Stop()
|
|
c.ui.SetView(ifc.ViewLogin)
|
|
}
|
|
|
|
func (c *Container) OnLogin() {
|
|
c.client.Store = c.config.Session
|
|
|
|
syncer := NewGomuksSyncer(c.config.Session)
|
|
syncer.OnEventType("m.room.message", c.HandleMessage)
|
|
syncer.OnEventType("m.room.member", c.HandleMembership)
|
|
syncer.OnEventType("m.typing", c.HandleTyping)
|
|
c.client.Syncer = syncer
|
|
|
|
c.UpdateRoomList()
|
|
c.UpdatePushRules()
|
|
}
|
|
|
|
func (c *Container) Start() {
|
|
defer c.gmx.Recover()
|
|
|
|
c.ui.SetView(ifc.ViewMain)
|
|
c.OnLogin()
|
|
|
|
if c.client == nil {
|
|
return
|
|
}
|
|
|
|
debug.Print("Starting sync...")
|
|
c.running = true
|
|
for {
|
|
select {
|
|
case <-c.stop:
|
|
debug.Print("Stopping sync...")
|
|
c.running = false
|
|
return
|
|
default:
|
|
if err := c.client.Sync(); err != nil {
|
|
debug.Print("Sync() errored", err)
|
|
} else {
|
|
debug.Print("Sync() returned without error")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Container) NotifyMessage(room *rooms.Room, message *types.Message) {
|
|
sender := fmt.Sprintf("%s (%s)", message.Sender, room.GetTitle())
|
|
if room.GetTitle() == message.Sender {
|
|
// 1:1 room, title is same as display name of other user.
|
|
sender = message.Sender
|
|
}
|
|
notification.Send(sender, message.Text, false)
|
|
}
|
|
|
|
func (c *Container) HandleMessage(evt *gomatrix.Event) {
|
|
room, message := c.ui.MainView().ProcessMessageEvent(evt)
|
|
if room != nil {
|
|
c.NotifyMessage(room.Room, message)
|
|
room.AddMessage(message, widget.AppendMessage)
|
|
c.ui.Render()
|
|
}
|
|
}
|
|
|
|
func (c *Container) HandleMembership(evt *gomatrix.Event) {
|
|
const Hour = 1 * 60 * 60 * 1000
|
|
if evt.Unsigned.Age > Hour {
|
|
return
|
|
}
|
|
|
|
room, message := c.ui.MainView().ProcessMembershipEvent(evt, true)
|
|
if room != nil {
|
|
// TODO this shouldn't be necessary
|
|
room.Room.UpdateState(evt)
|
|
// TODO This should probably also be in a different place
|
|
room.UpdateUserList()
|
|
|
|
room.AddMessage(message, widget.AppendMessage)
|
|
c.ui.Render()
|
|
}
|
|
}
|
|
|
|
func (c *Container) HandleTyping(evt *gomatrix.Event) {
|
|
users := evt.Content["user_ids"].([]interface{})
|
|
|
|
strUsers := make([]string, len(users))
|
|
for i, user := range users {
|
|
strUsers[i] = user.(string)
|
|
}
|
|
c.ui.MainView().SetTyping(evt.RoomID, strUsers)
|
|
}
|
|
|
|
func (c *Container) SendMessage(roomID, text string) (string, error) {
|
|
defer c.gmx.Recover()
|
|
c.SendTyping(roomID, false)
|
|
resp, err := c.client.SendText(roomID, text)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return resp.EventID, nil
|
|
}
|
|
|
|
func (c *Container) SendTyping(roomID string, typing bool) {
|
|
defer c.gmx.Recover()
|
|
ts := time.Now().Unix()
|
|
if c.typing > ts && typing {
|
|
return
|
|
}
|
|
|
|
if typing {
|
|
c.client.UserTyping(roomID, true, 5000)
|
|
c.typing = ts + 5
|
|
} else {
|
|
c.client.UserTyping(roomID, false, 0)
|
|
c.typing = 0
|
|
}
|
|
}
|
|
|
|
func (c *Container) JoinRoom(roomID string) error {
|
|
if len(roomID) == 0 {
|
|
return fmt.Errorf("invalid room ID")
|
|
}
|
|
|
|
server := ""
|
|
if roomID[0] == '!' {
|
|
server = roomID[strings.Index(roomID, ":")+1:]
|
|
}
|
|
|
|
_, err := c.client.JoinRoom(roomID, server, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) LeaveRoom(roomID string) error {
|
|
if len(roomID) == 0 {
|
|
return fmt.Errorf("invalid room ID")
|
|
}
|
|
|
|
_, err := c.client.LeaveRoom(roomID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) getState(roomID string) []*gomatrix.Event {
|
|
content := make([]*gomatrix.Event, 0)
|
|
err := c.client.StateEvent(roomID, "", "", &content)
|
|
if err != nil {
|
|
debug.Print("Error getting state of", roomID, err)
|
|
return nil
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
|
|
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return resp.Chunk, resp.End, nil
|
|
}
|
|
|
|
func (c *Container) GetRoom(roomID string) *rooms.Room {
|
|
room := c.config.Session.GetRoom(roomID)
|
|
if room != nil && len(room.State) == 0 {
|
|
events := c.getState(room.ID)
|
|
if events != nil {
|
|
for _, event := range events {
|
|
room.UpdateState(event)
|
|
}
|
|
}
|
|
}
|
|
return room
|
|
}
|