package main import ( "context" "log" "matrix-bot/bot" "os" "path/filepath" "sync" "maunium.net/go/mautrix" "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/crypto/cryptohelper" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) var homeserver string var username string var password string var roomID string var userId string var accessToken string var deviceId string var pickleKeyString string var recoveryKey string var cryptoDBPath string func setupCryptoHelper(cli *mautrix.Client) (*cryptohelper.CryptoHelper, error) { // remember to use a secure key for the pickle key in production pickleKey := []byte(pickleKeyString) if cryptoDBPath != "" { dir := filepath.Dir(cryptoDBPath) if dir != "." { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err } } } helper, err := cryptohelper.NewCryptoHelper(cli, pickleKey, cryptoDBPath) if err != nil { return nil, err } err = helper.Init(context.Background()) if err != nil { return nil, err } return helper, nil } func verifyWithRecoveryKey(machine *crypto.OlmMachine) (err error) { ctx := context.Background() 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 } func envOrFatal(key string) string { val := os.Getenv(key) if val == "" { log.Fatalf("missing required env var: %s", key) } return val } func envOrDefault(key string, def string) string { val := os.Getenv(key) if val == "" { return def } return val } func loadConfig() { homeserver = envOrFatal("MATRIX_HOMESERVER") username = envOrDefault("MATRIX_USERNAME", "") password = envOrDefault("MATRIX_PASSWORD", "") roomID = envOrDefault("MATRIX_ROOM_ID", "") userId = envOrFatal("MATRIX_USER_ID") accessToken = envOrFatal("MATRIX_ACCESS_TOKEN") deviceId = envOrFatal("MATRIX_DEVICE_ID") pickleKeyString = envOrFatal("MATRIX_PICKLE_KEY") recoveryKey = envOrFatal("MATRIX_RECOVERY_KEY") cryptoDBPath = envOrDefault("MATRIX_CRYPTO_DB", "crypto.db") } func main() { var err error loadConfig() bot.Load() client, err := mautrix.NewClient(homeserver, id.UserID(userId), accessToken) if err != nil { log.Fatal(err) } client.DeviceID = id.DeviceID(deviceId) syncer := mautrix.NewDefaultSyncer() client.Syncer = syncer cryptoHelper, err := setupCryptoHelper(client) if err != nil { log.Fatal(err) } client.Crypto = cryptoHelper syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) { // Ignore our own messages if evt.Sender.String() == userId { return } content := evt.Content.AsMessage() if content.MsgType != event.MsgText { return } log.Printf("Message from %s: %s\n", evt.Sender, content.Body) response := bot.HandleCommand(content.Body, evt.Sender.String(), evt.RoomID.String(), ctx, client, evt, &event.RelatesTo{ InReplyTo: &event.InReplyTo{EventID: evt.ID}, }) if response == nil { return } for _, resp := range response { switch r := resp.(type) { case event.MessageEventContent: _, err := client.SendMessageEvent(ctx, evt.RoomID, event.EventMessage, r) if err != nil { log.Println("Send error:", err) } default: log.Println("Unknown response type") } } }) ready := make(chan struct{}) var once sync.Once syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool { once.Do(func() { close(ready) }) return true }) go func() { if err := client.Sync(); err != nil { log.Fatal(err) } }() log.Println("Waiting for initial sync...") <-ready log.Println("Sync complete") if err := verifyWithRecoveryKey(cryptoHelper.Machine()); err != nil { log.Fatal(err) } log.Println("Bot is running...") select {} }