mirror of https://github.com/matrix-org/go-neb.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.1 KiB
144 lines
5.1 KiB
package clients
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/matrix-org/go-neb/api"
|
|
"github.com/matrix-org/go-neb/database"
|
|
"github.com/matrix-org/go-neb/matrix"
|
|
log "github.com/sirupsen/logrus"
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/crypto"
|
|
mevt "maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
// BotClient represents one of the bot's sessions, with a specific User and Device ID.
|
|
// It can be used for sending messages and retrieving information about the rooms that
|
|
// the client has joined.
|
|
type BotClient struct {
|
|
*mautrix.Client
|
|
config api.ClientConfig
|
|
olmMachine *crypto.OlmMachine
|
|
stateStore *NebStateStore
|
|
}
|
|
|
|
// InitOlmMachine initializes a BotClient's internal OlmMachine given a client object and a Neb store,
|
|
// which will be used to store room information.
|
|
func (botClient *BotClient) InitOlmMachine(client *mautrix.Client, nebStore *matrix.NEBStore) (err error) {
|
|
|
|
var cryptoStore crypto.Store
|
|
cryptoLogger := CryptoMachineLogger{}
|
|
if sdb, ok := database.GetServiceDB().(*database.ServiceDB); ok {
|
|
// Create an SQL crypto store based on the ServiceDB used
|
|
db, dialect := sdb.GetSQLDb()
|
|
accountID := botClient.config.UserID.String() + "-" + client.DeviceID.String()
|
|
sqlCryptoStore := crypto.NewSQLCryptoStore(db, dialect, accountID, client.DeviceID, []byte(client.DeviceID.String()+"pickle"), cryptoLogger)
|
|
// Try to create the tables if they are missing
|
|
if err = sqlCryptoStore.CreateTables(); err != nil {
|
|
return
|
|
}
|
|
cryptoStore = sqlCryptoStore
|
|
cryptoLogger.Debug("Using SQL backend as the crypto store")
|
|
} else {
|
|
deviceID := client.DeviceID.String()
|
|
if deviceID == "" {
|
|
deviceID = "_empty_device_id"
|
|
}
|
|
cryptoStore, err = crypto.NewGobStore(deviceID + ".gob")
|
|
if err != nil {
|
|
return
|
|
}
|
|
cryptoLogger.Debug("Using gob storage as the crypto store")
|
|
}
|
|
|
|
botClient.stateStore = &NebStateStore{&nebStore.InMemoryStore}
|
|
olmMachine := crypto.NewOlmMachine(client, cryptoLogger, cryptoStore, botClient.stateStore)
|
|
if err = olmMachine.Load(); err != nil {
|
|
return
|
|
}
|
|
botClient.olmMachine = olmMachine
|
|
|
|
return nil
|
|
}
|
|
|
|
// Register registers a BotClient's Sync and StateMember event callbacks to update its internal state
|
|
// when new events arrive.
|
|
func (botClient *BotClient) Register(syncer mautrix.ExtensibleSyncer) {
|
|
syncer.OnEventType(mevt.StateMember, func(_ mautrix.EventSource, evt *mevt.Event) {
|
|
botClient.olmMachine.HandleMemberEvent(evt)
|
|
})
|
|
syncer.OnSync(botClient.syncCallback)
|
|
}
|
|
|
|
func (botClient *BotClient) syncCallback(resp *mautrix.RespSync, since string) bool {
|
|
botClient.stateStore.UpdateStateStore(resp)
|
|
botClient.olmMachine.ProcessSyncResponse(resp, since)
|
|
if err := botClient.olmMachine.CryptoStore.Flush(); err != nil {
|
|
log.WithError(err).Error("Could not flush crypto store")
|
|
}
|
|
return true
|
|
}
|
|
|
|
// DecryptMegolmEvent attempts to decrypt an incoming m.room.encrypted message using the session information
|
|
// already present in the OlmMachine. The corresponding decrypted event is then returned.
|
|
// If it fails, usually because the session is not known, an error is returned.
|
|
func (botClient *BotClient) DecryptMegolmEvent(evt *mevt.Event) (*mevt.Event, error) {
|
|
return botClient.olmMachine.DecryptMegolmEvent(evt)
|
|
}
|
|
|
|
// SendMessageEvent sends the given content to the given room ID using this BotClient as a message event.
|
|
// If the target room has enabled encryption, a megolm session is created if one doesn't already exist
|
|
// and the message is sent after being encrypted.
|
|
func (botClient *BotClient) SendMessageEvent(roomID id.RoomID, evtType mevt.Type, content interface{},
|
|
extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) {
|
|
|
|
olmMachine := botClient.olmMachine
|
|
if olmMachine.StateStore.IsEncrypted(roomID) {
|
|
// Check if there is already a megolm session
|
|
if sess, err := olmMachine.CryptoStore.GetOutboundGroupSession(roomID); err != nil {
|
|
return nil, err
|
|
} else if sess == nil || sess.Expired() || !sess.Shared {
|
|
// No error but valid, shared session does not exist
|
|
memberIDs, err := botClient.stateStore.GetJoinedMembers(roomID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Share group session with room members
|
|
if err = olmMachine.ShareGroupSession(roomID, memberIDs); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
enc, err := olmMachine.EncryptMegolmEvent(roomID, mevt.EventMessage, content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
content = enc
|
|
evtType = mevt.EventEncrypted
|
|
}
|
|
return botClient.Client.SendMessageEvent(roomID, evtType, content, extra...)
|
|
}
|
|
|
|
// Sync loops to keep syncing the client with the homeserver by calling the /sync endpoint.
|
|
func (botClient *BotClient) Sync() {
|
|
// Get the state store up to date
|
|
resp, err := botClient.SyncRequest(30000, "", "", true, mevt.PresenceOnline)
|
|
if err != nil {
|
|
log.WithError(err).Error("Error performing initial sync")
|
|
return
|
|
}
|
|
botClient.stateStore.UpdateStateStore(resp)
|
|
|
|
for {
|
|
if e := botClient.Client.Sync(); e != nil {
|
|
log.WithFields(log.Fields{
|
|
log.ErrorKey: e,
|
|
"user_id": botClient.config.UserID,
|
|
}).Error("Fatal Sync() error")
|
|
time.Sleep(10 * time.Second)
|
|
} else {
|
|
log.WithField("user_id", botClient.config.UserID).Info("Stopping Sync()")
|
|
return
|
|
}
|
|
}
|
|
}
|