~edwargix/axon

7c9daffd78db7cb8f2646b0e8e9f5cc60a635c64 — David Florness 3 years ago 880a45d
Implement autojoin
6 files changed, 293 insertions(+), 0 deletions(-)

A .gitignore
A autojoin/cmd.go
M go.mod
A go.sum
A main.go
A shared/auth.go
A .gitignore => .gitignore +1 -0
@@ 0,0 1,1 @@
axon

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)
}