diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39a0253 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.todo +*.db +mcchunkie diff --git a/errata.go b/errata.go new file mode 100644 index 0000000..b320eaf --- /dev/null +++ b/errata.go @@ -0,0 +1,39 @@ +package main + +import ( + "net/http" + + "golang.org/x/net/html" +) + +// ParseErrata grabs all of the OpenBSD errata from an html page +func ParseErrata(s string) ([]string, error) { + var data []string + resp, err := http.Get(s) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + doc, err := html.Parse(resp.Body) + if err != nil { + return nil, err + } + + var f func(*html.Node) + f = func(node *html.Node) { + if node.Type == html.ElementNode && node.Data == "strong" { + if node.FirstChild != nil { + data = append(data, node.FirstChild.Data) + return + } + } + for child := node.FirstChild; child != nil; child = child.NextSibling { + f(child) + } + } + f(doc) + + return data, nil +} diff --git a/errata_test.go b/errata_test.go new file mode 100644 index 0000000..f3379b0 --- /dev/null +++ b/errata_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "testing" +) + +func TestParseErrata(t *testing.T) { + got, err := ParseErrata("https://www.openbsd.org/errata66.html") + if err != nil { + t.Error(err) + } + l := len(got) + if l == 0 { + t.Errorf("errata count %d; want > 0", l) + } +} diff --git a/go.mod b/go.mod index 14474c1..9d3eaa1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module git.sr.ht/~qbit/mcchunkie go 1.13 + +require ( + github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d + go.etcd.io/bbolt v1.3.3 + golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 + golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5403a80 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d h1:Vf/EQgAfg8/CBUQv9te7UJreZ9iKKouB2gb8UIRM4jQ= +github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..bff1c12 --- /dev/null +++ b/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "time" + + "github.com/matrix-org/gomatrix" +) + +var ( + username string +) + +func sendMessage(c *gomatrix.Client, roomID, message string) error { + _, err := c.UserTyping(roomID, true, 3) + if err != nil { + return err + } + + c.SendText(roomID, message) + + _, err = c.UserTyping(roomID, false, 0) + if err != nil { + return err + } + return nil +} + +func main() { + var store, err = NewStore("mcchunkie.db") + if err != nil { + log.Fatalf("%s\n", err) + } + + var password, userID, accessToken, server string + var setup bool + + flag.StringVar(&username, "user", "", "username to connect to matrix server with") + flag.StringVar(&server, "server", "", "matrix server") + flag.BoolVar(&setup, "s", false, "setup account") + + flag.Parse() + + if server == "" { + server, _ = store.get("config", "server") + if server == "" { + log.Fatalln("please specify a server") + } + + store.set("config", "server", server) + } + + log.Printf("connecting to %s\n", server) + + cli, err := gomatrix.NewClient( + server, + "", + "", + ) + + if setup { + log.Println("requesting access token") + password, err = prompt(fmt.Sprintf("Password for '%s': ", username)) + if err != nil { + log.Fatalln(err) + } + fmt.Println() + + log.Printf("'%s' : '%s'\n", username, password) + + resp, err := cli.Login(&gomatrix.ReqLogin{ + Type: "m.login.password", + User: username, + Password: password, + }) + if err != nil { + log.Fatalln(err) + } + + store.set("account", "username", username) + store.set("account", "access_token", resp.AccessToken) + store.set("account", "user_id", resp.UserID) + } else { + accessToken, _ = store.get("account", "access_token") + userID, _ = store.get("account", "user_id") + } + + cli.SetCredentials(userID, accessToken) + cli.Store = store + syncer := gomatrix.NewDefaultSyncer(username, store) + cli.Client = http.DefaultClient + cli.Syncer = syncer + + /* + if _, err := cli.JoinRoom("!tmCVBJAeuKjCfihUjb:cobryce.com", "", nil); err != nil { + log.Fatalln(err) + } + if _, err := cli.JoinRoom("!sFPUeGfHqjiItcjNIN:matrix.org", "", nil); err != nil { + log.Fatalln(err) + } + if _, err := cli.JoinRoom("!ALCZnrYadLGSySIFZr:matrix.org", "", nil); err != nil { + log.Fatalln(err) + } + */ + if _, err := cli.JoinRoom("!LTxJpLHtShMVmlpwmZ:tapenet.org", "", nil); err != nil { + log.Fatalln(err) + } + + syncer.OnEventType("m.room.message", func(ev *gomatrix.Event) { + fmt.Printf("'%s' == '%s'\n", ev.Sender, username) + if ev.Sender == fmt.Sprintf("@%s", username) { + return + } + + if mtype, ok := ev.MessageType(); ok { + switch mtype { + case "m.text": + if post, ok := ev.Body(); ok { + log.Printf("%s: '%s'", ev.Sender, post) + } + } + } + }) + + //cli.SendText("!tmCVBJAeuKjCfihUjb:cobryce.com", "Butts") + sendMessage(cli, "!LTxJpLHtShMVmlpwmZ:tapenet.org", "Typing hi!") + sendMessage(cli, "!tmCVBJAeuKjCfihUjb:cobryce.com", "Butts") + + avatar := "https://deftly.net/mcchunkie.png" + aurl, err := cli.GetAvatarURL() + + if aurl != avatar { + log.Printf("Setting avatar to: '%s'", avatar) + err = cli.SetAvatarURL(avatar) + if err != nil { + fmt.Println("Unable to set avatar: ", err) + } + } else { + log.Printf("avatar already set") + } + + for { + log.Println("syncing..") + if err := cli.Sync(); err != nil { + fmt.Println("Sync() returned ", err) + } + + time.Sleep(1 * time.Second) + } +} diff --git a/mcchunkie.png b/mcchunkie.png new file mode 100755 index 0000000..d6dedf4 Binary files /dev/null and b/mcchunkie.png differ diff --git a/pass_reader.go b/pass_reader.go new file mode 100644 index 0000000..bda7996 --- /dev/null +++ b/pass_reader.go @@ -0,0 +1,18 @@ +package main + +import ( + "os" + + "golang.org/x/crypto/ssh/terminal" +) + +func prompt(p string) (string, error) { + oldState, err := terminal.MakeRaw(0) + if err != nil { + panic(err) + } + defer terminal.Restore(0, oldState) + + t := terminal.NewTerminal(os.Stdin, p) + return t.ReadPassword(p) +} diff --git a/store.go b/store.go new file mode 100644 index 0000000..7d89ea6 --- /dev/null +++ b/store.go @@ -0,0 +1,150 @@ +package main + +import ( + "bytes" + "encoding/gob" + "log" + + "github.com/matrix-org/gomatrix" + bolt "go.etcd.io/bbolt" +) + +// MCStore implements a gomatrix.Storer and exposes a bbolt db to be used for +// application storage (account info, config info etc). +type MCStore struct { + db *bolt.DB +} + +// NewStore creates a new MCStore instance populated with the proper buckets. +func NewStore(path string) (*MCStore, error) { + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return nil, err + } + + s := &MCStore{db: db} + + err = s.db.Update(func(tx *bolt.Tx) error { + buckets := []string{"filter", "batch", "room", "account", "config", "errata"} + for _, b := range buckets { + if _, err := tx.CreateBucketIfNotExists([]byte(b)); err != nil { + return err + } + } + return nil + }) + + if err != nil { + s.db.Close() + return nil, err + } + return s, nil +} + +func (s *MCStore) set(bucket, key, value string) error { + err := s.db.Update(func(tx *bolt.Tx) error { + bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)) + if err != nil { + return err + } + + err = bkt.Put([]byte(key), []byte(value)) + if err != nil { + return err + } + return nil + }) + + if err != nil { + log.Fatal(err) + } + return nil +} + +func (s *MCStore) get(bucket, key string) (string, error) { + var result string + return result, s.db.View(func(tx *bolt.Tx) error { + bkt := tx.Bucket([]byte(bucket)) + data := bkt.Get([]byte(key)) + + if data == nil { + return nil + } + + result = string(data) + + return nil + }) +} + +func (s *MCStore) getBytes(bucket, key string) ([]byte, error) { + var result []byte + return result, s.db.View(func(tx *bolt.Tx) error { + bkt := tx.Bucket([]byte(bucket)) + data := bkt.Get([]byte(key)) + + if data == nil { + return nil + } + + result = data + return nil + }) +} + +func (s *MCStore) encodeRoom(room *gomatrix.Room) ([]byte, error) { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(room) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (s *MCStore) decodeRoom(room []byte) (*gomatrix.Room, error) { + var r *gomatrix.Room + buf := bytes.NewBuffer(room) + dec := gob.NewDecoder(buf) + err := dec.Decode(&r) + if err != nil { + return nil, err + } + return r, nil +} + +// SaveFilterID exposed for gomatrix +func (s *MCStore) SaveFilterID(userID, filterID string) { + _ = s.set("filter", userID, filterID) + +} + +// LoadFilterID exposed for gomatrix +func (s *MCStore) LoadFilterID(userID string) string { + filter, _ := s.get("filter", userID) + return string(filter) +} + +// SaveNextBatch exposed for gomatrix +func (s *MCStore) SaveNextBatch(userID, nextBatchToken string) { + _ = s.set("batch", userID, nextBatchToken) +} + +// LoadNextBatch exposed for gomatrix +func (s *MCStore) LoadNextBatch(userID string) string { + batch, _ := s.get("batch", userID) + return string(batch) +} + +// SaveRoom exposed for gomatrix +func (s *MCStore) SaveRoom(room *gomatrix.Room) { + b, _ := s.encodeRoom(room) + _ = s.set("room", room.ID, string(b)) +} + +// LoadRoom exposed for gomatrix +func (s *MCStore) LoadRoom(roomID string) *gomatrix.Room { + b, _ := s.getBytes("room", roomID) + room, _ := s.decodeRoom(b) + return room +}