162 lines
4.4 KiB
Go
162 lines
4.4 KiB
Go
|
package mautrix
|
||
|
|
||
|
import (
|
||
|
"maunium.net/go/mautrix/event"
|
||
|
"maunium.net/go/mautrix/id"
|
||
|
)
|
||
|
|
||
|
// Storer is an interface which must be satisfied to store client data.
|
||
|
//
|
||
|
// You can either write a struct which persists this data to disk, or you can use the
|
||
|
// provided "InMemoryStore" which just keeps data around in-memory which is lost on
|
||
|
// restarts.
|
||
|
type Storer interface {
|
||
|
SaveFilterID(userID id.UserID, filterID string)
|
||
|
LoadFilterID(userID id.UserID) string
|
||
|
SaveNextBatch(userID id.UserID, nextBatchToken string)
|
||
|
LoadNextBatch(userID id.UserID) string
|
||
|
SaveRoom(room *Room)
|
||
|
LoadRoom(roomID id.RoomID) *Room
|
||
|
}
|
||
|
|
||
|
// InMemoryStore implements the Storer interface.
|
||
|
//
|
||
|
// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
|
||
|
// or next batch tokens on any goroutine other than the syncing goroutine: the one
|
||
|
// which called Client.Sync().
|
||
|
type InMemoryStore struct {
|
||
|
Filters map[id.UserID]string
|
||
|
NextBatch map[id.UserID]string
|
||
|
Rooms map[id.RoomID]*Room
|
||
|
}
|
||
|
|
||
|
// SaveFilterID to memory.
|
||
|
func (s *InMemoryStore) SaveFilterID(userID id.UserID, filterID string) {
|
||
|
s.Filters[userID] = filterID
|
||
|
}
|
||
|
|
||
|
// LoadFilterID from memory.
|
||
|
func (s *InMemoryStore) LoadFilterID(userID id.UserID) string {
|
||
|
return s.Filters[userID]
|
||
|
}
|
||
|
|
||
|
// SaveNextBatch to memory.
|
||
|
func (s *InMemoryStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
|
||
|
s.NextBatch[userID] = nextBatchToken
|
||
|
}
|
||
|
|
||
|
// LoadNextBatch from memory.
|
||
|
func (s *InMemoryStore) LoadNextBatch(userID id.UserID) string {
|
||
|
return s.NextBatch[userID]
|
||
|
}
|
||
|
|
||
|
// SaveRoom to memory.
|
||
|
func (s *InMemoryStore) SaveRoom(room *Room) {
|
||
|
s.Rooms[room.ID] = room
|
||
|
}
|
||
|
|
||
|
// LoadRoom from memory.
|
||
|
func (s *InMemoryStore) LoadRoom(roomID id.RoomID) *Room {
|
||
|
return s.Rooms[roomID]
|
||
|
}
|
||
|
|
||
|
// UpdateState stores a state event. This can be passed to DefaultSyncer.OnEvent to keep all room state cached.
|
||
|
func (s *InMemoryStore) UpdateState(_ EventSource, evt *event.Event) {
|
||
|
if !evt.Type.IsState() {
|
||
|
return
|
||
|
}
|
||
|
room := s.LoadRoom(evt.RoomID)
|
||
|
if room == nil {
|
||
|
room = NewRoom(evt.RoomID)
|
||
|
s.SaveRoom(room)
|
||
|
}
|
||
|
room.UpdateState(evt)
|
||
|
}
|
||
|
|
||
|
// NewInMemoryStore constructs a new InMemoryStore.
|
||
|
func NewInMemoryStore() *InMemoryStore {
|
||
|
return &InMemoryStore{
|
||
|
Filters: make(map[id.UserID]string),
|
||
|
NextBatch: make(map[id.UserID]string),
|
||
|
Rooms: make(map[id.RoomID]*Room),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AccountDataStore uses account data to store the next batch token, and
|
||
|
// reuses the InMemoryStore for all other operations.
|
||
|
type AccountDataStore struct {
|
||
|
*InMemoryStore
|
||
|
eventType string
|
||
|
client *Client
|
||
|
}
|
||
|
|
||
|
type accountData struct {
|
||
|
NextBatch string `json:"next_batch"`
|
||
|
}
|
||
|
|
||
|
// SaveNextBatch to account data.
|
||
|
func (s *AccountDataStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
|
||
|
if userID.String() != s.client.UserID.String() {
|
||
|
panic("AccountDataStore must only be used with bots")
|
||
|
}
|
||
|
|
||
|
data := accountData{
|
||
|
NextBatch: nextBatchToken,
|
||
|
}
|
||
|
|
||
|
err := s.client.SetAccountData(s.eventType, data)
|
||
|
if err != nil {
|
||
|
if s.client.Logger != nil {
|
||
|
s.client.Logger.Debugfln("failed to save next batch token to account data: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// LoadNextBatch from account data.
|
||
|
func (s *AccountDataStore) LoadNextBatch(userID id.UserID) string {
|
||
|
if userID.String() != s.client.UserID.String() {
|
||
|
panic("AccountDataStore must only be used with bots")
|
||
|
}
|
||
|
|
||
|
data := &accountData{}
|
||
|
|
||
|
err := s.client.GetAccountData(s.eventType, data)
|
||
|
if err != nil {
|
||
|
if s.client.Logger != nil {
|
||
|
s.client.Logger.Debugfln("failed to load next batch token to account data: %s", err.Error())
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
return data.NextBatch
|
||
|
}
|
||
|
|
||
|
// NewAccountDataStore returns a new AccountDataStore, which stores
|
||
|
// the next_batch token as a custom event in account data in the
|
||
|
// homeserver.
|
||
|
//
|
||
|
// AccountDataStore is only appropriate for bots, not appservices.
|
||
|
//
|
||
|
// eventType should be a reversed DNS name like tld.domain.sub.internal and
|
||
|
// must be unique for a client. The data stored in it is considered internal
|
||
|
// and must not be modified through outside means. You should also add a filter
|
||
|
// for account data changes of this event type, to avoid ending up in a sync
|
||
|
// loop:
|
||
|
//
|
||
|
// mautrix.Filter{
|
||
|
// AccountData: mautrix.FilterPart{
|
||
|
// Limit: 20,
|
||
|
// NotTypes: []event.Type{
|
||
|
// event.NewEventType(eventType),
|
||
|
// },
|
||
|
// },
|
||
|
// }
|
||
|
// mautrix.Client.CreateFilter(...)
|
||
|
func NewAccountDataStore(eventType string, client *Client) *AccountDataStore {
|
||
|
return &AccountDataStore{
|
||
|
InMemoryStore: NewInMemoryStore(),
|
||
|
eventType: eventType,
|
||
|
client: client,
|
||
|
}
|
||
|
}
|