186 lines
4.4 KiB
Go
186 lines
4.4 KiB
Go
// Package linkpearl represents the library itself
|
|
package linkpearl
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/crypto"
|
|
"maunium.net/go/mautrix/event"
|
|
|
|
"gitlab.com/etke.cc/linkpearl/config"
|
|
"gitlab.com/etke.cc/linkpearl/store"
|
|
)
|
|
|
|
const (
|
|
// DefaultMaxRetries for operations like autojoin
|
|
DefaultMaxRetries = 10
|
|
// DefaultAccountDataCache size
|
|
DefaultAccountDataCache = 1000
|
|
)
|
|
|
|
// Linkpearl object
|
|
type Linkpearl struct {
|
|
db *sql.DB
|
|
acc *lru.Cache[string, map[string]string]
|
|
acr *Crypter
|
|
aclr map[string]string
|
|
log config.Logger
|
|
api *mautrix.Client
|
|
olm *crypto.OlmMachine
|
|
store *store.Store
|
|
|
|
joinPermit func(*event.Event) bool
|
|
autoleave bool
|
|
maxretries int
|
|
}
|
|
|
|
type ReqPresence struct {
|
|
Presence event.Presence `json:"presence"`
|
|
StatusMsg string `json:"status_msg,omitempty"`
|
|
}
|
|
|
|
func setDefaults(cfg *config.Config) {
|
|
if cfg.AccountDataLogReplace == nil {
|
|
cfg.AccountDataLogReplace = make(map[string]string)
|
|
}
|
|
if cfg.MaxRetries == 0 {
|
|
cfg.MaxRetries = DefaultMaxRetries
|
|
}
|
|
if cfg.AccountDataCache == 0 {
|
|
cfg.AccountDataCache = DefaultAccountDataCache
|
|
}
|
|
if cfg.JoinPermit == nil {
|
|
// By default, we approve all join requests
|
|
cfg.JoinPermit = func(*event.Event) bool { return true }
|
|
}
|
|
}
|
|
|
|
func initCrypter(secret string) (*Crypter, error) {
|
|
if secret == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
return NewCrypter(secret)
|
|
}
|
|
|
|
// New linkpearl
|
|
func New(cfg *config.Config) (*Linkpearl, error) {
|
|
setDefaults(cfg)
|
|
api, err := mautrix.NewClient(cfg.Homeserver, "", "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
api.Logger = cfg.APILogger
|
|
|
|
acc, _ := lru.New[string, map[string]string](cfg.AccountDataCache) //nolint:errcheck // addressed in setDefaults()
|
|
acr, err := initCrypter(cfg.AccountDataSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lp := &Linkpearl{
|
|
db: cfg.DB,
|
|
acc: acc,
|
|
acr: acr,
|
|
aclr: cfg.AccountDataLogReplace,
|
|
api: api,
|
|
log: cfg.LPLogger,
|
|
joinPermit: cfg.JoinPermit,
|
|
autoleave: cfg.AutoLeave,
|
|
maxretries: cfg.MaxRetries,
|
|
}
|
|
|
|
storer := store.New(cfg.DB, cfg.Dialect, cfg.StoreLogger)
|
|
if err = storer.CreateTables(); err != nil {
|
|
return nil, err
|
|
}
|
|
lp.store = storer
|
|
lp.api.Store = storer
|
|
|
|
if err = lp.login(cfg.Login, cfg.Password); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !cfg.NoEncryption {
|
|
if err = lp.store.WithCrypto(lp.api.UserID, lp.api.DeviceID, cfg.StoreLogger); err != nil {
|
|
return nil, err
|
|
}
|
|
lp.olm = crypto.NewOlmMachine(lp.api, cfg.CryptoLogger, lp.store, lp.store)
|
|
if err = lp.olm.Load(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return lp, nil
|
|
}
|
|
|
|
// GetClient returns underlying API client
|
|
func (l *Linkpearl) GetClient() *mautrix.Client {
|
|
return l.api
|
|
}
|
|
|
|
// GetDB returns underlying DB object
|
|
func (l *Linkpearl) GetDB() *sql.DB {
|
|
return l.db
|
|
}
|
|
|
|
// GetStore returns underlying persistent store object, compatible with crypto.Store, crypto.StateStore and mautrix.Storer
|
|
func (l *Linkpearl) GetStore() *store.Store {
|
|
return l.store
|
|
}
|
|
|
|
// GetMachine returns underlying OLM machine
|
|
func (l *Linkpearl) GetMachine() *crypto.OlmMachine {
|
|
return l.olm
|
|
}
|
|
|
|
// GetAccountDataCrypter returns crypter used for account data (if any)
|
|
func (l *Linkpearl) GetAccountDataCrypter() *Crypter {
|
|
return l.acr
|
|
}
|
|
|
|
// SetPresence (own). See https://spec.matrix.org/v1.3/client-server-api/#put_matrixclientv3presenceuseridstatus
|
|
func (l *Linkpearl) SetPresence(presence event.Presence, message string) error {
|
|
req := ReqPresence{Presence: presence, StatusMsg: message}
|
|
u := l.GetClient().BuildClientURL("v3", "presence", l.GetClient().UserID, "status")
|
|
_, err := l.GetClient().MakeRequest("PUT", u, req, nil)
|
|
|
|
return err
|
|
}
|
|
|
|
// SetJoinPermit sets the the join permit callback function
|
|
func (l *Linkpearl) SetJoinPermit(value func(*event.Event) bool) {
|
|
l.joinPermit = value
|
|
}
|
|
|
|
// Start performs matrix /sync
|
|
func (l *Linkpearl) Start(optionalStatusMsg ...string) error {
|
|
l.initSync()
|
|
var statusMsg string
|
|
if len(optionalStatusMsg) > 0 {
|
|
statusMsg = optionalStatusMsg[0]
|
|
}
|
|
|
|
err := l.SetPresence(event.PresenceOnline, statusMsg)
|
|
if err != nil {
|
|
l.log.Error("cannot set presence: %v", err)
|
|
}
|
|
defer l.Stop()
|
|
|
|
l.log.Info("client has been started")
|
|
return l.api.Sync()
|
|
}
|
|
|
|
// Stop the client
|
|
func (l *Linkpearl) Stop() {
|
|
l.log.Debug("stopping the client")
|
|
err := l.api.SetPresence(event.PresenceOffline)
|
|
if err != nil {
|
|
l.log.Error("cannot set presence: %v", err)
|
|
}
|
|
l.api.StopSync()
|
|
l.log.Info("client has been stopped")
|
|
}
|