From dd4ac1bf7a48f3bb0d79ff29e60c9f884a2540aa Mon Sep 17 00:00:00 2001 From: timedout Date: Mon, 19 Jan 2026 02:56:56 +0000 Subject: [PATCH 1/3] Fall back to continuwuityadmin when blocking rooms --- bot/init.go | 39 ++++++++++++++++++---------------- go.mod | 2 +- go.sum | 4 ++++ policyeval/commands.go | 48 ++++++++++++++++++++++++++++++------------ 4 files changed, 61 insertions(+), 32 deletions(-) diff --git a/bot/init.go b/bot/init.go index 326e9ab..5af9b64 100644 --- a/bot/init.go +++ b/bot/init.go @@ -11,6 +11,7 @@ import ( "go.mau.fi/util/exerrors" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" + "maunium.net/go/mautrix/continuwuityadmin" "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/crypto/cryptohelper" "maunium.net/go/mautrix/id" @@ -23,14 +24,15 @@ type Bot struct { Meta *database.Bot Log zerolog.Logger *mautrix.Client - Intent *appservice.IntentAPI - SynapseAdmin *synapseadmin.Client - ServerName string - CryptoStore *crypto.SQLCryptoStore - CryptoHelper *cryptohelper.CryptoHelper - Mach *crypto.OlmMachine - eventProcessor *appservice.EventProcessor - mainDB *database.Database + Intent *appservice.IntentAPI + SynapseAdmin *synapseadmin.Client + ContinuwuityAdmin *continuwuityadmin.Client + ServerName string + CryptoStore *crypto.SQLCryptoStore + CryptoHelper *cryptohelper.CryptoHelper + Mach *crypto.OlmMachine + eventProcessor *appservice.EventProcessor + mainDB *database.Database } func NewBot( @@ -75,16 +77,17 @@ func NewBot( } } return &Bot{ - Meta: bot, - Client: client, - Intent: intent, - Log: log, - SynapseAdmin: adminClient, - ServerName: client.UserID.Homeserver(), - CryptoStore: cryptoStore, - CryptoHelper: helper, - eventProcessor: ep, - mainDB: db, + Meta: bot, + Client: client, + Intent: intent, + Log: log, + SynapseAdmin: adminClient, + ContinuwuityAdmin: &continuwuityadmin.Client{Client: adminClient.Client}, + ServerName: client.UserID.Homeserver(), + CryptoStore: cryptoStore, + CryptoHelper: helper, + eventProcessor: ep, + mainDB: db, } } diff --git a/go.mod b/go.mod index 8113769..9ef4c30 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( golang.org/x/sync v0.19.0 gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mauflag v1.0.0 - maunium.net/go/mautrix v0.26.2 + maunium.net/go/mautrix v0.26.3-0.20260119025107-d8b3f64a2d0f ) require ( diff --git a/go.sum b/go.sum index 9a073d1..6f855f5 100644 --- a/go.sum +++ b/go.sum @@ -105,3 +105,7 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mautrix v0.26.2 h1:rLiZLQoSKCJDZ+mF1gBQS4p74h3jZXs83g8D4W6Te8g= maunium.net/go/mautrix v0.26.2/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260118190708-a39bf77fe57d h1:XmdQEKfVHPcGwAkJD40mcT1jsftPcIFhMiKSeiK8vzg= +maunium.net/go/mautrix v0.26.3-0.20260118190708-a39bf77fe57d/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260119025107-d8b3f64a2d0f h1:tRnBU1JAj0hQlS/TfNhoIx2Xjyc6HcBwSVesWAiVZbU= +maunium.net/go/mautrix v0.26.3-0.20260119025107-d8b3f64a2d0f/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= diff --git a/policyeval/commands.go b/policyeval/commands.go index 0e20c1b..b802a77 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -5,6 +5,9 @@ import ( "crypto/sha256" "encoding/base64" "encoding/json" + "errors" + + //"errors" "fmt" "regexp" "slices" @@ -926,27 +929,27 @@ var cmdRoomInfo = &CommandHandler{ }), } -func formatDeleteResult(resp synapseadmin.RespDeleteRoomResult) string { +func formatDeleteResult(kicked, failed []id.UserID, aliases []id.RoomAlias) string { var parts []string - if len(resp.KickedUsers) > 10 { - parts = append(parts, fmt.Sprintf("* Kicked %d users", len(resp.KickedUsers))) + if len(kicked) > 10 { + parts = append(parts, fmt.Sprintf("* Kicked %d users", len(kicked))) } else { - kickedUsers := make([]string, len(resp.KickedUsers)) - for i, user := range resp.KickedUsers { + kickedUsers := make([]string, len(kicked)) + for i, user := range kicked { kickedUsers[i] = format.MarkdownMention(user) } parts = append(parts, fmt.Sprintf("* Kicked users: %s", strings.Join(kickedUsers, ", "))) } - if len(resp.FailedToKickUsers) > 0 { - failedUsers := make([]string, len(resp.FailedToKickUsers)) - for i, user := range resp.FailedToKickUsers { + if len(failed) > 0 { + failedUsers := make([]string, len(failed)) + for i, user := range failed { failedUsers[i] = format.MarkdownMention(user) } parts = append(parts, fmt.Sprintf("* Failed to kick users: %s", strings.Join(failedUsers, ", "))) } - if len(resp.LocalAliases) > 0 { - localAliases := make([]string, len(resp.LocalAliases)) - for i, alias := range resp.LocalAliases { + if len(aliases) > 0 { + localAliases := make([]string, len(aliases)) + for i, alias := range aliases { localAliases[i] = format.SafeMarkdownCode(alias) } parts = append(parts, fmt.Sprintf("* Deleted local aliases: %s", strings.Join(localAliases, ", "))) @@ -970,7 +973,9 @@ var cmdRoomDeleteStatus = &CommandHandler{ if err != nil { ce.Reply("Failed to get delete status for %s: %v", format.SafeMarkdownCode(args.DeleteID), err) } else if resp.Status == "complete" { - ce.Reply("Deletion is complete:\n\n%s", formatDeleteResult(resp.ShutdownRoom)) + ce.Reply("Deletion is complete:\n\n%s", formatDeleteResult( + resp.ShutdownRoom.KickedUsers, resp.ShutdownRoom.FailedToKickUsers, resp.ShutdownRoom.LocalAliases, + )) } else if resp.Status == "failed" { ce.Reply("Deletion failed: %s", resp.Error) } else { @@ -1060,9 +1065,26 @@ var cmdRoomDelete = &CommandHandler{ resp, err := ce.Meta.Bot.SynapseAdmin.DeleteRoomSync(ce.Ctx, roomID, req) _, _ = ce.Meta.Bot.RedactEvent(ce.Ctx, ce.RoomID, reactionID) if err != nil { + if errors.Is(err, mautrix.MUnrecognized) { + resp2, err := ce.Meta.Bot.ContinuwuityAdmin.BanRoom(ce.Ctx, roomID) + if err != nil { + ce.Reply("Failed to ban room %s: %v", format.SafeMarkdownCode(roomID), err) + return + } + ce.Reply( + "Successfully banned room %s\n\n%s", + format.SafeMarkdownCode(roomID), + formatDeleteResult(resp2.KickedUsers, resp2.FailedKickedUsers, resp2.LocalAliases), + ) + return + } ce.Reply("Failed to delete room %s: %v", format.SafeMarkdownCode(roomID), err) } else { - ce.Reply("Successfully deleted room %s\n\n%s", format.SafeMarkdownCode(roomID), formatDeleteResult(resp)) + ce.Reply( + "Successfully deleted room %s\n\n%s", + format.SafeMarkdownCode(roomID), + formatDeleteResult(resp.KickedUsers, resp.FailedToKickUsers, resp.LocalAliases), + ) } } }), From 2571478c95dd5cfc373131aa3995d7215021cd7b Mon Sep 17 00:00:00 2001 From: timedout Date: Mon, 19 Jan 2026 02:58:27 +0000 Subject: [PATCH 2/3] Remove stray comment --- go.sum | 4 ---- policyeval/commands.go | 1 - 2 files changed, 5 deletions(-) diff --git a/go.sum b/go.sum index 6f855f5..a963499 100644 --- a/go.sum +++ b/go.sum @@ -103,9 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.26.2 h1:rLiZLQoSKCJDZ+mF1gBQS4p74h3jZXs83g8D4W6Te8g= -maunium.net/go/mautrix v0.26.2/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= -maunium.net/go/mautrix v0.26.3-0.20260118190708-a39bf77fe57d h1:XmdQEKfVHPcGwAkJD40mcT1jsftPcIFhMiKSeiK8vzg= -maunium.net/go/mautrix v0.26.3-0.20260118190708-a39bf77fe57d/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= maunium.net/go/mautrix v0.26.3-0.20260119025107-d8b3f64a2d0f h1:tRnBU1JAj0hQlS/TfNhoIx2Xjyc6HcBwSVesWAiVZbU= maunium.net/go/mautrix v0.26.3-0.20260119025107-d8b3f64a2d0f/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= diff --git a/policyeval/commands.go b/policyeval/commands.go index b802a77..27f6cbb 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" - //"errors" "fmt" "regexp" "slices" From 6ec3bb33144a8a89edf1fee22c1059951797bfca Mon Sep 17 00:00:00 2001 From: timedout Date: Mon, 19 Jan 2026 03:23:26 +0000 Subject: [PATCH 3/3] Support fetching room IDs on startup --- cmd/meowlnir/main.go | 20 +++++++++++++++++++- config/config.go | 3 ++- config/example-config.yaml | 4 ++++ config/upgrade.go | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/meowlnir/main.go b/cmd/meowlnir/main.go index f7d83aa..a997fc1 100644 --- a/cmd/meowlnir/main.go +++ b/cmd/meowlnir/main.go @@ -29,6 +29,7 @@ import ( flag "maunium.net/go/mauflag" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" + "maunium.net/go/mautrix/continuwuityadmin" cryptoupgrade "maunium.net/go/mautrix/crypto/sql_store_upgrade" "maunium.net/go/mautrix/federation" "maunium.net/go/mautrix/id" @@ -397,7 +398,24 @@ func (m *Meowlnir) Run(ctx context.Context) { func (m *Meowlnir) LoadAllRoomHashes(ctx context.Context) { if m.SynapseDB == nil { - m.Log.Warn().Msg("Synapse database not configured, can't load all room hashes") + m.Log.Debug().Msg("No Synapse database configured, loading room IDs from Continuwuity admin API") + c10yClient := continuwuityadmin.Client{Client: m.AS.Intent(m.Config.Meowlnir.ContinuwuityAdminUser).Client} + start := time.Now() + resp, err := c10yClient.ListRooms(ctx) + if err != nil { + if errors.Is(err, mautrix.MUnrecognized) { + err = nil + } + m.Log.Warn().Err(err).Msg("Synapse database not configured, can't load all room hashes") + return + } + for _, room := range resp.Rooms { + m.RoomHashes.Put(room) + } + m.Log.Info(). + Dur("duration", time.Since(start)). + Int("count", len(resp.Rooms)). + Msg("Read all existing room IDs from Continuwuity admin API") return } start := time.Now() diff --git a/config/config.go b/config/config.go index 854b602..fc7d0de 100644 --- a/config/config.go +++ b/config/config.go @@ -43,7 +43,8 @@ type MeowlnirConfig struct { HackyRuleFilter []string `yaml:"hacky_rule_filter"` HackyRedactPatterns []string `yaml:"hacky_redact_patterns"` - AdminTokens map[id.UserID]string `yaml:"admin_tokens"` + AdminTokens map[id.UserID]string `yaml:"admin_tokens"` + ContinuwuityAdminUser id.UserID `yaml:"continuwuity_admin_user"` } type Meowlnir4AllConfig struct { diff --git a/config/example-config.yaml b/config/example-config.yaml index c0627e7..5f2c6f4 100644 --- a/config/example-config.yaml +++ b/config/example-config.yaml @@ -68,6 +68,10 @@ meowlnir: admin_tokens: "@abuse:example.com": admin_token + # The user ID that is in the Continuwuity admin room. Used to load room hashes and takedowns. + # Irrelevant unless you use Continuwuity. + continuwuity_admin_user: "@abuse:example.com" + # Settings for provisioning new bots using the !provision command. # None of this is relevant unless you offer moderation bots to other users. meowlnir4all: diff --git a/config/upgrade.go b/config/upgrade.go index a45d977..2246968 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -42,6 +42,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.List, "meowlnir", "hacky_rule_filter") helper.Copy(up.List, "meowlnir", "hacky_redact_patterns") helper.Copy(up.Map, "meowlnir", "admin_tokens") + helper.Copy(up.Str, "meowlnir", "continuwuity_admin_user") helper.Copy(up.Str|up.Null, "meowlnir4all", "admin_room") helper.Copy(up.Str, "meowlnir4all", "localpart_template")