package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "github.com/rs/zerolog/log" "gitlab.com/beeper/standupbot/store" "maunium.net/go/maulogger/v2" "maunium.net/go/mautrix" mcrypto "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" "maunium.net/go/mautrix/util/dbutil" ) type config struct { Username string Password string Homeserver string DeviceID id.DeviceID DisplayName string } var client *mautrix.Client var stateStore *store.StateStore var olmMachine *mcrypto.OlmMachine func main() { //log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) c := config{ Homeserver: "matrix.n6e.de", Username: "@testbot:n6e.de", Password: "PASSWORD", DeviceID: id.DeviceID("testbot2"), DisplayName: "Test BOT SSP", } log.Debug().Interface("config", c).Msg("Get Config") log.Debug().Msg("Create sqlite3 db file") db, err := sql.Open("sqlite3", "./standupbot.db") if err != nil { log.Fatal().Err(err).Msg("Cant create sqlite database") } log.Debug().Msg("Created sqlite3 tables") stateStore = store.NewStateStore(db) err = stateStore.CreateTables() if err != nil { log.Fatal().Err(err).Msg("Cant create store tables") } log.Debug().Msg("Create Mautrix Client") c.DeviceID = FindDeviceID(db, id.UserID(c.Username).String()) log.Debug().Str("deviceID", c.DeviceID.String()).Msg("New DeviceID") client, err = mautrix.NewClient(c.Homeserver, "", "") log.Debug().Msg("Start login") res, err := client.Login(&mautrix.ReqLogin{ Type: mautrix.AuthTypePassword, Identifier: mautrix.UserIdentifier{ Type: mautrix.IdentifierTypeUser, User: c.Username, }, Password: c.Password, InitialDeviceDisplayName: c.DisplayName, DeviceID: c.DeviceID, StoreCredentials: true, }) if err != nil { log.Fatal().Err(err).Msg("Cant Login to Matrix") } log.Debug().Str("access token", res.AccessToken).Msg("Login Successfull") client.Store = stateStore stateStore.Client = client myrooms, err := client.JoinedRooms() if err != nil { log.Fatal().Err(err).Msg("Cant get rooms account is joinmed") } log.Debug().Interface("rooms", myrooms).Msg("Got Joined Rooms") log.Debug().Msg("Create dbutils Database") logger := maulogger.Createm(map[string]interface{}{}) l := dbutil.MauLogger(logger) d, err := dbutil.NewWithDB(db, "sqlite3") if err != nil { log.Fatal().Err(err).Msg("Cant create dbutile database") } log.Debug().Msg("Create CryptoStore") sqlCryptoStore := mcrypto.NewSQLCryptoStore( d, l, c.Username, c.DeviceID, []byte("cryptostore_key"), ) log.Debug().Msg("Upgrade") err = sqlCryptoStore.DB.Upgrade() if err != nil { log.Fatal().Err(err).Msg("Failed Upgrade Database") } olmMachine = mcrypto.NewOlmMachine(client, &CryptoLogger{}, sqlCryptoStore, stateStore) err = olmMachine.Load() if err != nil { log.Fatal().Err(err).Msg("Cant load OLM Machine") } syncer := client.Syncer.(*mautrix.DefaultSyncer) syncer.OnSync(func(resp *mautrix.RespSync, since string) bool { log.Debug().Str("since", since).Interface("resp", resp).Msg("OnSync Called") olmMachine.ProcessSyncResponse(resp, since) return true }) //client.LeaveRoom(id.RoomID("!wpUHkAOzYMbXiLhyVS:54gradsoftware.de")) //client.JoinRoomByID(id.RoomID("!wpUHkAOzYMbXiLhyVS:54gradsoftware.de")) syncer.OnEvent(func(source mautrix.EventSource, evt *event.Event) { log.Debug().Interface("EventSource", source).Interface("evt", evt).Interface("message", evt.Content.Parsed).Msg("Event") }) syncer.OnEventType(event.StateMember, func(_ mautrix.EventSource, e *event.Event) { log.Info().Msg("StateMember Event") olmMachine.HandleMemberEvent(e) stateStore.SetMembership(e) username := id.UserID(c.Username) if e.GetStateKey() == username.String() && e.Content.AsMember().Membership == event.MembershipInvite { log.Info().Msgf("Joining ", e.RoomID) _, err = client.JoinRoomByID(e.RoomID) if err != nil { log.Error().Err(err).Str("roomid", e.RoomID.String()).Msg("Cant Join Room") return } log.Info().Msgf("Joined %s sucessfully", e.RoomID.String()) } else if e.GetStateKey() == username.String() && e.Content.AsMember().Membership.IsLeaveOrBan() { log.Info().Msgf("Left or banned from %s", e.RoomID) } }) syncer.OnEventType(event.EventEncrypted, func(source mautrix.EventSource, event *event.Event) { log.Info().Msgf("Got Encryptet event, try to encrypt") decryptedEvent, err := olmMachine.DecryptMegolmEvent(event) if err != nil { log.Error().Err(err).Str("sender", event.Sender.String()).Str("Roomid", event.RoomID.String()).Msg("Failed to decrypt Message") } else { log.Debug().Interface("event", decryptedEvent).Msgf("Received encrypted event from %s in %s", event.Sender, event.RoomID) handleEvent(decryptedEvent) } }) syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { handleEvent(evt) }) syncer.OnEventType(event.StateEncryption, func(_ mautrix.EventSource, e *event.Event) { stateStore.SetEncryptionEvent(e) }) log.Debug().Msg("Start Matrix Bot") err = client.Sync() if err != nil { log.Fatal().Err(err).Msg("Error running bot") } } func handleEvent(e *event.Event) { log.Warn().Str("body", e.Content.AsMessage().Body).Msg("Handle Message") if e.Content.AsMessage().Body == "!hello" { r := format.RenderMarkdown("World", false, false) sendEasyMessage(e.RoomID, r) } if e.Content.AsMessage().Body == "!stop" { client.StopSync() } } func sendEasyMessage(roomId id.RoomID, content event.MessageEventContent) (resp *mautrix.RespSendEvent, err error) { if stateStore.IsEncrypted(roomId) { log.Debug().Msgf("Sending encrypted event to %s", roomId) encrypted, err := olmMachine.EncryptMegolmEvent(roomId, event.EventMessage, content) // These three errors mean we have to make a new Megolm session if err == mcrypto.SessionExpired || err == mcrypto.SessionNotShared || err == mcrypto.NoGroupSession { err = olmMachine.ShareGroupSession(roomId, stateStore.GetRoomMembers(roomId)) if err != nil { log.Error().Msgf("Failed to share group session to %s: %s", roomId, err) return nil, err } encrypted, err = olmMachine.EncryptMegolmEvent(roomId, event.EventMessage, content) } if err != nil { log.Error().Msgf("Failed to encrypt message to %s: %s", roomId, err) return nil, err } //encrypted.RelatesTo = content.RelatesTo // The m.relates_to field should be unencrypted, so copy it. return client.SendMessageEvent(roomId, event.EventEncrypted, encrypted) } else { client.SendMessageEvent(roomId, event.EventMessage, content) } return nil, nil } func FindDeviceID(db *sql.DB, accountID string) (deviceID id.DeviceID) { err := db.QueryRow("SELECT device_id FROM crypto_account WHERE account_id=$1", accountID).Scan(&deviceID) if err != nil && err != sql.ErrNoRows { log.Warn().Err(err).Msg("Failed to scan device ID") } return }