From f0333df1b2b6e0ef54a580668cb759a094b56010 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 13 Mar 2018 15:27:12 +0200 Subject: [PATCH] Add initial login view and debug panel with tview --- .editorconfig | 12 +++++ .gitignore | 1 + README.md | 2 +- config.go | 70 ++++++++++++++++++++++++++++++ debug.go | 56 ++++++++++++++++++++++++ go.mod | 14 ++++++ gomuks.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++ matrix.go | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ session.go | 102 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 config.go create mode 100644 debug.go create mode 100644 go.mod create mode 100644 gomuks.go create mode 100644 matrix.go create mode 100644 session.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..21d312a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{yaml,yml}] +indent_style = space diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/README.md b/README.md index ce5b4a9..be70bf4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # gomuks -A Go terminal matrix client. +A terminal Matrix client written in Go. diff --git a/config.go b/config.go new file mode 100644 index 0000000..3510e4a --- /dev/null +++ b/config.go @@ -0,0 +1,70 @@ +// 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 . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +type Config struct { + MXID string `yaml:"mxid"` + HS string `yaml:"homeserver"` + + dir string `yaml:"-"` + Session *Session `yaml:"-"` +} + +func (config *Config) Load(dir string) { + config.dir = dir + os.MkdirAll(dir, 0700) + configPath := filepath.Join(dir, "config.yaml") + data, err := ioutil.ReadFile(configPath) + if err != nil { + if os.IsNotExist(err) { + return + } else { + fmt.Println("Failed to read config from", configPath) + panic(err) + } + } + + err = yaml.Unmarshal(data, &config) + if err != nil { + fmt.Println("Failed to parse config at", configPath) + panic(err) + } +} + +func (config *Config) Save() { + data, err := yaml.Marshal(&config) + if err != nil { + debug.Print("Failed to marshal config") + panic(err) + } + + path := filepath.Join(config.dir, "config.yaml") + err = ioutil.WriteFile(path, data, 0600) + if err != nil { + debug.Print("Failed to write config to", path) + panic(err) + } +} diff --git a/debug.go b/debug.go new file mode 100644 index 0000000..2533d5c --- /dev/null +++ b/debug.go @@ -0,0 +1,56 @@ +// 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 . + +package main + +import ( + "fmt" + + "github.com/rivo/tview" +) + +type DebugPane struct { + text string + pane *tview.TextView + num int +} + +func (db *DebugPane) Printf(text string, args ...interface{}) { + db.num++ + db.Write(fmt.Sprintf("[%d] %s\n", db.num, fmt.Sprintf(text, args...))) +} + +func (db *DebugPane) Print(text ...interface{}) { + db.num++ + db.Write(fmt.Sprintf("[%d] %s", db.num, fmt.Sprintln(text...))) +} + +func (db *DebugPane) Write(text string) { + if db.pane != nil { + db.text += text + db.pane.SetText(db.text) + } +} + +func (db *DebugPane) Wrap(main *tview.Pages) tview.Primitive { + db.pane = tview.NewTextView() + db.pane.SetBorder(true).SetTitle("Debug output") + db.text += "[0] Debug pane initialized\n" + db.pane.SetText(db.text) + return tview.NewGrid().SetRows(0, 20).SetColumns(0). + AddItem(main, 0, 0, 1, 1, 1, 1, true). + AddItem(db.pane, 1, 0, 1, 1, 1, 1, false) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4888c39 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module "maunium.net/go/gomuks" + +require ( + "github.com/gdamore/encoding" v0.0.0-20151215212835-b23993cbb635 + "github.com/gdamore/tcell" v1.0.0 + "github.com/jroimartin/gocui" v0.0.0-20170827195011-4f518eddb04b + "github.com/lucasb-eyer/go-colorful" v0.0.0-20170903184257-231272389856 + "github.com/matrix-org/gomatrix" v0.0.0-20171003113848-a7fc80c8060c + "github.com/mattn/go-runewidth" v0.0.2 + "github.com/nsf/termbox-go" v0.0.0-20180303152453-e2050e41c884 + "github.com/rivo/tview" v0.0.0-20180313071706-0b69b9b58142 + "golang.org/x/text" v0.0.0-20171214130843-f21a4dfb5e38 + "gopkg.in/yaml.v2" v1.1.1-gopkgin-v2.1.1 +) diff --git a/gomuks.go b/gomuks.go new file mode 100644 index 0000000..8ffc2e9 --- /dev/null +++ b/gomuks.go @@ -0,0 +1,113 @@ +// 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 . + +package main + +import ( + "os" + "path/filepath" + + "github.com/gdamore/tcell" + "github.com/rivo/tview" +) + +var matrix = new(MatrixContainer) +var config = new(Config) +var debug = new(DebugPane) + +func main() { + configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks") + os.MkdirAll(configDir, 0700) + config.Load(configDir) + + views := tview.NewPages() + InitUI(views) + + main := debug.Wrap(views) + + if len(config.MXID) > 0 { + config.LoadSession(config.MXID) + } + matrix.Init(config) + + if err := tview.NewApplication().SetRoot(main, true).Run(); err != nil { + panic(err) + } +} + +func InitUI(views *tview.Pages) { + views.AddPage("login", InitLoginUI(), true, true) +} + +func Center(width, height int, p tview.Primitive) tview.Primitive { + return tview.NewFlex(). + AddItem(tview.NewBox(), 0, 1, false). + AddItem(tview.NewFlex(). + SetDirection(tview.FlexRow). + AddItem(tview.NewBox(), 0, 1, false). + AddItem(p, height, 1, true). + AddItem(tview.NewBox(), 0, 1, false), width, 1, true). + AddItem(tview.NewBox(), 0, 1, false) +} + +type FormTextView struct { + *tview.TextView +} + +func (ftv *FormTextView) GetLabel() string { + return "" +} + +func (ftv *FormTextView) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem { + return ftv +} + +func (ftv *FormTextView) GetFieldWidth() int { + _, _, w, _ := ftv.TextView.GetRect() + return w +} + +func (ftv *FormTextView) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem { + ftv.SetDoneFunc(handler) + return ftv +} + +func login(form *tview.Form) func() { + return func() { + hs := form.GetFormItem(0).(*tview.InputField).GetText() + mxid := form.GetFormItem(1).(*tview.InputField).GetText() + password := form.GetFormItem(2).(*tview.InputField).GetText() + debug.Printf("%s %s %s", hs, mxid, password) + config.HS = hs + debug.Print(matrix.Init(config)) + debug.Print(matrix.Login(mxid, password)) + } +} + +func InitLoginUI() tview.Primitive { + form := tview.NewForm().SetButtonsAlign(tview.AlignCenter) + hs := config.HS + if len(hs) == 0 { + hs = "https://matrix.org" + } + form. + AddInputField("Homeserver", hs, 30, nil, nil). + AddInputField("Username", config.MXID, 30, nil, nil). + AddPasswordField("Password", "", 30, '*', nil). + AddButton("Log in", login(form)) + form.SetBorder(true).SetTitle("Log in to Matrix") + return Center(45, 13, form) +} diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..b559f17 --- /dev/null +++ b/matrix.go @@ -0,0 +1,118 @@ +// 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 . + +package main + +import ( + "fmt" + + "github.com/matrix-org/gomatrix" +) + +type MatrixContainer struct { + lient *gomatrix.Client + config *Config + running bool + stop chan bool +} + +func (c *MatrixContainer) Initialized() bool { + return c.lient != nil +} + +func (c *MatrixContainer) Init(config *Config) error { + c.config = config + + if c.lient != nil { + c.lient.StopSync() + } + + if len(c.config.HS) == 0 { + return fmt.Errorf("no homeserver in config") + } + + var mxid, accessToken string + if c.config.Session != nil { + accessToken = c.config.Session.AccessToken + mxid = c.config.MXID + } + + var err error + c.lient, 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 +} + +func (c *MatrixContainer) Login(user, password string) error { + resp, err := c.lient.Login(&gomatrix.ReqLogin{ + Type: "m.login.password", + User: user, + Password: password, + }) + if err != nil { + return err + } + c.lient.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() { + c.stop <- true + c.lient.StopSync() +} + +func (c *MatrixContainer) Start() { + debug.Print("Starting sync...") + c.running = true + c.lient.Store = c.config.Session + + syncer := c.lient.Syncer.(*gomatrix.DefaultSyncer) + syncer.OnEventType("m.room.message", c.HandleMessage) + + for { + select { + case <-c.stop: + debug.Print("Stopping sync...") + c.running = false + return + default: + if err := c.lient.Sync(); err != nil { + debug.Print("Sync() errored", err) + } + } + } +} + +func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) { + debug.Print("Message received") +} diff --git a/session.go b/session.go new file mode 100644 index 0000000..8f0e5d8 --- /dev/null +++ b/session.go @@ -0,0 +1,102 @@ +// 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 . + +package main + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + + "github.com/matrix-org/gomatrix" +) + +type Session struct { + MXID string `json:"-"` + path string `json:"-"` + AccessToken string + NextBatch string + FilterID string + Rooms map[string]*gomatrix.Room +} + +func (config *Config) LoadSession(mxid string) { + config.Session = config.NewSession(mxid) + config.Session.Load() +} + +func (config *Config) NewSession(mxid string) *Session { + return &Session{ + MXID: mxid, + path: filepath.Join(config.dir, mxid+".session"), + Rooms: make(map[string]*gomatrix.Room), + } +} + +func (s *Session) Load() { + data, err := ioutil.ReadFile(s.path) + if err != nil { + debug.Print("Failed to read session from", s.path) + panic(err) + } + + err = json.Unmarshal(data, s) + if err != nil { + debug.Print("Failed to parse session at", s.path) + panic(err) + } +} + +func (s *Session) Save() { + data, err := json.Marshal(s) + if err != nil { + debug.Print("Failed to marshal session of", s.MXID) + panic(err) + } + + err = ioutil.WriteFile(s.path, data, 0600) + if err != nil { + debug.Print("Failed to write session to", s.path) + panic(err) + } +} + +func (s *Session) LoadFilterID(_ string) string { + return s.FilterID +} + +func (s *Session) LoadNextBatch(_ string) string { + return s.NextBatch +} + +func (s *Session) LoadRoom(mxid string) *gomatrix.Room { + return s.Rooms[mxid] +} + +func (s *Session) SaveFilterID(_, filterID string) { + s.FilterID = filterID + s.Save() +} + +func (s *Session) SaveNextBatch(_, nextBatch string) { + s.NextBatch = nextBatch + s.Save() +} + +func (s *Session) SaveRoom(room *gomatrix.Room) { + s.Rooms[room.ID] = room + s.Save() +}