easybridge/account.go

542 lines
13 KiB
Go
Raw Permalink Normal View History

2020-02-26 15:07:33 +00:00
package main
2020-02-16 18:30:49 +00:00
import (
2020-02-16 21:07:41 +00:00
"fmt"
2020-02-26 20:36:35 +00:00
"reflect"
2020-02-17 18:02:00 +00:00
"strings"
2020-02-26 15:30:10 +00:00
"sync"
2020-02-17 08:41:08 +00:00
log "github.com/sirupsen/logrus"
2020-02-16 21:07:41 +00:00
2020-02-16 18:30:49 +00:00
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
2020-02-28 09:18:47 +00:00
// Necessary for them to register their protocols
_ "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/external"
2020-02-28 09:18:47 +00:00
_ "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/irc"
_ "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/mattermost"
_ "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector/xmpp"
2020-02-16 18:30:49 +00:00
)
type Account struct {
2020-02-17 18:02:26 +00:00
MatrixUser string
2020-02-16 18:30:49 +00:00
AccountName string
2020-02-17 18:02:26 +00:00
Protocol string
Config map[string]string
2020-02-26 20:36:35 +00:00
Conn Connector
JoinedRooms map[RoomID]bool
}
2020-02-26 15:30:10 +00:00
var accountsLock sync.Mutex
var registeredAccounts = map[string]map[string]*Account{}
2020-02-26 20:36:35 +00:00
func SetAccount(mxid string, name string, protocol string, config map[string]string) error {
accountsLock.Lock()
defer accountsLock.Unlock()
if _, ok := registeredAccounts[mxid]; !ok {
registeredAccounts[mxid] = make(map[string]*Account)
}
accounts := registeredAccounts[mxid]
// Check we can create connector
proto, ok := Protocols[protocol]
if !ok {
return fmt.Errorf("Invalid protocol: %s", protocol)
}
conn := proto.NewConnector()
if conn == nil {
return fmt.Errorf("Could not create connector for protocol %s", protocol)
}
// If the account existed already, close and drop connector
2020-02-26 20:36:35 +00:00
if prev_acct, ok := accounts[name]; ok {
if prev_acct.Protocol == protocol && reflect.DeepEqual(config, prev_acct.Config) {
return nil
2020-02-26 20:36:35 +00:00
}
go prev_acct.Conn.Close()
delete(accounts, name)
2020-02-26 20:36:35 +00:00
}
// Configure and connect
account := &Account{
MatrixUser: mxid,
AccountName: name,
Protocol: protocol,
Config: config,
Conn: conn,
JoinedRooms: map[RoomID]bool{},
}
conn.SetHandler(account)
accounts[name] = account
go account.connect()
2020-02-26 20:36:35 +00:00
return nil
}
func ListAccounts(mxUser string) []*Account {
2020-02-26 15:30:10 +00:00
accountsLock.Lock()
defer accountsLock.Unlock()
2020-02-26 20:36:35 +00:00
ret := []*Account{}
if accts, ok := registeredAccounts[mxUser]; ok {
for _, acct := range accts {
ret = append(ret, acct)
}
}
2020-02-26 20:36:35 +00:00
return ret
}
func FindAccount(mxUser string, name string) *Account {
2020-02-26 15:30:10 +00:00
accountsLock.Lock()
defer accountsLock.Unlock()
if u, ok := registeredAccounts[mxUser]; ok {
if a, ok := u[name]; ok {
return a
}
}
return nil
}
func FindJoinedAccount(mxUser string, protocol string, room RoomID) *Account {
2020-02-26 15:30:10 +00:00
accountsLock.Lock()
defer accountsLock.Unlock()
if u, ok := registeredAccounts[mxUser]; ok {
for _, acct := range u {
if acct.Protocol == protocol {
if j, ok := acct.JoinedRooms[room]; ok && j {
return acct
}
}
}
}
return nil
}
func RemoveAccount(mxUser string, name string) {
2020-02-26 15:30:10 +00:00
accountsLock.Lock()
defer accountsLock.Unlock()
if u, ok := registeredAccounts[mxUser]; ok {
2020-02-26 22:08:25 +00:00
if acct, ok := u[name]; ok {
acct.Conn.Close()
}
delete(u, name)
}
2020-02-16 18:30:49 +00:00
}
func CloseAllAccountsForShutdown() {
accountsLock.Lock()
2020-03-02 20:51:13 +00:00
defer accountsLock.Unlock()
for _, accl := range registeredAccounts {
for _, acct := range accl {
2020-03-04 17:16:26 +00:00
log.Printf("Closing %s %s (%s)", acct.MatrixUser, acct.AccountName, acct.Protocol)
acct.Conn.Close()
}
}
}
// ----
2020-02-26 20:36:35 +00:00
func SaveDbAccounts(mxid string, key *[32]byte) {
accountsLock.Lock()
defer accountsLock.Unlock()
if accounts, ok := registeredAccounts[mxid]; ok {
for name, acct := range accounts {
var entry DbAccountConfig
db.Where(&DbAccountConfig{
MxUserID: mxid,
Name: name,
}).Assign(&DbAccountConfig{
Protocol: acct.Protocol,
Config: encryptAccountConfig(acct.Config, key),
}).FirstOrCreate(&entry)
}
}
}
func LoadDbAccounts(mxid string, key *[32]byte) {
var allAccounts []DbAccountConfig
db.Where(&DbAccountConfig{MxUserID: mxid}).Find(&allAccounts)
for _, acct := range allAccounts {
config, err := decryptAccountConfig(acct.Config, key)
if err != nil {
2020-02-28 17:29:10 +00:00
ezbrSystemSendf("Could not decrypt stored configuration for account %s (%s)", acct.Name, acct.Protocol)
continue
}
err = SetAccount(mxid, acct.Name, acct.Protocol, config)
if err != nil {
2020-02-28 21:08:18 +00:00
ezbrSystemSendf(mxid, "Could not setup account %s: %s", acct.Name, err.Error())
}
}
}
2020-02-26 15:30:10 +00:00
// ----
func (a *Account) ezbrMessagef(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
msg = fmt.Sprintf("%s %s: %s", a.Protocol, a.AccountName, msg)
ezbrSystemSend(a.MatrixUser, msg)
}
2020-02-26 20:36:35 +00:00
func (a *Account) connect() {
log.Printf("Connecting %s %s (%s)", a.MatrixUser, a.AccountName, a.Protocol)
2020-02-26 15:30:10 +00:00
ezbrSystemSendf(a.MatrixUser, "Connecting to account %s (%s)", a.AccountName, a.Protocol)
2020-02-26 20:36:35 +00:00
err := a.Conn.Configure(a.Config)
2020-02-26 15:30:10 +00:00
if err != nil {
ezbrSystemSendf(a.MatrixUser, "%s (%s) cannot connect: %s", a.AccountName, a.Protocol, err.Error())
return
}
var autojoin []DbJoinedRoom
db.Where(&DbJoinedRoom{
MxUserID: a.MatrixUser,
Protocol: a.Protocol,
AccountName: a.AccountName,
}).Find(&autojoin)
for _, aj := range autojoin {
err := a.Conn.Join(aj.RoomID)
if err != nil {
ezbrSystemSendf(a.MatrixUser, "%s (%s) cannot join %s: %s", a.AccountName, a.Protocol, aj.RoomID, err.Error())
}
}
}
2020-02-26 15:32:58 +00:00
func (a *Account) addAutojoin(roomId RoomID) {
var entry DbJoinedRoom
db.Where(&DbJoinedRoom{
MxUserID: a.MatrixUser,
Protocol: a.Protocol,
AccountName: a.AccountName,
RoomID: roomId,
}).FirstOrCreate(&entry)
}
func (a *Account) delAutojoin(roomId RoomID) {
db.Where(&DbJoinedRoom{
MxUserID: a.MatrixUser,
Protocol: a.Protocol,
AccountName: a.AccountName,
RoomID: roomId,
}).Delete(&DbJoinedRoom{})
}
2020-02-17 18:02:00 +00:00
// ---- Begin event handlers ----
func (a *Account) SystemMessage(msg string) {
a.ezbrMessagef("%s", msg)
}
func (a *Account) SaveConfig(config Configuration) {
a.Config = config
if key, ok := userKeys[a.MatrixUser]; ok {
var entry DbAccountConfig
db.Where(&DbAccountConfig{
MxUserID: a.MatrixUser,
Name: a.AccountName,
}).Assign(&DbAccountConfig{
Protocol: a.Protocol,
Config: encryptAccountConfig(a.Config, key),
}).FirstOrCreate(&entry)
}
}
2020-02-16 18:30:49 +00:00
func (a *Account) Joined(roomId RoomID) {
2020-02-17 18:02:00 +00:00
err := a.joinedInternal(roomId)
if err != nil {
2020-02-21 18:50:55 +00:00
a.ezbrMessagef("Dropping Account.Joined %s: %s", roomId, err.Error())
2020-02-17 18:02:00 +00:00
}
}
func (a *Account) joinedInternal(roomId RoomID) error {
a.JoinedRooms[roomId] = true
a.addAutojoin(roomId)
2020-02-16 21:07:41 +00:00
mx_room_id, err := dbGetMxRoom(a.Protocol, roomId)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
2020-02-27 09:35:09 +00:00
log.Tracef("Joined %s (%s)\n", roomId, a.MatrixUser)
2020-02-16 21:07:41 +00:00
err = mx.RoomInvite(mx_room_id, a.MatrixUser)
2020-02-17 18:02:00 +00:00
if err != nil && strings.Contains(err.Error(), "already in the room") {
err = nil
2020-02-16 21:07:41 +00:00
}
2020-02-17 18:02:00 +00:00
return err
2020-02-16 18:30:49 +00:00
}
2020-02-17 18:02:00 +00:00
// ----
2020-02-16 18:30:49 +00:00
func (a *Account) Left(roomId RoomID) {
2020-02-17 18:02:00 +00:00
err := a.leftInternal(roomId)
if err != nil {
2020-02-21 18:50:55 +00:00
a.ezbrMessagef("Dropping Account.Left %s: %s", roomId, err.Error())
2020-02-17 18:02:00 +00:00
}
}
func (a *Account) leftInternal(roomId RoomID) error {
delete(a.JoinedRooms, roomId)
a.delAutojoin(roomId)
2020-02-16 21:07:41 +00:00
mx_room_id, err := dbGetMxRoom(a.Protocol, roomId)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
2020-02-27 09:35:09 +00:00
log.Tracef("Left %s (%s)\n", roomId, a.MatrixUser)
2020-02-16 21:07:41 +00:00
err = mx.RoomKick(mx_room_id, a.MatrixUser, fmt.Sprintf("got leave room event on %s", a.Protocol))
if err != nil && strings.Contains(err.Error(), "not in the room") {
err = nil
}
return err
2020-02-17 18:02:00 +00:00
}
// ----
func (a *Account) UserInfoUpdated(user UserID, info *UserInfo) {
err := a.userInfoUpdatedInternal(user, info)
2020-02-16 21:07:41 +00:00
if err != nil {
2020-02-21 18:50:55 +00:00
a.ezbrMessagef("Dropping Account.UserInfoUpdated %s: %s", user, err.Error())
2020-02-16 21:07:41 +00:00
}
2020-02-16 18:30:49 +00:00
}
2020-02-17 18:02:00 +00:00
func (a *Account) userInfoUpdatedInternal(user UserID, info *UserInfo) error {
mx_user_id, err := dbGetMxUser(a.Protocol, user)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
}
if info.DisplayName != "" {
err2 := mx.ProfileDisplayname(mx_user_id, fmt.Sprintf("%s (%s)", info.DisplayName, a.Protocol))
2020-02-17 18:02:00 +00:00
if err2 != nil {
err = err2
}
}
2020-02-17 18:02:00 +00:00
if info.Avatar.MediaObject != nil {
cache_key := fmt.Sprintf("%s/user_avatar/%s", a.Protocol, user)
cache_val := info.Avatar.Filename()
if cache_val == "" || dbKvGet(cache_key) != cache_val {
err2 := mx.ProfileAvatar(mx_user_id, info.Avatar)
if err2 == nil {
dbKvPut(cache_key, cache_val)
} else {
err = err2
}
}
}
2020-02-17 18:02:00 +00:00
return err
2020-02-16 18:30:49 +00:00
}
2020-02-17 18:02:00 +00:00
// ----
func (a *Account) RoomInfoUpdated(roomId RoomID, author UserID, info *RoomInfo) {
2020-02-17 18:02:00 +00:00
err := a.roomInfoUpdatedInternal(roomId, author, info)
if err != nil {
2020-02-21 18:50:55 +00:00
a.ezbrMessagef("Dropping Account.RoomInfoUpdated %s: %s", roomId, err.Error())
2020-02-17 18:02:00 +00:00
}
}
func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *RoomInfo) error {
mx_room_id, err := dbGetMxRoom(a.Protocol, roomId)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
}
as_mxid := ezbrMxId()
2020-02-26 16:03:49 +00:00
if author == a.Conn.User() {
as_mxid = a.MatrixUser
} else if len(author) > 0 {
2020-02-17 18:02:00 +00:00
mx_user_id, err2 := dbGetMxUser(a.Protocol, author)
if err2 == nil {
as_mxid = mx_user_id
}
}
if info.Topic != "" {
err2 := mx.RoomTopicAs(mx_room_id, info.Topic, as_mxid)
2020-02-17 18:02:00 +00:00
if err2 != nil {
err = err2
}
}
if info.Name != "" {
name := fmt.Sprintf("%s (%s)", info.Name, a.Protocol)
err2 := mx.RoomNameAs(mx_room_id, name, as_mxid)
2020-02-17 18:02:00 +00:00
if err2 != nil {
err = err2
}
}
if info.Picture.MediaObject != nil {
cache_key := fmt.Sprintf("%s/room_picture/%s", a.Protocol, roomId)
cache_val := info.Picture.Filename()
if cache_val == "" || dbKvGet(cache_key) != cache_val {
err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid)
if err2 == nil {
dbKvPut(cache_key, cache_val)
} else {
err = err2
}
}
}
2020-02-17 18:02:00 +00:00
return err
2020-02-16 18:30:49 +00:00
}
2020-02-17 18:02:00 +00:00
// ----
2020-02-16 18:30:49 +00:00
func (a *Account) Event(event *Event) {
2020-02-17 18:02:00 +00:00
err := a.eventInternal(event)
if err != nil {
2020-02-21 18:50:55 +00:00
a.ezbrMessagef("Dropping Account.Event %s %s %s: %s", event.Author, event.Recipient, event.Room, err.Error())
2020-02-17 18:02:00 +00:00
}
}
func (a *Account) eventInternal(event *Event) error {
// TODO: automatically ignore events that come from one of our bridged matrix users
// TODO: deduplicate events if we have several matrix users joined the same room (hard problem)
2020-02-16 21:07:41 +00:00
mx_user_id, err := dbGetMxUser(a.Protocol, event.Author)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
if event.Type == EVENT_JOIN {
2020-02-27 09:35:09 +00:00
log.Tracef("%s join %s %s", a.Protocol, event.Author, event.Room)
2020-02-16 21:07:41 +00:00
mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
err = mx.RoomInvite(mx_room_id, mx_user_id)
2020-02-16 21:07:41 +00:00
if err != nil {
2020-02-17 18:02:00 +00:00
if strings.Contains(err.Error(), "already in the room") {
2020-02-21 18:28:00 +00:00
return nil
2020-02-17 18:02:00 +00:00
}
return err
2020-02-16 21:07:41 +00:00
}
return mx.RoomJoinAs(mx_room_id, mx_user_id)
2020-02-16 21:07:41 +00:00
} else if event.Type == EVENT_LEAVE {
2020-02-27 09:35:09 +00:00
log.Tracef("%s join %s %s", a.Protocol, event.Author, event.Room)
2020-02-16 21:07:41 +00:00
mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room)
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
return mx.RoomLeaveAs(mx_room_id, mx_user_id)
2020-02-16 21:57:30 +00:00
} else {
2020-02-27 09:35:09 +00:00
log.Tracef("%s msg %s %s", a.Protocol, event.Author, event.Room)
2020-02-16 21:57:30 +00:00
mx_room_id := ""
2020-02-16 21:07:41 +00:00
if len(event.Room) > 0 {
2020-02-16 21:57:30 +00:00
mx_room_id, err = dbGetMxRoom(a.Protocol, event.Room)
2020-02-16 21:07:41 +00:00
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
2020-02-16 21:57:30 +00:00
} else {
mx_room_id, err = dbGetMxPmRoom(a.Protocol, event.Author, mx_user_id, a.MatrixUser, a.AccountName)
2020-02-16 21:07:41 +00:00
if err != nil {
2020-02-17 18:02:00 +00:00
return err
2020-02-16 21:07:41 +00:00
}
2020-02-16 21:57:30 +00:00
}
var cache_key string
2020-02-21 18:28:00 +00:00
if event.Id != "" {
2020-03-13 10:29:03 +00:00
// Use mx_room_id as a lock slot key, because this section is
// concurrent with the part that sends events out in server.go,
// and at that point we don't know the event's ID yet
// since it will be attributed by the backend during the call to send
dbLockSlot(mx_room_id)
defer dbUnlockSlot(mx_room_id)
// If the event has an ID, make sure it is processed only once
cache_key = fmt.Sprintf("%s/event_seen/%s/%s",
2020-02-21 18:28:00 +00:00
a.Protocol, mx_room_id, event.Id)
if dbKvGet(cache_key) == "yes" {
2020-02-21 18:28:00 +00:00
// false: cache key was not modified, meaning we
// already saw the event
return nil
}
}
2020-02-16 21:57:30 +00:00
typ := "m.text"
if event.Type == EVENT_ACTION {
typ = "m.emote"
}
2020-02-21 14:57:53 +00:00
err = mx.SendMessageAs(mx_room_id, typ, event.Text, mx_user_id)
if err != nil {
return err
}
if event.Attachments != nil {
for _, file := range event.Attachments {
2020-02-21 14:57:53 +00:00
mxfile, err := mx.UploadMedia(file)
if err != nil {
return err
}
2020-02-23 19:24:50 +00:00
content := map[string]interface{}{
"body": mxfile.Filename(),
2020-02-21 14:57:53 +00:00
"filename": mxfile.Filename(),
2020-02-23 19:24:50 +00:00
"url": fmt.Sprintf("mxc://%s/%s", mxfile.MxcServer, mxfile.MxcMediaId),
2020-02-21 14:57:53 +00:00
}
if sz := mxfile.ImageSize(); sz != nil {
content["msgtype"] = "m.image"
2020-02-23 19:24:50 +00:00
content["info"] = map[string]interface{}{
2020-02-21 14:57:53 +00:00
"mimetype": mxfile.Mimetype(),
2020-02-23 19:24:50 +00:00
"size": mxfile.Size(),
"w": sz.Width,
"h": sz.Height,
2020-02-21 14:57:53 +00:00
}
} else {
content["msgtype"] = "m.file"
2020-02-23 19:24:50 +00:00
content["info"] = map[string]interface{}{
2020-02-21 14:57:53 +00:00
"mimetype": mxfile.Mimetype(),
2020-02-23 19:24:50 +00:00
"size": mxfile.Size(),
2020-02-21 14:57:53 +00:00
}
}
err = mx.SendAs(mx_room_id, "m.room.message", content, mx_user_id)
if err != nil {
return err
}
}
}
// Mark message as received in db
if cache_key != "" {
dbKvPutLocked(cache_key, "yes")
}
2020-02-21 14:57:53 +00:00
return nil
2020-02-16 21:07:41 +00:00
}
2020-02-16 18:30:49 +00:00
}
// ----
func (a *Account) CacheGet(key string) string {
cache_key := fmt.Sprintf("%s/account/%s/%s/%s",
a.Protocol, a.MatrixUser, a.AccountName, key)
2020-02-28 09:34:22 +00:00
return dbKvGet(cache_key)
}
func (a *Account) CachePut(key string, value string) {
cache_key := fmt.Sprintf("%s/account/%s/%s/%s",
a.Protocol, a.MatrixUser, a.AccountName, key)
2020-02-28 09:34:22 +00:00
dbKvPut(cache_key, value)
}