Skip to content

Commit

Permalink
Add ways to handle lost portals
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Dec 31, 2023
1 parent 4bf3bc4 commit dbcdfee
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 14 deletions.
37 changes: 37 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (br *SignalBridge) RegisterCommands() {
cmdUnsetRelay,
cmdDeletePortal,
cmdDeleteAllPortals,
cmdCleanupLostPortals,
)
}

Expand Down Expand Up @@ -474,3 +475,39 @@ func fnDeleteAllPortals(ce *WrappedCommandEvent) {
ce.Reply("Finished background cleanup of deleted portal rooms.")
}()
}

var cmdCleanupLostPortals = &commands.FullHandler{
Func: wrapCommand(fnCleanupLostPortals),
Name: "cleanup-lost-portals",
Help: commands.HelpMeta{
Section: HelpSectionPortalManagement,
Description: "Clean up portals that were discarded due to the receiver not being logged into the bridge",
},
RequiresAdmin: true,
}

func fnCleanupLostPortals(ce *WrappedCommandEvent) {
portals, err := ce.Bridge.DB.LostPortal.GetAll(context.TODO())
if err != nil {
ce.Reply("Failed to get portals: %v", err)
return
} else if len(portals) == 0 {
ce.Reply("No lost portals found")
return
}

ce.Reply("Found %d lost portals, deleting...", len(portals))
for _, portal := range portals {
dmUUID, err := uuid.Parse(portal.ChatID)
intent := ce.Bot
if err == nil {
intent = ce.Bridge.GetPuppetBySignalID(dmUUID).DefaultIntent()
}
ce.Bridge.CleanupRoom(ce.ZLog, intent, portal.MXID, false)
err = portal.Delete(context.TODO())
if err != nil {
ce.ZLog.Err(err).Msg("Failed to delete lost portal from database after cleanup")
}
}
ce.Reply("Finished cleaning up portals")
}
2 changes: 2 additions & 0 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Database struct {

User *UserQuery
Portal *PortalQuery
LostPortal *LostPortalQuery
Puppet *PuppetQuery
Message *MessageQuery
Reaction *ReactionQuery
Expand All @@ -44,6 +45,7 @@ func New(db *dbutil.Database) *Database {
Database: db,
User: &UserQuery{dbutil.MakeQueryHelper(db, newUser)},
Portal: &PortalQuery{dbutil.MakeQueryHelper(db, newPortal)},
LostPortal: &LostPortalQuery{dbutil.MakeQueryHelper(db, newLostPortal)},
Puppet: &PuppetQuery{dbutil.MakeQueryHelper(db, newPuppet)},
Message: &MessageQuery{dbutil.MakeQueryHelper(db, newMessage)},
Reaction: &ReactionQuery{dbutil.MakeQueryHelper(db, newReaction)},
Expand Down
58 changes: 58 additions & 0 deletions database/lostportal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// mautrix-signal - A Matrix-signal puppeting bridge.
// Copyright (C) 2023 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package database

import (
"context"

"go.mau.fi/util/dbutil"
"maunium.net/go/mautrix/id"
)

const (
getLostPortalsQuery = `SELECT chat_id, receiver, mxid FROM lost_portals`
deleteLostPortalQuery = `DELETE FROM lost_portals WHERE mxid=$1`
)

type LostPortalQuery struct {
*dbutil.QueryHelper[*LostPortal]
}

func (lpq *LostPortalQuery) GetAll(ctx context.Context) ([]*LostPortal, error) {
return lpq.QueryMany(ctx, getLostPortalsQuery)
}

type LostPortal struct {
qh *dbutil.QueryHelper[*LostPortal]

ChatID string
Receiver string
MXID id.RoomID
}

func newLostPortal(qh *dbutil.QueryHelper[*LostPortal]) *LostPortal {
return &LostPortal{qh: qh}
}

func (l *LostPortal) Scan(row dbutil.Scannable) (*LostPortal, error) {
err := row.Scan(&l.ChatID, &l.Receiver, &l.MXID)
return l, err
}

func (l *LostPortal) Delete(ctx context.Context) error {
return l.qh.Exec(ctx, deleteLostPortalQuery, l.MXID)
}
20 changes: 20 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,27 @@ func (br *SignalBridge) Init() {
signalmeow.HackyCaptionToggle = br.Config.Bridge.CaptionInMessage
}

func (br *SignalBridge) logLostPortals(ctx context.Context) {
lostPortals, err := br.DB.LostPortal.GetAll(ctx)
if err != nil {
br.ZLog.Err(err).Msg("Failed to get lost portals")
return
} else if len(lostPortals) == 0 {
return
}
lostCountByReceiver := make(map[string]int)
for _, lost := range lostPortals {
lostCountByReceiver[lost.Receiver]++
}
br.ZLog.Warn().
Any("count_by_receiver", lostCountByReceiver).
Msg("Some portals were discarded due to the receiver not being logged into the bridge anymore. " +
"Use `!signal cleanup-lost-portals` to remove them from the database. " +
"Alternatively, you can re-insert the data into the portal table with the appropriate receiver column to restore the portals.")
}

func (br *SignalBridge) Start() {
go br.logLostPortals(context.TODO())
err := br.MeowStore.Upgrade()
if err != nil {
br.Log.Fatalln("Failed to upgrade signalmeow database: %v", err)
Expand Down
31 changes: 17 additions & 14 deletions portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1914,41 +1914,44 @@ func (portal *Portal) Delete() {
}

func (portal *Portal) Cleanup(puppetsOnly bool) {
if len(portal.MXID) == 0 {
portal.bridge.CleanupRoom(&portal.log, portal.MainIntent(), portal.MXID, puppetsOnly)
}

func (br *SignalBridge) CleanupRoom(log *zerolog.Logger, intent *appservice.IntentAPI, mxid id.RoomID, puppetsOnly bool) {
if len(mxid) == 0 {
return
}
intent := portal.MainIntent()
if portal.bridge.SpecVersions.Supports(mautrix.BeeperFeatureRoomYeeting) {
err := intent.BeeperDeleteRoom(portal.MXID)
if br.SpecVersions.Supports(mautrix.BeeperFeatureRoomYeeting) {
err := intent.BeeperDeleteRoom(mxid)
if err == nil || errors.Is(err, mautrix.MNotFound) {
return
}
portal.log.Warn().Err(err).Msg("Failed to delete room using beeper yeet endpoint, falling back to normal behavior")
log.Warn().Err(err).Msg("Failed to delete room using beeper yeet endpoint, falling back to normal behavior")
}
members, err := intent.JoinedMembers(portal.MXID)
members, err := intent.JoinedMembers(mxid)
if err != nil {
portal.log.Err(err).Msg("Failed to get portal members for cleanup")
log.Err(err).Msg("Failed to get portal members for cleanup")
return
}
for member := range members.Joined {
if member == intent.UserID {
continue
}
puppet := portal.bridge.GetPuppetByMXID(member)
puppet := br.GetPuppetByMXID(member)
if puppet != nil {
_, err = puppet.DefaultIntent().LeaveRoom(portal.MXID)
_, err = puppet.DefaultIntent().LeaveRoom(mxid)
if err != nil {
portal.log.Err(err).Msg("Failed to leave as puppet while cleaning up portal")
log.Err(err).Msg("Failed to leave as puppet while cleaning up portal")
}
} else if !puppetsOnly {
_, err = intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
_, err = intent.KickUser(mxid, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
if err != nil {
portal.log.Err(err).Msg("Failed to kick user while cleaning up portal")
log.Err(err).Msg("Failed to kick user while cleaning up portal")
}
}
}
_, err = intent.LeaveRoom(portal.MXID)
_, err = intent.LeaveRoom(mxid)
if err != nil {
portal.log.Err(err).Msg("Failed to leave room while cleaning up portal")
log.Err(err).Msg("Failed to leave room while cleaning up portal")
}
}

0 comments on commit dbcdfee

Please sign in to comment.