diff --git a/database/database.go b/database/database.go index cb1a1db..26da8da 100644 --- a/database/database.go +++ b/database/database.go @@ -57,7 +57,7 @@ func New(dbType string, uri string, baseLog log.Logger) (*Database, error) { } gdb, err := gorm.Open(conn, &gorm.Config{ - // Logger: logger.Default.LogMode(logger.Info), + //Logger: logger.Default.LogMode(logger.Info), // Logger: baseLog, DisableForeignKeyConstraintWhenMigrating: true, @@ -113,7 +113,7 @@ func (db *Database) Init() error { return err } - err = db.AutoMigrate(&mxRegistered{}, &mxUserProfile{}) + err = db.AutoMigrate(&mxRegistered{}, &MxUserProfile{}) if err != nil { return err } diff --git a/database/puppet.go b/database/puppet.go index 5ff4fae..7994b1b 100644 --- a/database/puppet.go +++ b/database/puppet.go @@ -45,8 +45,7 @@ func (pq *PuppetQuery) GetAll() (puppets []*Puppet) { return nil } for _, puppet := range puppets { - puppet.db = pq.db - puppet.log = pq.log + pq.initializePuppet(puppet) } // defer rows.Close() // for rows.Next() { @@ -61,8 +60,7 @@ func (pq *PuppetQuery) Get(jid types.GroupMeID) *Puppet { if ans.Error != nil || ans.RowsAffected == 0 { return nil } - puppet.db = pq.db - puppet.log = pq.log + pq.initializePuppet(&puppet) return &puppet } @@ -72,8 +70,7 @@ func (pq *PuppetQuery) GetByCustomMXID(mxid id.UserID) *Puppet { if ans.Error != nil || ans.RowsAffected == 0 { return nil } - puppet.db = pq.db - puppet.log = pq.log + pq.initializePuppet(&puppet) return &puppet } @@ -84,8 +81,7 @@ func (pq *PuppetQuery) GetAllWithCustomMXID() (puppets []*Puppet) { return nil } for _, puppet := range puppets { - puppet.db = pq.db - puppet.log = pq.log + pq.initializePuppet(puppet) } // defer rows.Close() // for rows.Next() { @@ -94,16 +90,21 @@ func (pq *PuppetQuery) GetAllWithCustomMXID() (puppets []*Puppet) { return } +func (pq *PuppetQuery) initializePuppet(p *Puppet) { + p.db = pq.db + p.log = pq.log +} + //Puppet is comment type Puppet struct { db *Database log log.Logger - JID types.GroupMeID `gorm:"primaryKey"` - Avatar string - AvatarURL types.ContentURI - Displayname string - NameQuality int8 + JID types.GroupMeID `gorm:"primaryKey"` + //Avatar string + //AvatarURL types.ContentURI + //Displayname string + //NameQuality int8 CustomMXID id.UserID `gorm:"column:custom_mxid;"` AccessToken string diff --git a/database/statestore.go b/database/statestore.go index dd73fe4..833915a 100644 --- a/database/statestore.go +++ b/database/statestore.go @@ -76,18 +76,19 @@ func (store *SQLStateStore) MarkRegistered(userID id.UserID) { } } -type mxUserProfile struct { +type MxUserProfile struct { RoomID string `gorm:"primaryKey"` UserID string `gorm:"primaryKey"` Membership string `gorm:"notNull"` DisplayName string AvatarURL string + Avatar string } func (store *SQLStateStore) GetRoomMembers(roomID id.RoomID) map[id.UserID]*event.MemberEventContent { members := make(map[id.UserID]*event.MemberEventContent) - var users []mxUserProfile + var users []MxUserProfile ans := store.db.Where("room_id = ?", roomID.String()).Find(&users) if ans.Error != nil { return members @@ -113,7 +114,7 @@ func (store *SQLStateStore) GetRoomMembers(roomID id.RoomID) map[id.UserID]*even } func (store *SQLStateStore) GetMembership(roomID id.RoomID, userID id.UserID) event.Membership { - var user mxUserProfile + var user MxUserProfile ans := store.db.Where("room_id = ? AND user_id = ?", roomID, userID).Limit(1).Find(&user) membership := event.MembershipLeave if ans.Error != nil && ans.Error != gorm.ErrRecordNotFound { @@ -133,7 +134,7 @@ func (store *SQLStateStore) GetMember(roomID id.RoomID, userID id.UserID) *event } func (store *SQLStateStore) TryGetMember(roomID id.RoomID, userID id.UserID) (*event.MemberEventContent, bool) { - var user mxUserProfile + var user MxUserProfile ans := store.db.Where("room_id = ? AND user_id = ?", roomID, userID).Take(&user) if ans.Error != nil && ans.Error != gorm.ErrRecordNotFound { @@ -148,6 +149,32 @@ func (store *SQLStateStore) TryGetMember(roomID id.RoomID, userID id.UserID) (*e return &eventMember, ans.Error != nil } +func (store *SQLStateStore) TryGetMemberRaw(roomID id.RoomID, userID id.UserID) (user MxUserProfile, err bool) { + ans := store.db.Where("room_id = ? AND user_id = ?", roomID, userID).Take(&user) + + if ans.Error == gorm.ErrRecordNotFound { + err = true + return + } + if ans.Error != nil && ans.Error != gorm.ErrRecordNotFound { + store.log.Warnfln("Failed to scan member info of %s in %s: %v", userID, roomID, ans.Error) + err = true + return + } + + return user, false +} + +func (store *SQLStateStore) SetMemberRaw(member *MxUserProfile) { + ans := store.db.Clauses(clause.OnConflict{ + UpdateAll: true, + }).Create(member) + + if ans.Error != nil { + store.log.Warnfln("Failed to set membership of %s in %s to %s: %v", member.UserID, member.RoomID, member, ans.Error) + } +} + func (store *SQLStateStore) FindSharedRooms(userID id.UserID) (rooms []id.RoomID) { rows, err := store.db.Table("mx_user_profile").Select("room_id"). @@ -192,7 +219,7 @@ func (store *SQLStateStore) IsMembership(roomID id.RoomID, userID id.UserID, all func (store *SQLStateStore) SetMembership(roomID id.RoomID, userID id.UserID, membership event.Membership) { var err error - user := mxUserProfile{ + user := MxUserProfile{ RoomID: roomID.String(), UserID: userID.String(), Membership: string(membership), @@ -208,19 +235,18 @@ func (store *SQLStateStore) SetMembership(roomID id.RoomID, userID id.UserID, me store.log.Warnfln("Failed to set membership of %s in %s to %s: %v", userID, roomID, membership, err) } } - func (store *SQLStateStore) SetMember(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent) { - user := mxUserProfile{ + user := MxUserProfile{ RoomID: roomID.String(), UserID: userID.String(), Membership: string(member.Membership), DisplayName: member.Displayname, - AvatarURL: string(member.AvatarURL), + // AvatarURL: string(member.AvatarURL),//try ignoring } ans := store.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "room_id"}, {Name: "user_id"}}, - DoUpdates: clause.AssignmentColumns([]string{"membership"}), + DoUpdates: clause.AssignmentColumns([]string{"membership", "display_name"}), }).Create(&user) if ans.Error != nil { diff --git a/formatting.go b/formatting.go index 3d1d544..03b21d1 100644 --- a/formatting.go +++ b/formatting.go @@ -18,16 +18,13 @@ package main import ( "fmt" - "html" "regexp" "strings" - "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" "github.com/karmanyaahm/matrix-groupme-go/types" - whatsappExt "github.com/karmanyaahm/matrix-groupme-go/whatsapp-ext" ) var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)") @@ -104,40 +101,40 @@ func NewFormatter(bridge *Bridge) *Formatter { return formatter } -func (formatter *Formatter) getMatrixInfoByJID(jid types.GroupMeID) (mxid id.UserID, displayname string) { - if user := formatter.bridge.GetUserByJID(jid); user != nil { - mxid = user.MXID - displayname = string(user.MXID) - } else if puppet := formatter.bridge.GetPuppetByJID(jid); puppet != nil { - mxid = puppet.MXID - displayname = puppet.Displayname - } - return -} +//func (formatter *Formatter) getMatrixInfoByJID(jid types.GroupMeID) (mxid id.UserID, displayname string) { +// if user := formatter.bridge.GetUserByJID(jid); user != nil { +// mxid = user.MXID +// displayname = string(user.MXID) +// } else if puppet := formatter.bridge.GetPuppetByJID(jid); puppet != nil { +// mxid = puppet.MXID +// displayname = puppet.Displayname +// } +// return +//} -func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []types.GroupMeID) { - output := html.EscapeString(content.Body) - for regex, replacement := range formatter.waReplString { - output = regex.ReplaceAllString(output, replacement) - } - for regex, replacer := range formatter.waReplFunc { - output = regex.ReplaceAllStringFunc(output, replacer) - } - for _, jid := range mentionedJIDs { - mxid, displayname := formatter.getMatrixInfoByJID(jid) - number := "@" + strings.Replace(jid, whatsappExt.NewUserSuffix, "", 1) - output = strings.Replace(output, number, fmt.Sprintf(`%s`, mxid, displayname), -1) - content.Body = strings.Replace(content.Body, number, displayname, -1) - } - if output != content.Body { - output = strings.Replace(output, "\n", "
", -1) - content.FormattedBody = output - content.Format = event.FormatHTML - for regex, replacer := range formatter.waReplFuncText { - content.Body = regex.ReplaceAllStringFunc(content.Body, replacer) - } - } -} +//func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []types.GroupMeID) { +// output := html.EscapeString(content.Body) +// for regex, replacement := range formatter.waReplString { +// output = regex.ReplaceAllString(output, replacement) +// } +// for regex, replacer := range formatter.waReplFunc { +// output = regex.ReplaceAllStringFunc(output, replacer) +// } +// for _, jid := range mentionedJIDs { +// mxid, displayname := formatter.getMatrixInfoByJID(jid) +// number := "@" + strings.Replace(jid, whatsappExt.NewUserSuffix, "", 1) +// output = strings.Replace(output, number, fmt.Sprintf(`%s`, mxid, displayname), -1) +// content.Body = strings.Replace(content.Body, number, displayname, -1) +// } +// if output != content.Body { +// output = strings.Replace(output, "\n", "
", -1) +// content.FormattedBody = output +// content.Format = event.FormatHTML +// for regex, replacer := range formatter.waReplFuncText { +// content.Body = regex.ReplaceAllStringFunc(content.Body, replacer) +// } +// } +//} func (formatter *Formatter) ParseMatrix(html string) (string, []types.GroupMeID) { ctx := make(format.Context) diff --git a/go.mod b/go.mod index 1b5961f..f72403c 100644 --- a/go.mod +++ b/go.mod @@ -31,3 +31,5 @@ require ( replace github.com/karmanyaahm/groupme => ../groupme replace github.com/karmanyaahm/wray => ../wray + +replace maunium.net/go/mautrix => ../mautrix diff --git a/matrix.go b/matrix.go index a4900e9..5526291 100644 --- a/matrix.go +++ b/matrix.go @@ -31,6 +31,7 @@ import ( "maunium.net/go/mautrix/id" "github.com/karmanyaahm/matrix-groupme-go/database" + "github.com/karmanyaahm/matrix-groupme-go/types" ) type MatrixHandler struct { @@ -166,10 +167,11 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter portal.Topic = "WhatsApp private chat" _, _ = portal.MainIntent().SetRoomTopic(portal.MXID, portal.Topic) if portal.bridge.Config.Bridge.PrivateChatPortalMeta { - portal.Name = puppet.Displayname - portal.AvatarURL = puppet.AvatarURL + m, _ := mx.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) + portal.Name = m.DisplayName + portal.AvatarURL = types.ContentURI{id.MustParseContentURI(m.AvatarURL)} print("possible bug with pointer above") - portal.Avatar = puppet.Avatar + portal.Avatar = m.Avatar _, _ = portal.MainIntent().SetRoomName(portal.MXID, portal.Name) _, _ = portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL.ContentURI) } else { diff --git a/portal.go b/portal.go index 9ae656c..32e7b6b 100644 --- a/portal.go +++ b/portal.go @@ -348,7 +348,7 @@ func (portal *Portal) SyncParticipants(metadata *groupme.Group) { if user != nil { changed = levels.EnsureUserLevel(user.MXID, expectedLevel) || changed } - go puppet.Sync(nil, *participant) //why nil whynot + puppet.Sync(nil, portal.MXID, *participant) //why nil whynot } if changed { _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels) @@ -940,10 +940,11 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { var metadata *groupme.Group if portal.IsPrivateChat() { puppet := portal.bridge.GetPuppetByJID(portal.Key.JID) + m, _ := portal.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) if portal.bridge.Config.Bridge.PrivateChatPortalMeta { - portal.Name = puppet.Displayname - portal.AvatarURL = puppet.AvatarURL - portal.Avatar = puppet.Avatar + portal.Name = m.DisplayName + portal.AvatarURL = types.ContentURI{id.MustParseContentURI(m.AvatarURL)} + portal.Avatar = m.Avatar } else { portal.Name = "" } diff --git a/puppet.go b/puppet.go index 6e3cc75..489d03b 100644 --- a/puppet.go +++ b/puppet.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "os" "regexp" "strings" @@ -181,19 +182,32 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI { return puppet.bridge.AS.Intent(puppet.MXID) } -func (puppet *Puppet) UpdateAvatar(source *User, avatar string) bool { +//func (puppet *Puppet) SetRoomMetadata(name, avatarURL string) bool { +// +//} + +func (puppet *Puppet) UpdateAvatar(source *User, portalMXID id.RoomID, avatar string) bool { + memberRaw, _ := puppet.bridge.StateStore.TryGetMemberRaw(portalMXID, puppet.MXID) //TODO Handle + fmt.Println(len(avatar), avatar, memberRaw.Avatar) + if len(avatar) == 0 { - err := puppet.DefaultIntent().SetAvatarURL(id.ContentURI{}) + var err error + err = puppet.DefaultIntent().SetRoomAvatarURL(portalMXID, id.ContentURI{}) + if err != nil { - puppet.log.Warnln("Failed to remove avatar:", err) + puppet.log.Warnln("Failed to remove avatar:", err, puppet.MXID) + os.Exit(1) } - puppet.AvatarURL = types.ContentURI{} - puppet.Avatar = avatar + memberRaw.Avatar = avatar + memberRaw.AvatarURL = "" + go puppet.updatePortalAvatar() + + puppet.bridge.StateStore.SetMemberRaw(&memberRaw) //TODO handle return true } - if puppet.Avatar == avatar { + if memberRaw.Avatar == avatar { return false // up to date } @@ -209,24 +223,32 @@ func (puppet *Puppet) UpdateAvatar(source *User, avatar string) bool { puppet.log.Warnln("Failed to upload avatar:", err) return false } - - puppet.AvatarURL = types.ContentURI{resp.ContentURI} - err = puppet.DefaultIntent().SetAvatarURL(resp.ContentURI) + err = puppet.DefaultIntent().SetRoomAvatarURL(portalMXID, resp.ContentURI) if err != nil { puppet.log.Warnln("Failed to set avatar:", err) } - puppet.Avatar = avatar + + memberRaw.AvatarURL = resp.ContentURI.String() + memberRaw.Avatar = avatar + + puppet.bridge.StateStore.SetMemberRaw(&memberRaw) //TODO handle + go puppet.updatePortalAvatar() return true } -func (puppet *Puppet) UpdateName(source *User, contact groupme.Member) bool { +func (puppet *Puppet) UpdateName(source *User, portalMXID id.RoomID, contact groupme.Member) bool { newName, quality := puppet.bridge.Config.Bridge.FormatDisplayname(contact) - if puppet.Displayname != newName && quality >= puppet.NameQuality { - err := puppet.DefaultIntent().SetDisplayName(newName) + + memberRaw, _ := puppet.bridge.StateStore.TryGetMemberRaw(portalMXID, puppet.MXID) //TODO Handle + quality = quality //quality not used + if memberRaw.DisplayName != newName { //&& quality >= puppet.NameQuality[portalMXID] { + var err error + err = puppet.DefaultIntent().SetRoomDisplayName(portalMXID, newName) + if err == nil { - puppet.Displayname = newName - puppet.NameQuality = quality + memberRaw.DisplayName = newName + // puppet.NameQuality[portalMXID] = quality go puppet.updatePortalName() puppet.Update() } else { @@ -247,38 +269,37 @@ func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) { func (puppet *Puppet) updatePortalAvatar() { puppet.updatePortalMeta(func(portal *Portal) { + + m, _ := puppet.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) if len(portal.MXID) > 0 { - _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, puppet.AvatarURL.ContentURI) + _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, id.MustParseContentURI(m.AvatarURL)) if err != nil { portal.log.Warnln("Failed to set avatar:", err) } } - portal.AvatarURL = puppet.AvatarURL - portal.Avatar = puppet.Avatar + portal.AvatarURL = types.ContentURI{id.MustParseContentURI(m.AvatarURL)} + portal.Avatar = m.Avatar portal.Update() }) } func (puppet *Puppet) updatePortalName() { puppet.updatePortalMeta(func(portal *Portal) { + m, _ := puppet.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) if len(portal.MXID) > 0 { - _, err := portal.MainIntent().SetRoomName(portal.MXID, puppet.Displayname) + _, err := portal.MainIntent().SetRoomName(portal.MXID, m.DisplayName) if err != nil { portal.log.Warnln("Failed to set name:", err) } } - portal.Name = puppet.Displayname + portal.Name = m.DisplayName portal.Update() }) } -func (puppet *Puppet) Sync(source *User, contact groupme.Member) { +func (puppet *Puppet) Sync(source *User, portalMXID id.RoomID, contact groupme.Member) { if contact.UserID.String() == "system" { puppet.log.Warnln("Trying to sync system puppet") - - // portal.Sync(puppet.bridge.GetUserByJID(portal.Key.Receiver), groupme.Group{}) - //TODO permissoins idk if its fine to use portal owner - return } @@ -287,14 +308,9 @@ func (puppet *Puppet) Sync(source *User, contact groupme.Member) { puppet.log.Errorln("Failed to ensure registered:", err) } - //if contact.ID.String() == source.JID { - //TODO What is this - // contact.Notify = source.Conn.Info.Pushname - //} - update := false - update = puppet.UpdateName(source, contact) || update - update = puppet.UpdateAvatar(source, contact.ImageURL) || update + update = puppet.UpdateName(source, portalMXID, contact) || update + update = puppet.UpdateAvatar(source, portalMXID, contact.ImageURL) || update if update { puppet.Update() } diff --git a/user.go b/user.go index 9a59cea..ab288d2 100644 --- a/user.go +++ b/user.go @@ -817,14 +817,15 @@ func (user *User) handleMessageLoop() { case msg := <-user.messageOutput: user.bridge.Metrics.TrackBufferLength(user.MXID, len(user.messageOutput)) puppet := user.bridge.GetPuppetByJID(msg.data.UserID.String()) + portal := user.bridge.GetPortalByJID(database.GroupPortalKey(msg.chat)) if puppet != nil { - puppet.Sync(user, groupme.Member{ + puppet.Sync(user, portal.MXID, groupme.Member{ UserID: msg.data.UserID, Nickname: msg.data.Name, ImageURL: msg.data.AvatarURL, }) //TODO: add params or docs? } - user.bridge.GetPortalByJID(database.GroupPortalKey(msg.chat)).messages <- msg + portal.messages <- msg case <-user.syncStart: user.log.Debugln("Processing of incoming messages is locked") user.bridge.Metrics.TrackSyncLock(user.JID, true)