A .gitignore => .gitignore +1 -0
A autojoin/cmd.go => autojoin/cmd.go +65 -0
@@ 0,0 1,65 @@
+package autojoin
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/rwxrob/cmdtab"
+
+ "git.hnitbjorg.xyz/~edwargix/axon/shared"
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+)
+
+func init() {
+ x := cmdtab.New("autojoin")
+ x.Summary = "wait for room invitations and automatically join"
+
+ x.Method = func(args []string) error {
+ if len(args) != 0 {
+ return x.UsageError()
+ }
+ autojoin()
+ return nil
+ }
+}
+
+func autojoin() {
+ data, err := shared.GetAuthInfo()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ client, err := mautrix.NewClient(data.Homeserver, data.UserID, data.AccessToken)
+ if err != nil {
+ panic(err)
+ }
+
+ syncer := client.Syncer.(*mautrix.DefaultSyncer)
+ syncer.OnEvent(client.Store.(*mautrix.InMemoryStore).UpdateState)
+ syncer.OnEventType(event.StateMember, func(_ mautrix.EventSource, event *event.Event) {
+ // skip non-member events and member events that don't pertain
+ // to us
+ if *event.StateKey != data.UserID.String() || event.Type.Type != "m.room.member" {
+ return
+ }
+ content := event.Content.AsMember()
+ // skip non-invite messages
+ if content.Membership != "invite" {
+ return
+ }
+ fmt.Printf("Joining room %s...\n", event.RoomID)
+ _, err := client.JoinRoomByID(event.RoomID)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("Joined room %s\n", event.RoomID)
+ })
+
+ fmt.Println("Looking for rooms to join...")
+ err = client.Sync()
+ if err != nil {
+ panic(err)
+ }
+}
M go.mod => go.mod +7 -0
@@ 1,3 1,10 @@
module git.hnitbjorg.xyz/~edwargix/axon
go 1.16
+
+require (
+ github.com/kyoh86/xdg v1.2.0
+ github.com/rwxrob/cmdtab v0.10.2
+ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
+ maunium.net/go/mautrix v0.9.12
+)
A go.sum => go.sum +75 -0
@@ 0,0 1,75 @@
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
+github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
+github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
+github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/rwxrob/cmdtab v0.10.2 h1:DElMFAqlJcfXTiZUSgU/c2AHAdRbxpOYQdYLvMpjF3k=
+github.com/rwxrob/cmdtab v0.10.2/go.mod h1:AZ16dJkvySlkGy2PBZDcUcRdar+SLv7t1an8OOKkOag=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
+github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
+maunium.net/go/mautrix v0.9.12 h1:iNsKOCyK/BqN6Q/7A1aLj1B0/lMbr81LIg+Lry1Vjuo=
+maunium.net/go/mautrix v0.9.12/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
A main.go => main.go +15 -0
@@ 0,0 1,15 @@
+package main
+
+import (
+ "github.com/rwxrob/cmdtab"
+ _ "git.hnitbjorg.xyz/~edwargix/axon/autojoin"
+)
+
+func main() {
+ x := cmdtab.New("axon", "autojoin")
+ x.Summary = "a set of simple Matrix CS utilities"
+ x.Usage = "(autojoin)"
+ x.Version = "v0.0.0"
+
+ cmdtab.Execute("axon")
+}
A shared/auth.go => shared/auth.go +130 -0
@@ 0,0 1,130 @@
+package shared
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "syscall"
+
+ "github.com/kyoh86/xdg"
+ "golang.org/x/crypto/ssh/terminal"
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/id"
+)
+
+type AuthInfo struct {
+ AccessToken string `json:"access_token"`
+ DeviceID id.DeviceID `json:"device_id"`
+ Homeserver string `json:"homeserver"`
+ UserID id.UserID `json:"user_id"`
+ Username string `json:"username"`
+}
+
+var authFname = xdg.DataHome() + "/axon/auth.json"
+
+func GetAuthInfo() (authInfo *AuthInfo, err error) {
+ if _, err = os.Stat(authFname); os.IsNotExist(err) {
+ fmt.Println("no cached auth data found; inquiring for credentials")
+ authInfo, err = inquireForAuthInfo()
+ if err == nil {
+ authInfo.save()
+ }
+ return authInfo, err
+ }
+
+ jsonBytes, err := ioutil.ReadFile(authFname)
+ if err != nil {
+ return nil, fmt.Errorf("error reading auth file %s: %s", authFname, err)
+ }
+
+ authInfo = &AuthInfo{}
+ err = json.Unmarshal(jsonBytes, authInfo)
+ if err != nil {
+ return nil, fmt.Errorf("error unmarshalling auth file: %s", err)
+ }
+
+ return authInfo, nil
+}
+
+func stripNewline(s string) string {
+ if s == "" {
+ return s
+ }
+ if s[len(s)-1] == '\n' {
+ return s[:len(s)-1]
+ }
+ return s
+}
+
+func inquireForAuthInfo() (authInfo *AuthInfo, err error) {
+ reader := bufio.NewReader(os.Stdin)
+
+ authInfo = &AuthInfo{}
+
+ fmt.Print("Matrix username (@localpart:domain): ")
+ username, err := reader.ReadString('\n')
+ if err != nil {
+ return nil, fmt.Errorf("error reading username: %s", err)
+ }
+ username = stripNewline(username)
+ localPart, serverName, err := id.UserID(username).Parse()
+ if err != nil {
+ return nil, fmt.Errorf("error parsing username: %s", err)
+ }
+ authInfo.Username = localPart
+
+ wellKnownResp, err := mautrix.DiscoverClientAPI(serverName)
+ if err != nil {
+ return nil, fmt.Errorf("error discovering homeserver: %s", err)
+ }
+ if wellKnownResp == nil {
+ fmt.Println("You should tell your homeserver sysadmin to setup /.well-known/matrix/client")
+ fmt.Print("homeserver URL: ")
+ homeserver, err := reader.ReadString('\n')
+ if err != nil {
+ return nil, fmt.Errorf("error reading homeserver: %s", err)
+ }
+ authInfo.Homeserver = stripNewline(homeserver)
+ } else {
+ authInfo.Homeserver = wellKnownResp.Homeserver.BaseURL
+ }
+
+ fmt.Print("password: ")
+ rawPassword, err := terminal.ReadPassword(syscall.Stdin)
+ if err != nil {
+ return nil, fmt.Errorf("error reading password: %s", err)
+ }
+ // entering a password doesn't insert a newline
+ fmt.Println()
+
+ client, err := mautrix.NewClient(authInfo.Homeserver, "", "")
+ if err != nil {
+ return nil, fmt.Errorf("error creating mautrix client: %s", err)
+ }
+ fmt.Printf("attempting login...\n")
+ respLogin, err := client.Login(&mautrix.ReqLogin{
+ Type: "m.login.password",
+ Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: authInfo.Username},
+ Password: string(rawPassword),
+ StoreCredentials: true,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("error logging in with given credentials: %s", err)
+ }
+
+ authInfo.AccessToken = respLogin.AccessToken
+ authInfo.DeviceID = respLogin.DeviceID
+ authInfo.UserID = respLogin.UserID
+
+ return authInfo, nil
+}
+
+func (authInfo *AuthInfo) save() error {
+ jsonBytes, err := json.Marshal(*authInfo)
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(authFname, jsonBytes, 0600)
+}