diff --git a/src/matrix/.client.go.swp b/src/matrix/.client.go.swp new file mode 100644 index 0000000..2c04bfa Binary files /dev/null and b/src/matrix/.client.go.swp differ diff --git a/src/matrix/client.go b/src/matrix/client.go index 60e38d0..3b72da2 100644 --- a/src/matrix/client.go +++ b/src/matrix/client.go @@ -1,7 +1,147 @@ package matrix +import ( + "context" + "strings" + "sync" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/crypto" + "maunium.net/go/mautrix/crypto/cryptohelper" +) + type Client struct { - client *mautrix.Client + client *mautrix.Client + accessToken string + deviceId string + pickleKeyString string } -func New // https://chrastecky.dev/post/11 +func NewClient(ctx context.Context, uid, password, recoveryKey string) (*Client, error) { + ctx, can := context.WithCancel(ctx) + defer can() + + // uid like @username:homeserver.com + username := strings.Split(uid, ":")[0] + homeserver := strings.Split(uid, ":")[1] + + c, err := mautrix.NewClient(homeserver, uid, "") + if err != nil { + return nil, err + } + syncer := mautrix.NewDefaultSyncer() + c.Syncer = syncer + cryptoHelper, err := setupCryptoHelper(ctx, client) + if err != nil { + panic(err) + } + c.Crypto = cryptoHelper + + readyChan := make(chan bool) + var once sync.Once + syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool { + once.Do(func() { + close(readyChan) + }) + return true + }) + go func() { + if err := c.Sync(); err != nil { + can() + panic(err) + } + }() + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-readyChan: + } + + if err := verifyWithRecoveryKey(ctx, cryptoHelper.Machine()); err != nil { + return nil, err + } + + client := &Client{ + client: c, + } + + return client, client.login(ctx, username, password) +} + +func (c *Client) login(ctx context.Context, username, password string) error { + if c.accessToken != "" { + return nil + } + + resp, err := c.client.Login(ctx, &mautrix.ReqLogin{ + Type: mautrix.AuthTypePassword, + Identifier: mautrix.UserIdentifier{ + User: username, + Type: mautrix.IdentifierTypeUser, + }, + Password: password, + StoreCredentials: true, + }) + if err != nil { + return err + } + + c.deviceId = resp.DeviceID + c.accessToken = resp.AccessToken + return nil +} + +/* + content := event.MessageEventContent{ + MsgType: event.MsgText, + Body: "Hello world from Go!", + } + + _, err = client.SendMessageEvent(context.Background(), roomID, event.EventMessage, content) + if err != nil { + panic(err) + } +*/ + +func setupCryptoHelper(ctx context.Context, cli *mautrix.Client) (*cryptohelper.CryptoHelper, error) { + // remember to use a secure key for the pickle key in production + pickleKey := []byte(pickleKeyString) + + // this is a path to the SQLite database you will use to store various data about your bot + dbPath := "crypto.db" + + helper, err := cryptohelper.NewCryptoHelper(cli, pickleKey, dbPath) + if err != nil { + return nil, err + } + + // initialize the database and other stuff + err = helper.Init(ctx) + if err != nil { + return nil, err + } + + return helper, nil +} + +func verifyWithRecoveryKey(ctx context.Context, machine *crypto.OlmMachine) (err error) { + keyId, keyData, err := machine.SSSS.GetDefaultKeyData(ctx) + if err != nil { + return + } + key, err := keyData.VerifyRecoveryKey(keyId, recoveryKey) + if err != nil { + return + } + err = machine.FetchCrossSigningKeysFromSSSS(ctx, key) + if err != nil { + return + } + err = machine.SignOwnDevice(ctx, machine.OwnIdentity()) + if err != nil { + return + } + err = machine.SignOwnMasterKey(ctx) + + return +}