commit 1cfa94dc75a76debec05376be04f30525b14170b Author: Steve Date: Sun Jul 17 17:22:28 2022 -0400 init diff --git a/client.go b/client.go new file mode 100644 index 0000000..9cbfb25 --- /dev/null +++ b/client.go @@ -0,0 +1,44 @@ +package matrixbotlib + +import ( + "fmt" + "strings" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" +) + +//NewMatrixClient returns a new logged in Mautrix Client struct +func NewMatrixClient(config *MatrixClientConfig, store mautrix.Storer) (*mautrix.Client, error) { + fmt.Println("Logging into", config.Homeserver, "as", config.Username) + var client *mautrix.Client + var err error + //make sure username is lower case otherwise token login breaks + uname := strings.ToLower(config.Username) + if config.Token == "" { + client, err = mautrix.NewClient(config.Homeserver, "", "") + if err != nil { + return client, err + } + } else { + client, err = mautrix.NewClient(config.Homeserver, id.NewUserID(uname, config.Domain), config.Token) + if err != nil { + return client, err + } + } + client.Store = store + if config.Token == "" { + loginRes, err := client.Login(&mautrix.ReqLogin{ + Type: "m.login.password", + Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: uname}, + Password: config.Password, + StoreCredentials: true, + }) + if err != nil { + return client, err + } + config.Token = loginRes.AccessToken + WriteMatrixClientConfig(config) + } + return client, err +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..4b712c5 --- /dev/null +++ b/config.go @@ -0,0 +1,53 @@ +package matrixbotlib + +import ( + "io/ioutil" + "os" + + "gopkg.in/yaml.v2" +) + +//MatrixClientConfig represents the config requires to log into the matrix server +type MatrixClientConfig struct { + Homeserver string `yaml:"homeserver"` + Domain string `yaml:"domain"` + Dimension string `yaml:"dimension"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Statefile string `yaml:"statefile,omitempty"` + Token string `yaml:"token"` + filename string +} + +//LoadMatrixClientConfig reads info from a file +func LoadMatrixClientConfig(filename string) (*MatrixClientConfig, error) { + yamlFile, err := ioutil.ReadFile(filename) + cnf := &MatrixClientConfig{} + if err == nil { + err = yaml.Unmarshal(yamlFile, cnf) + } else { + return cnf, err + } + if err != nil { + return cnf, err + } + cnf.filename = filename + return cnf, nil +} + +//WriteMatrixClientConfig saves current running config to a file +func WriteMatrixClientConfig(cnf *MatrixClientConfig) error { + file, err := os.OpenFile(cnf.filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer file.Close() + + enc := yaml.NewEncoder(file) + + err = enc.Encode(cnf) + if err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f8e9ef9 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module git.saintnet.tech/stryan/matrixbotlib + +go 1.18 + +require ( + gopkg.in/yaml.v2 v2.4.0 + maunium.net/go/mautrix v0.11.0 +) + +require ( + golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect + golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e3c506d --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= +golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +maunium.net/go/mautrix v0.11.0 h1:B1FBHcvE4Mud+AC+zgNQQOw0JxSVrt40watCejhVA7w= +maunium.net/go/mautrix v0.11.0/go.mod h1:K29EcHwsNg6r7fMfwvi0GHQ9o5wSjqB9+Q8RjCIQEjA= diff --git a/lazystore.go b/lazystore.go new file mode 100644 index 0000000..ed9897b --- /dev/null +++ b/lazystore.go @@ -0,0 +1,87 @@ +package matrixbotlib + +import ( + "bytes" + "encoding/gob" + + "os" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" +) + +//LazyMemStore is a very simple persistent Mautrix.Storer by saving to a statefile +//It should only be used if for some reason the InMemoryStore or AccountDataStore aren't acceptable +type LazyMemStore struct { + mem *mautrix.InMemoryStore + NextBatch map[id.UserID]string + saveFile string +} + +func NewLazyMemStore(fileloc string) *LazyMemStore { + return &LazyMemStore{ + mem: mautrix.NewInMemoryStore(), + NextBatch: make(map[id.UserID]string), + saveFile: fileloc, + } +} + +func (l *LazyMemStore) SaveFilterID(userID id.UserID, filterID string) { + l.mem.SaveFilterID(userID, filterID) +} + +func (l *LazyMemStore) LoadFilterID(userID id.UserID) string { + return l.mem.LoadFilterID(userID) +} + +func (l *LazyMemStore) SaveNextBatch(userID id.UserID, nextBatchToken string) { + b := new(bytes.Buffer) + l.NextBatch[userID] = nextBatchToken + e := gob.NewEncoder(b) + err := e.Encode(l.NextBatch) + if err != nil { + panic(err) + } + if err := os.WriteFile(l.saveFile, b.Bytes(), 0666); err != nil { + panic(err) + } + +} + +func (l *LazyMemStore) LoadNextBatch(userID id.UserID) string { + + dat, err := os.ReadFile(l.saveFile) + if err != nil { + if os.IsNotExist(err) { + b := new(bytes.Buffer) + e := gob.NewEncoder(b) + err := e.Encode(l.NextBatch) + if err != nil { + panic(err) + } + if err := os.WriteFile(l.saveFile, b.Bytes(), 0666); err != nil { + panic(err) + } + dat, err = os.ReadFile(l.saveFile) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } + d := gob.NewDecoder(bytes.NewBuffer(dat)) + err = d.Decode(&l.NextBatch) + if err != nil { + panic(err) + } + return l.NextBatch[userID] +} + +func (l *LazyMemStore) SaveRoom(room *mautrix.Room) { + l.mem.SaveRoom(room) +} + +func (l *LazyMemStore) LoadRoom(roomID id.RoomID) *mautrix.Room { + return l.mem.LoadRoom(roomID) +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..32423c0 --- /dev/null +++ b/util.go @@ -0,0 +1,39 @@ +package matrixbotlib + +import ( + "errors" + "fmt" + "strings" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" +) + +//ParseCommand takes a Event and pulls a command + args if available +func ParseCommand(evt *event.Event, prefix string) ([]string, error) { + body := evt.Content.AsMessage().Body + bodyS := strings.Split(body, " ") + if bodyS[0] != fmt.Sprintf("!%v", prefix) { + return []string{}, errors.New("no prefix found") + } + if len(bodyS) < 2 { + return []string{}, errors.New("nothing to parse from command") + } + + return bodyS[1:], nil +} + +//AcceptAllRoomInvites adds a handler to join all rooms invited to +func AcceptAllRoomInvites(client *mautrix.Client) { + syncer := client.Syncer.(*mautrix.DefaultSyncer) + syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) { + if evt.Content.AsMember().Membership.IsInviteOrJoin() { + _, err := client.JoinRoomByID(evt.RoomID) + if err != nil { + fmt.Printf("error joining room %v", evt.RoomID) + } else { + fmt.Printf("joined room %v", evt.RoomID) + } + } + }) +}