diff --git a/README.md b/README.md index b1a2edf8..ac41ca78 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # mautrix-go [](https://pkg.go.dev/maunium.net/go/mautrix) -A Golang Matrix framework. Used by [gomuks](https://gomuks.app), -[go-neb](https://github.com/matrix-org/go-neb), -[mautrix-whatsapp](https://github.com/mautrix/whatsapp) +A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks), +[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/mautrix/whatsapp) and others. Matrix room: [`#go:maunium.net`](https://matrix.to/#/#go:maunium.net) @@ -14,10 +13,9 @@ The original project is licensed under [Apache 2.0](https://github.com/matrix-or In addition to the basic client API features the original project has, this framework also has: * Appservice support (Intent API like mautrix-python, room state storage, etc) -* End-to-end encryption support (incl. key backup, cross-signing, interactive verification, etc) +* End-to-end encryption support (incl. interactive SAS verification) * High-level module for building puppeting bridges -* Partial federation module (making requests, PDU processing and event authorization) -* A media proxy server which can be used to expose anything as a Matrix media repo +* High-level module for building chat clients * Wrapper functions for the Synapse admin API * Structs for parsing event content * Helpers for parsing and generating Matrix HTML diff --git a/appservice/intent.go b/appservice/intent.go index 5d43f190..e4d8e100 100644 --- a/appservice/intent.go +++ b/appservice/intent.go @@ -51,7 +51,7 @@ func (as *AppService) NewIntentAPI(localpart string) *IntentAPI { } func (intent *IntentAPI) Register(ctx context.Context) error { - _, err := intent.Client.MakeRequest(ctx, http.MethodPost, intent.BuildClientURL("v3", "register"), &mautrix.ReqRegister[any]{ + _, err := intent.Client.MakeRequest(ctx, http.MethodPost, intent.BuildClientURL("v3", "register"), &mautrix.ReqRegister{ Username: intent.Localpart, Type: mautrix.AuthTypeAppservice, InhibitLogin: true, @@ -222,17 +222,6 @@ func (intent *IntentAPI) SendMessageEvent(ctx context.Context, roomID id.RoomID, return intent.Client.SendMessageEvent(ctx, roomID, eventType, contentJSON, extra...) } -func (intent *IntentAPI) BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON any, extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) { - if err := intent.EnsureJoined(ctx, roomID); err != nil { - return nil, err - } - if !intent.SpecVersions.Supports(mautrix.BeeperFeatureEphemeralEvents) { - return nil, mautrix.MUnrecognized.WithMessage("Homeserver does not advertise com.beeper.ephemeral support") - } - contentJSON = intent.AddDoublePuppetValue(contentJSON) - return intent.Client.BeeperSendEphemeralEvent(ctx, roomID, eventType, contentJSON, extra...) -} - // Deprecated: use SendMessageEvent with mautrix.ReqSendEvent.Timestamp instead func (intent *IntentAPI) SendMassagedMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) { return intent.SendMessageEvent(ctx, roomID, eventType, contentJSON, mautrix.ReqSendEvent{Timestamp: ts}) diff --git a/bridgev2/bridgeconfig/config.go b/bridgev2/bridgeconfig/config.go index bd6b9c06..8b9aa019 100644 --- a/bridgev2/bridgeconfig/config.go +++ b/bridgev2/bridgeconfig/config.go @@ -62,40 +62,38 @@ type CleanupOnLogouts struct { } type BridgeConfig struct { - CommandPrefix string `yaml:"command_prefix"` - PersonalFilteringSpaces bool `yaml:"personal_filtering_spaces"` - PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"` - AsyncEvents bool `yaml:"async_events"` - SplitPortals bool `yaml:"split_portals"` - ResendBridgeInfo bool `yaml:"resend_bridge_info"` - NoBridgeInfoStateKey bool `yaml:"no_bridge_info_state_key"` - BridgeStatusNotices string `yaml:"bridge_status_notices"` - UnknownErrorAutoReconnect time.Duration `yaml:"unknown_error_auto_reconnect"` - UnknownErrorMaxAutoReconnects int `yaml:"unknown_error_max_auto_reconnects"` - BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"` - BridgeNotices bool `yaml:"bridge_notices"` - TagOnlyOnCreate bool `yaml:"tag_only_on_create"` - OnlyBridgeTags []event.RoomTag `yaml:"only_bridge_tags"` - MuteOnlyOnCreate bool `yaml:"mute_only_on_create"` - DeduplicateMatrixMessages bool `yaml:"deduplicate_matrix_messages"` - CrossRoomReplies bool `yaml:"cross_room_replies"` - OutgoingMessageReID bool `yaml:"outgoing_message_re_id"` - RevertFailedStateChanges bool `yaml:"revert_failed_state_changes"` - KickMatrixUsers bool `yaml:"kick_matrix_users"` - CleanupOnLogout CleanupOnLogouts `yaml:"cleanup_on_logout"` - Relay RelayConfig `yaml:"relay"` - Permissions PermissionConfig `yaml:"permissions"` - Backfill BackfillConfig `yaml:"backfill"` + CommandPrefix string `yaml:"command_prefix"` + PersonalFilteringSpaces bool `yaml:"personal_filtering_spaces"` + PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"` + AsyncEvents bool `yaml:"async_events"` + SplitPortals bool `yaml:"split_portals"` + ResendBridgeInfo bool `yaml:"resend_bridge_info"` + NoBridgeInfoStateKey bool `yaml:"no_bridge_info_state_key"` + BridgeStatusNotices string `yaml:"bridge_status_notices"` + UnknownErrorAutoReconnect time.Duration `yaml:"unknown_error_auto_reconnect"` + BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"` + BridgeNotices bool `yaml:"bridge_notices"` + TagOnlyOnCreate bool `yaml:"tag_only_on_create"` + OnlyBridgeTags []event.RoomTag `yaml:"only_bridge_tags"` + MuteOnlyOnCreate bool `yaml:"mute_only_on_create"` + DeduplicateMatrixMessages bool `yaml:"deduplicate_matrix_messages"` + CrossRoomReplies bool `yaml:"cross_room_replies"` + OutgoingMessageReID bool `yaml:"outgoing_message_re_id"` + RevertFailedStateChanges bool `yaml:"revert_failed_state_changes"` + KickMatrixUsers bool `yaml:"kick_matrix_users"` + CleanupOnLogout CleanupOnLogouts `yaml:"cleanup_on_logout"` + Relay RelayConfig `yaml:"relay"` + Permissions PermissionConfig `yaml:"permissions"` + Backfill BackfillConfig `yaml:"backfill"` } type MatrixConfig struct { - MessageStatusEvents bool `yaml:"message_status_events"` - DeliveryReceipts bool `yaml:"delivery_receipts"` - MessageErrorNotices bool `yaml:"message_error_notices"` - SyncDirectChatList bool `yaml:"sync_direct_chat_list"` - FederateRooms bool `yaml:"federate_rooms"` - UploadFileThreshold int64 `yaml:"upload_file_threshold"` - GhostExtraProfileInfo bool `yaml:"ghost_extra_profile_info"` + MessageStatusEvents bool `yaml:"message_status_events"` + DeliveryReceipts bool `yaml:"delivery_receipts"` + MessageErrorNotices bool `yaml:"message_error_notices"` + SyncDirectChatList bool `yaml:"sync_direct_chat_list"` + FederateRooms bool `yaml:"federate_rooms"` + UploadFileThreshold int64 `yaml:"upload_file_threshold"` } type AnalyticsConfig struct { diff --git a/bridgev2/bridgeconfig/upgrade.go b/bridgev2/bridgeconfig/upgrade.go index 92515ea0..a0278672 100644 --- a/bridgev2/bridgeconfig/upgrade.go +++ b/bridgev2/bridgeconfig/upgrade.go @@ -33,7 +33,6 @@ func doUpgrade(helper up.Helper) { helper.Copy(up.Bool, "bridge", "no_bridge_info_state_key") helper.Copy(up.Str|up.Null, "bridge", "bridge_status_notices") helper.Copy(up.Str|up.Int|up.Null, "bridge", "unknown_error_auto_reconnect") - helper.Copy(up.Int, "bridge", "unknown_error_max_auto_reconnects") helper.Copy(up.Bool, "bridge", "bridge_matrix_leave") helper.Copy(up.Bool, "bridge", "bridge_notices") helper.Copy(up.Bool, "bridge", "tag_only_on_create") @@ -101,7 +100,6 @@ func doUpgrade(helper up.Helper) { helper.Copy(up.Bool, "matrix", "sync_direct_chat_list") helper.Copy(up.Bool, "matrix", "federate_rooms") helper.Copy(up.Int, "matrix", "upload_file_threshold") - helper.Copy(up.Bool, "matrix", "ghost_extra_profile_info") helper.Copy(up.Str|up.Null, "analytics", "token") helper.Copy(up.Str|up.Null, "analytics", "url") diff --git a/bridgev2/bridgestate.go b/bridgev2/bridgestate.go index 96d9fd5c..babbccab 100644 --- a/bridgev2/bridgestate.go +++ b/bridgev2/bridgestate.go @@ -37,8 +37,6 @@ type BridgeStateQueue struct { stopChan chan struct{} stopReconnect atomic.Pointer[context.CancelFunc] - - unknownErrorReconnects int } func (br *Bridge) SendGlobalBridgeState(state status.BridgeState) { @@ -194,14 +192,8 @@ func (bsq *BridgeStateQueue) unknownErrorReconnect(triggeredBy status.BridgeStat } else if prevUnsent.StateEvent != status.StateUnknownError || prev.StateEvent != status.StateUnknownError { log.Debug().Msg("Not reconnecting as the previous state was not an unknown error") return - } else if bsq.unknownErrorReconnects > bsq.bridge.Config.UnknownErrorMaxAutoReconnects { - log.Warn().Msg("Not reconnecting as the maximum number of unknown error reconnects has been reached") - return } - bsq.unknownErrorReconnects++ - log.Info(). - Int("reconnect_num", bsq.unknownErrorReconnects). - Msg("Disconnecting and reconnecting login due to unknown error") + log.Info().Msg("Disconnecting and reconnecting login due to unknown error") bsq.login.Disconnect() log.Debug().Msg("Disconnection finished, recreating client and reconnecting") err := bsq.login.recreateClient(ctx) diff --git a/bridgev2/commands/login.go b/bridgev2/commands/login.go index 96d62d3e..80a7c733 100644 --- a/bridgev2/commands/login.go +++ b/bridgev2/commands/login.go @@ -121,7 +121,6 @@ func fnLogin(ce *Event) { ce.Reply("Failed to start login: %v", err) return } - ce.Log.Debug().Any("first_step", nextStep).Msg("Created login process") nextStep = checkLoginCommandDirectParams(ce, login, nextStep) if nextStep != nil { @@ -252,19 +251,14 @@ func sendQR(ce *Event, qr string, prevEventID *id.EventID) error { return fmt.Errorf("failed to upload image: %w", err) } content := &event.MessageEventContent{ - MsgType: event.MsgImage, - FileName: "qr.png", - URL: qrMXC, - File: qrFile, + MsgType: event.MsgImage, + FileName: "qr.png", + URL: qrMXC, + File: qrFile, + Body: qr, Format: event.FormatHTML, FormattedBody: fmt.Sprintf("
%s", html.EscapeString(qr)),
- Info: &event.FileInfo{
- MimeType: "image/png",
- Width: qrSizePx,
- Height: qrSizePx,
- Size: len(qrData),
- },
}
if *prevEventID != "" {
content.SetEdit(*prevEventID)
@@ -279,36 +273,6 @@ func sendQR(ce *Event, qr string, prevEventID *id.EventID) error {
return nil
}
-func sendUserInputAttachments(ce *Event, atts []*bridgev2.LoginUserInputAttachment) error {
- for _, att := range atts {
- if att.FileName == "" {
- return fmt.Errorf("missing attachment filename")
- }
- mxc, file, err := ce.Bot.UploadMedia(ce.Ctx, ce.RoomID, att.Content, att.FileName, att.Info.MimeType)
- if err != nil {
- return fmt.Errorf("failed to upload attachment %q: %w", att.FileName, err)
- }
- content := &event.MessageEventContent{
- MsgType: att.Type,
- FileName: att.FileName,
- URL: mxc,
- File: file,
- Info: &event.FileInfo{
- MimeType: att.Info.MimeType,
- Width: att.Info.Width,
- Height: att.Info.Height,
- Size: att.Info.Size,
- },
- Body: att.FileName,
- }
- _, err = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: content}, nil)
- if err != nil {
- return nil
- }
- }
- return nil
-}
-
type contextKey int
const (
@@ -500,7 +464,6 @@ func maybeURLDecodeCookie(val string, field *bridgev2.LoginCookieField) string {
}
func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginStep, override *bridgev2.UserLogin) {
- ce.Log.Debug().Any("next_step", step).Msg("Got next login step")
if step.Instructions != "" {
ce.Reply(step.Instructions)
}
@@ -515,10 +478,6 @@ func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginSte
Override: override,
}).prompt(ce)
case bridgev2.LoginStepTypeUserInput:
- err := sendUserInputAttachments(ce, step.UserInputParams.Attachments)
- if err != nil {
- ce.Reply("Failed to send attachments: %v", err)
- }
(&userInputLoginCommandState{
Login: login.(bridgev2.LoginProcessUserInput),
RemainingFields: step.UserInputParams.Fields,
diff --git a/bridgev2/commands/managechat.go b/bridgev2/commands/managechat.go
deleted file mode 100644
index 6098e24c..00000000
--- a/bridgev2/commands/managechat.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2026 Tulir Asokan
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package commands
-
-import (
- "slices"
- "time"
-
- "maunium.net/go/mautrix/bridgev2"
- "maunium.net/go/mautrix/event"
- "maunium.net/go/mautrix/format"
-)
-
-var CommandID = &FullHandler{
- Func: func(ce *Event) {
- ce.Reply("This room is bridged to %s on %s", format.SafeMarkdownCode(ce.Portal.ID), ce.Bridge.Network.GetName().DisplayName)
- },
- Name: "id",
- Help: HelpMeta{
- Section: HelpSectionChats,
- Description: "View the internal network ID of the current portal room",
- },
- RequiresPortal: true,
-}
-
-var CommandSyncChat = &FullHandler{
- Func: fnSyncChat,
- Name: "sync-portal",
- Help: HelpMeta{
- Section: HelpSectionChats,
- Description: "Sync the current portal room",
- },
- RequiresPortal: true,
- RequiresLogin: true,
-}
-
-func fnSyncChat(ce *Event) {
- login, _, err := ce.Portal.FindPreferredLogin(ce.Ctx, ce.User, false)
- if err != nil {
- ce.Log.Err(err).Msg("Failed to find login for sync")
- ce.Reply("Failed to find login: %v", err)
- return
- } else if login == nil {
- ce.Reply("No login found for sync")
- return
- }
- info, err := login.Client.GetChatInfo(ce.Ctx, ce.Portal)
- if err != nil {
- ce.Log.Err(err).Msg("Failed to get chat info for sync")
- ce.Reply("Failed to get chat info: %v", err)
- return
- }
- ce.Portal.UpdateInfo(ce.Ctx, info, login, nil, time.Time{})
- ce.React("✅️")
-}
-
-var CommandMute = &FullHandler{
- Func: fnMute,
- Name: "mute",
- Aliases: []string{"unmute"},
- Help: HelpMeta{
- Section: HelpSectionChats,
- Description: "Mute or unmute a chat on the remote network",
- Args: "[duration]",
- },
- RequiresPortal: true,
- RequiresLogin: true,
- NetworkAPI: NetworkAPIImplements[bridgev2.MuteHandlingNetworkAPI],
-}
-
-func fnMute(ce *Event) {
- _, api, _ := getClientForStartingChat[bridgev2.MuteHandlingNetworkAPI](ce, "muting chats")
- var mutedUntil int64
- if ce.Command == "mute" {
- mutedUntil = -1
- if len(ce.Args) > 0 {
- duration, err := time.ParseDuration(ce.Args[0])
- if err != nil {
- ce.Reply("Invalid duration: %v", err)
- return
- }
- mutedUntil = time.Now().Add(duration).UnixMilli()
- }
- }
- err := api.HandleMute(ce.Ctx, &bridgev2.MatrixMute{
- MatrixEventBase: bridgev2.MatrixEventBase[*event.BeeperMuteEventContent]{
- Content: &event.BeeperMuteEventContent{MutedUntil: mutedUntil},
- Portal: ce.Portal,
- },
- })
- if err != nil {
- ce.Reply("Failed to %s chat: %v", ce.Command, err)
- } else {
- ce.React("✅️")
- }
-}
-
-var CommandDeleteChat = &FullHandler{
- Func: fnDeleteChat,
- Name: "delete-chat",
- Help: HelpMeta{
- Section: HelpSectionChats,
- Description: "Delete the current chat on the remote network",
- Args: "[--for-everyone]",
- },
- RequiresPortal: true,
- RequiresLogin: true,
- NetworkAPI: NetworkAPIImplements[bridgev2.DeleteChatHandlingNetworkAPI],
-}
-
-func fnDeleteChat(ce *Event) {
- _, api, _ := getClientForStartingChat[bridgev2.DeleteChatHandlingNetworkAPI](ce, "deleting chats")
- err := api.HandleMatrixDeleteChat(ce.Ctx, &bridgev2.MatrixDeleteChat{
- Event: nil,
- Content: &event.BeeperChatDeleteEventContent{
- DeleteForEveryone: slices.Contains(ce.Args, "--for-everyone"),
- FromMessageRequest: ce.Portal.MessageRequest,
- },
- Portal: ce.Portal,
- })
- if err != nil {
- ce.Reply("Failed to delete chat: %v", err)
- } else {
- ce.React("✅️")
- }
-}
diff --git a/bridgev2/commands/processor.go b/bridgev2/commands/processor.go
index d525e380..391c3685 100644
--- a/bridgev2/commands/processor.go
+++ b/bridgev2/commands/processor.go
@@ -45,8 +45,7 @@ func NewProcessor(bridge *bridgev2.Bridge) bridgev2.CommandProcessor {
CommandDeletePortal, CommandDeleteAllPortals, CommandSetManagementRoom,
CommandLogin, CommandRelogin, CommandListLogins, CommandLogout, CommandSetPreferredLogin,
CommandSetRelay, CommandUnsetRelay,
- CommandResolveIdentifier, CommandStartChat, CommandCreateGroup, CommandSearch,
- CommandID, CommandSyncChat, CommandMute, CommandDeleteChat,
+ CommandResolveIdentifier, CommandStartChat, CommandCreateGroup, CommandSearch, CommandSyncChat, CommandMute,
CommandSudo, CommandDoIn,
)
return proc
diff --git a/bridgev2/commands/startchat.go b/bridgev2/commands/startchat.go
index 0d7942a6..c7b05a6e 100644
--- a/bridgev2/commands/startchat.go
+++ b/bridgev2/commands/startchat.go
@@ -14,6 +14,7 @@ import (
"maps"
"slices"
"strings"
+ "time"
"github.com/rs/zerolog"
@@ -37,6 +38,35 @@ var CommandResolveIdentifier = &FullHandler{
NetworkAPI: NetworkAPIImplements[bridgev2.IdentifierResolvingNetworkAPI],
}
+var CommandSyncChat = &FullHandler{
+ Func: func(ce *Event) {
+ login, _, err := ce.Portal.FindPreferredLogin(ce.Ctx, ce.User, false)
+ if err != nil {
+ ce.Log.Err(err).Msg("Failed to find login for sync")
+ ce.Reply("Failed to find login: %v", err)
+ return
+ } else if login == nil {
+ ce.Reply("No login found for sync")
+ return
+ }
+ info, err := login.Client.GetChatInfo(ce.Ctx, ce.Portal)
+ if err != nil {
+ ce.Log.Err(err).Msg("Failed to get chat info for sync")
+ ce.Reply("Failed to get chat info: %v", err)
+ return
+ }
+ ce.Portal.UpdateInfo(ce.Ctx, info, login, nil, time.Time{})
+ ce.React("✅️")
+ },
+ Name: "sync-portal",
+ Help: HelpMeta{
+ Section: HelpSectionChats,
+ Description: "Sync the current portal room",
+ },
+ RequiresPortal: true,
+ RequiresLogin: true,
+}
+
var CommandStartChat = &FullHandler{
Func: fnResolveIdentifier,
Name: "start-chat",
@@ -128,9 +158,8 @@ var CommandCreateGroup = &FullHandler{
Description: "Create a new group chat for the current Matrix room",
Args: "[_group type_]",
},
- RequiresLogin: true,
- NetworkAPI: NetworkAPIImplements[bridgev2.GroupCreatingNetworkAPI],
- RequiresEventLevel: event.StateBridge,
+ RequiresLogin: true,
+ NetworkAPI: NetworkAPIImplements[bridgev2.GroupCreatingNetworkAPI],
}
func getState[T any](ctx context.Context, roomID id.RoomID, evtType event.Type, provider bridgev2.MatrixConnectorWithArbitraryRoomState) (content T) {
@@ -144,6 +173,7 @@ func getState[T any](ctx context.Context, roomID id.RoomID, evtType event.Type,
}
func fnCreateGroup(ce *Event) {
+ ce.Bridge.Matrix.GetCapabilities()
login, api, remainingArgs := getClientForStartingChat[bridgev2.GroupCreatingNetworkAPI](ce, "creating group")
if api == nil {
return
@@ -260,3 +290,44 @@ func fnSearch(ce *Event) {
}
ce.Reply("Search results:\n\n%s", strings.Join(resultsString, "\n"))
}
+
+var CommandMute = &FullHandler{
+ Func: fnMute,
+ Name: "mute",
+ Aliases: []string{"unmute"},
+ Help: HelpMeta{
+ Section: HelpSectionChats,
+ Description: "Mute or unmute a chat on the remote network",
+ Args: "[duration]",
+ },
+ RequiresPortal: true,
+ RequiresLogin: true,
+ NetworkAPI: NetworkAPIImplements[bridgev2.MuteHandlingNetworkAPI],
+}
+
+func fnMute(ce *Event) {
+ _, api, _ := getClientForStartingChat[bridgev2.MuteHandlingNetworkAPI](ce, "muting chats")
+ var mutedUntil int64
+ if ce.Command == "mute" {
+ mutedUntil = -1
+ if len(ce.Args) > 0 {
+ duration, err := time.ParseDuration(ce.Args[0])
+ if err != nil {
+ ce.Reply("Invalid duration: %v", err)
+ return
+ }
+ mutedUntil = time.Now().Add(duration).UnixMilli()
+ }
+ }
+ err := api.HandleMute(ce.Ctx, &bridgev2.MatrixMute{
+ MatrixEventBase: bridgev2.MatrixEventBase[*event.BeeperMuteEventContent]{
+ Content: &event.BeeperMuteEventContent{MutedUntil: mutedUntil},
+ Portal: ce.Portal,
+ },
+ })
+ if err != nil {
+ ce.Reply("Failed to %s chat: %v", ce.Command, err)
+ } else {
+ ce.React("✅️")
+ }
+}
diff --git a/bridgev2/database/ghost.go b/bridgev2/database/ghost.go
index 16af35ca..c32929ad 100644
--- a/bridgev2/database/ghost.go
+++ b/bridgev2/database/ghost.go
@@ -7,17 +7,12 @@
package database
import (
- "bytes"
"context"
"encoding/hex"
- "encoding/json"
- "fmt"
"go.mau.fi/util/dbutil"
- "go.mau.fi/util/exerrors"
"maunium.net/go/mautrix/bridgev2/networkid"
- "maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/id"
)
@@ -27,55 +22,6 @@ type GhostQuery struct {
*dbutil.QueryHelper[*Ghost]
}
-type ExtraProfile map[string]json.RawMessage
-
-func (ep *ExtraProfile) Set(key string, value any) error {
- if key == "displayname" || key == "avatar_url" {
- return fmt.Errorf("cannot set reserved profile key %q", key)
- }
- marshaled, err := json.Marshal(value)
- if err != nil {
- return err
- }
- if *ep == nil {
- *ep = make(ExtraProfile)
- }
- (*ep)[key] = canonicaljson.CanonicalJSONAssumeValid(marshaled)
- return nil
-}
-
-func (ep *ExtraProfile) With(key string, value any) *ExtraProfile {
- exerrors.PanicIfNotNil(ep.Set(key, value))
- return ep
-}
-
-func canonicalizeIfObject(data json.RawMessage) json.RawMessage {
- if len(data) > 0 && (data[0] == '{' || data[0] == '[') {
- return canonicaljson.CanonicalJSONAssumeValid(data)
- }
- return data
-}
-
-func (ep *ExtraProfile) CopyTo(dest *ExtraProfile) (changed bool) {
- if len(*ep) == 0 {
- return
- }
- if *dest == nil {
- *dest = make(ExtraProfile)
- }
- for key, val := range *ep {
- if key == "displayname" || key == "avatar_url" {
- continue
- }
- existing, exists := (*dest)[key]
- if !exists || !bytes.Equal(canonicalizeIfObject(existing), val) {
- (*dest)[key] = val
- changed = true
- }
- }
- return
-}
-
type Ghost struct {
BridgeID networkid.BridgeID
ID networkid.UserID
@@ -89,14 +35,13 @@ type Ghost struct {
ContactInfoSet bool
IsBot bool
Identifiers []string
- ExtraProfile ExtraProfile
Metadata any
}
const (
getGhostBaseQuery = `
SELECT bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc,
- name_set, avatar_set, contact_info_set, is_bot, identifiers, extra_profile, metadata
+ name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata
FROM ghost
`
getGhostByIDQuery = getGhostBaseQuery + `WHERE bridge_id=$1 AND id=$2`
@@ -104,14 +49,13 @@ const (
insertGhostQuery = `
INSERT INTO ghost (
bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc,
- name_set, avatar_set, contact_info_set, is_bot, identifiers, extra_profile, metadata
+ name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata
)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
`
updateGhostQuery = `
UPDATE ghost SET name=$3, avatar_id=$4, avatar_hash=$5, avatar_mxc=$6,
- name_set=$7, avatar_set=$8, contact_info_set=$9, is_bot=$10,
- identifiers=$11, extra_profile=$12, metadata=$13
+ name_set=$7, avatar_set=$8, contact_info_set=$9, is_bot=$10, identifiers=$11, metadata=$12
WHERE bridge_id=$1 AND id=$2
`
)
@@ -142,7 +86,7 @@ func (g *Ghost) Scan(row dbutil.Scannable) (*Ghost, error) {
&g.BridgeID, &g.ID,
&g.Name, &g.AvatarID, &avatarHash, &g.AvatarMXC,
&g.NameSet, &g.AvatarSet, &g.ContactInfoSet, &g.IsBot,
- dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: &g.ExtraProfile}, dbutil.JSON{Data: g.Metadata},
+ dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.Metadata},
)
if err != nil {
return nil, err
@@ -172,6 +116,6 @@ func (g *Ghost) sqlVariables() []any {
g.BridgeID, g.ID,
g.Name, g.AvatarID, avatarHash, g.AvatarMXC,
g.NameSet, g.AvatarSet, g.ContactInfoSet, g.IsBot,
- dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.ExtraProfile}, dbutil.JSON{Data: g.Metadata},
+ dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.Metadata},
}
}
diff --git a/bridgev2/database/message.go b/bridgev2/database/message.go
index 4fd599a8..43f33666 100644
--- a/bridgev2/database/message.go
+++ b/bridgev2/database/message.go
@@ -68,8 +68,8 @@ const (
getFirstMessagePartByIDQuery = getMessageBaseQuery + `WHERE bridge_id=$1 AND (room_receiver=$2 OR room_receiver='') AND id=$3 ORDER BY part_id ASC LIMIT 1`
getMessagesBetweenTimeQuery = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND timestamp>$4 AND timestamp<=$5`
getOldestMessageInPortal = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 ORDER BY timestamp ASC, part_id ASC LIMIT 1`
- getFirstMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY thread_root_id NULLS FIRST, timestamp ASC, part_id ASC LIMIT 1`
- getLastMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY thread_root_id NULLS LAST, timestamp DESC, part_id DESC LIMIT 1`
+ getFirstMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY timestamp ASC, part_id ASC LIMIT 1`
+ getLastMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY timestamp DESC, part_id DESC LIMIT 1`
getLastNInPortal = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 ORDER BY timestamp DESC, part_id DESC LIMIT $4`
getLastMessagePartAtOrBeforeTimeQuery = getMessageBaseQuery + `WHERE bridge_id = $1 AND room_id=$2 AND room_receiver=$3 AND timestamp<=$4 ORDER BY timestamp DESC, part_id DESC LIMIT 1`
diff --git a/bridgev2/database/upgrades/00-latest.sql b/bridgev2/database/upgrades/00-latest.sql
index 6092dc24..b193d314 100644
--- a/bridgev2/database/upgrades/00-latest.sql
+++ b/bridgev2/database/upgrades/00-latest.sql
@@ -1,4 +1,4 @@
--- v0 -> v27 (compatible with v9+): Latest revision
+-- v0 -> v26 (compatible with v9+): Latest revision
CREATE TABLE "user" (
bridge_id TEXT NOT NULL,
mxid TEXT NOT NULL,
@@ -80,7 +80,6 @@ CREATE TABLE ghost (
contact_info_set BOOLEAN NOT NULL,
is_bot BOOLEAN NOT NULL,
identifiers jsonb NOT NULL,
- extra_profile jsonb,
metadata jsonb NOT NULL,
PRIMARY KEY (bridge_id, id)
diff --git a/bridgev2/database/upgrades/27-ghost-extra-profile.sql b/bridgev2/database/upgrades/27-ghost-extra-profile.sql
deleted file mode 100644
index e8e0549a..00000000
--- a/bridgev2/database/upgrades/27-ghost-extra-profile.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- v27 (compatible with v9+): Add column for extra ghost profile metadata
-ALTER TABLE ghost ADD COLUMN extra_profile jsonb;
diff --git a/bridgev2/database/userlogin.go b/bridgev2/database/userlogin.go
index 00ff01c9..9fa6569a 100644
--- a/bridgev2/database/userlogin.go
+++ b/bridgev2/database/userlogin.go
@@ -116,7 +116,7 @@ func (u *UserLogin) ensureHasMetadata(metaType MetaTypeCreator) *UserLogin {
func (u *UserLogin) sqlVariables() []any {
var remoteProfile dbutil.JSON
- if !u.RemoteProfile.IsZero() {
+ if !u.RemoteProfile.IsEmpty() {
remoteProfile.Data = &u.RemoteProfile
}
return []any{u.BridgeID, u.UserMXID, u.ID, u.RemoteName, remoteProfile, dbutil.StrPtr(u.SpaceRoom), dbutil.JSON{Data: u.Metadata}}
diff --git a/bridgev2/errors.go b/bridgev2/errors.go
index f6677d2e..514dc238 100644
--- a/bridgev2/errors.go
+++ b/bridgev2/errors.go
@@ -75,7 +75,6 @@ var (
ErrMediaConvertFailed error = WrapErrorInStatus(errors.New("failed to convert media")).WithMessage("failed to convert media").WithIsCertain(true).WithSendNotice(true)
ErrMembershipNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support changing group membership")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrDeleteChatNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support deleting chats")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
- ErrBeeperAIStreamNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support Beeper AI stream events")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrPowerLevelsNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support changing group power levels")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrRemoteEchoTimeout = WrapErrorInStatus(errors.New("remote echo timed out")).WithIsCertain(false).WithSendNotice(true).WithErrorReason(event.MessageStatusTooOld)
ErrRemoteAckTimeout = WrapErrorInStatus(errors.New("remote ack timed out")).WithIsCertain(false).WithSendNotice(true).WithErrorReason(event.MessageStatusTooOld)
diff --git a/bridgev2/ghost.go b/bridgev2/ghost.go
index 590dd1dc..f7072a9c 100644
--- a/bridgev2/ghost.go
+++ b/bridgev2/ghost.go
@@ -9,15 +9,12 @@ package bridgev2
import (
"context"
"crypto/sha256"
- "encoding/json"
"fmt"
- "maps"
"net/http"
- "slices"
"github.com/rs/zerolog"
- "go.mau.fi/util/exerrors"
"go.mau.fi/util/exmime"
+ "golang.org/x/exp/slices"
"maunium.net/go/mautrix/bridgev2/database"
"maunium.net/go/mautrix/bridgev2/networkid"
@@ -137,11 +134,10 @@ func (a *Avatar) Reupload(ctx context.Context, intent MatrixAPI, currentHash [32
}
type UserInfo struct {
- Identifiers []string
- Name *string
- Avatar *Avatar
- IsBot *bool
- ExtraProfile database.ExtraProfile
+ Identifiers []string
+ Name *string
+ Avatar *Avatar
+ IsBot *bool
ExtraUpdates ExtraUpdater[*Ghost]
}
@@ -189,9 +185,9 @@ func (ghost *Ghost) UpdateAvatar(ctx context.Context, avatar *Avatar) bool {
return true
}
-func (ghost *Ghost) getExtraProfileMeta() any {
+func (ghost *Ghost) getExtraProfileMeta() *event.BeeperProfileExtra {
bridgeName := ghost.Bridge.Network.GetName()
- baseExtra := &event.BeeperProfileExtra{
+ return &event.BeeperProfileExtra{
RemoteID: string(ghost.ID),
Identifiers: ghost.Identifiers,
Service: bridgeName.BeeperBridgeType,
@@ -199,35 +195,23 @@ func (ghost *Ghost) getExtraProfileMeta() any {
IsBridgeBot: false,
IsNetworkBot: ghost.IsBot,
}
- if len(ghost.ExtraProfile) == 0 {
- return baseExtra
- }
- mergedExtra := maps.Clone(ghost.ExtraProfile)
- baseExtraMarshaled := exerrors.Must(json.Marshal(baseExtra))
- exerrors.PanicIfNotNil(json.Unmarshal(baseExtraMarshaled, &mergedExtra))
- return mergedExtra
}
-func (ghost *Ghost) UpdateContactInfo(ctx context.Context, identifiers []string, isBot *bool, extraProfile database.ExtraProfile) bool {
- if !ghost.Bridge.Matrix.GetCapabilities().ExtraProfileMeta {
- ghost.ContactInfoSet = false
- return false
- }
+func (ghost *Ghost) UpdateContactInfo(ctx context.Context, identifiers []string, isBot *bool) bool {
if identifiers != nil {
slices.Sort(identifiers)
}
- changed := extraProfile.CopyTo(&ghost.ExtraProfile)
+ if ghost.ContactInfoSet &&
+ (identifiers == nil || slices.Equal(identifiers, ghost.Identifiers)) &&
+ (isBot == nil || *isBot == ghost.IsBot) {
+ return false
+ }
if identifiers != nil {
- changed = changed || !slices.Equal(identifiers, ghost.Identifiers)
ghost.Identifiers = identifiers
}
if isBot != nil {
- changed = changed || *isBot != ghost.IsBot
ghost.IsBot = *isBot
}
- if ghost.ContactInfoSet && !changed {
- return false
- }
err := ghost.Intent.SetExtraProfileMeta(ctx, ghost.getExtraProfileMeta())
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to set extra profile metadata")
@@ -303,8 +287,8 @@ func (ghost *Ghost) UpdateInfo(ctx context.Context, info *UserInfo) {
ghost.AvatarSet = true
update = true
}
- if info.Identifiers != nil || info.IsBot != nil || info.ExtraProfile != nil {
- update = ghost.UpdateContactInfo(ctx, info.Identifiers, info.IsBot, info.ExtraProfile) || update
+ if info.Identifiers != nil || info.IsBot != nil {
+ update = ghost.UpdateContactInfo(ctx, info.Identifiers, info.IsBot) || update
}
if info.ExtraUpdates != nil {
update = info.ExtraUpdates(ctx, ghost) || update
diff --git a/bridgev2/login.go b/bridgev2/login.go
index b8321719..4ddbf13e 100644
--- a/bridgev2/login.go
+++ b/bridgev2/login.go
@@ -13,7 +13,6 @@ import (
"strings"
"maunium.net/go/mautrix/bridgev2/networkid"
- "maunium.net/go/mautrix/event"
)
// LoginProcess represents a single occurrence of a user logging into the remote network.
@@ -180,7 +179,6 @@ const (
LoginInputFieldTypeURL LoginInputFieldType = "url"
LoginInputFieldTypeDomain LoginInputFieldType = "domain"
LoginInputFieldTypeSelect LoginInputFieldType = "select"
- LoginInputFieldTypeCaptchaCode LoginInputFieldType = "captcha_code"
)
type LoginInputDataField struct {
@@ -273,23 +271,6 @@ func (f *LoginInputDataField) FillDefaultValidate() {
type LoginUserInputParams struct {
// The fields that the user needs to fill in.
Fields []LoginInputDataField `json:"fields"`
-
- // Attachments to display alongside the input fields.
- Attachments []*LoginUserInputAttachment `json:"attachments"`
-}
-
-type LoginUserInputAttachment struct {
- Type event.MessageType `json:"type,omitempty"`
- FileName string `json:"filename,omitempty"`
- Content []byte `json:"content,omitempty"`
- Info LoginUserInputAttachmentInfo `json:"info,omitempty"`
-}
-
-type LoginUserInputAttachmentInfo struct {
- MimeType string `json:"mimetype,omitempty"`
- Width int `json:"w,omitempty"`
- Height int `json:"h,omitempty"`
- Size int `json:"size,omitempty"`
}
type LoginCompleteParams struct {
diff --git a/bridgev2/matrix/connector.go b/bridgev2/matrix/connector.go
index 5a2df953..aed6d3bd 100644
--- a/bridgev2/matrix/connector.go
+++ b/bridgev2/matrix/connector.go
@@ -144,7 +144,6 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
br.EventProcessor.On(event.EventReaction, br.handleRoomEvent)
br.EventProcessor.On(event.EventRedaction, br.handleRoomEvent)
br.EventProcessor.On(event.EventEncrypted, br.handleEncryptedEvent)
- br.EventProcessor.On(event.EphemeralEventEncrypted, br.handleEncryptedEvent)
br.EventProcessor.On(event.StateMember, br.handleRoomEvent)
br.EventProcessor.On(event.StatePowerLevels, br.handleRoomEvent)
br.EventProcessor.On(event.StateRoomName, br.handleRoomEvent)
@@ -157,7 +156,6 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
br.EventProcessor.On(event.BeeperAcceptMessageRequest, br.handleRoomEvent)
br.EventProcessor.On(event.EphemeralEventReceipt, br.handleEphemeralEvent)
br.EventProcessor.On(event.EphemeralEventTyping, br.handleEphemeralEvent)
- br.EventProcessor.On(event.BeeperEphemeralEventAIStream, br.handleEphemeralEvent)
br.Bot = br.AS.BotIntent()
br.Crypto = NewCryptoHelper(br)
br.Bridge.Commands.(*commands.Processor).AddHandlers(
@@ -369,8 +367,6 @@ func (br *Connector) ensureConnection(ctx context.Context) {
br.Capabilities.AutoJoinInvites = br.SpecVersions.Supports(mautrix.BeeperFeatureAutojoinInvites)
br.Capabilities.BatchSending = br.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending)
br.Capabilities.ArbitraryMemberChange = br.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryMemberChange)
- br.Capabilities.ExtraProfileMeta = br.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) ||
- (br.SpecVersions.Supports(mautrix.FeatureArbitraryProfileFields) && br.Config.Matrix.GhostExtraProfileInfo)
break
}
}
diff --git a/bridgev2/matrix/intent.go b/bridgev2/matrix/intent.go
index f7254bd4..173f7c15 100644
--- a/bridgev2/matrix/intent.go
+++ b/bridgev2/matrix/intent.go
@@ -9,7 +9,6 @@ package matrix
import (
"bytes"
"context"
- "encoding/json"
"errors"
"fmt"
"io"
@@ -28,7 +27,6 @@ import (
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/bridgeconfig"
"maunium.net/go/mautrix/crypto/attachment"
- "maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/pushrules"
@@ -45,7 +43,6 @@ type ASIntent struct {
var _ bridgev2.MatrixAPI = (*ASIntent)(nil)
var _ bridgev2.MarkAsDMMatrixAPI = (*ASIntent)(nil)
-var _ bridgev2.EphemeralSendingMatrixAPI = (*ASIntent)(nil)
func (as *ASIntent) SendMessage(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, extra *bridgev2.MatrixSendExtra) (*mautrix.RespSendEvent, error) {
if extra == nil {
@@ -87,21 +84,6 @@ func (as *ASIntent) SendMessage(ctx context.Context, roomID id.RoomID, eventType
return as.Matrix.SendMessageEvent(ctx, roomID, eventType, content, mautrix.ReqSendEvent{Timestamp: extra.Timestamp.UnixMilli()})
}
-func (as *ASIntent) BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, txnID string) (*mautrix.RespSendEvent, error) {
- if !as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureEphemeralEvents) {
- return nil, mautrix.MUnrecognized.WithMessage("Homeserver does not advertise com.beeper.ephemeral support")
- }
- if encrypted, err := as.Matrix.StateStore.IsEncrypted(ctx, roomID); err != nil {
- return nil, fmt.Errorf("failed to check if room is encrypted: %w", err)
- } else if encrypted && as.Connector.Crypto != nil {
- if err = as.Connector.Crypto.Encrypt(ctx, roomID, eventType, content); err != nil {
- return nil, err
- }
- eventType = event.EventEncrypted
- }
- return as.Matrix.BeeperSendEphemeralEvent(ctx, roomID, eventType, content, mautrix.ReqSendEvent{TransactionID: txnID})
-}
-
func (as *ASIntent) fillMemberEvent(ctx context.Context, roomID id.RoomID, userID id.UserID, content *event.Content) {
targetContent, ok := content.Parsed.(*event.MemberEventContent)
if !ok || targetContent.Displayname != "" || targetContent.AvatarURL != "" {
@@ -486,62 +468,11 @@ func (as *ASIntent) SetAvatarURL(ctx context.Context, avatarURL id.ContentURIStr
return as.Matrix.SetAvatarURL(ctx, parsedAvatarURL)
}
-func dataToFields(data any) (map[string]json.RawMessage, error) {
- fields, ok := data.(map[string]json.RawMessage)
- if ok {
- return fields, nil
- }
- d, err := json.Marshal(data)
- if err != nil {
- return nil, err
- }
- d = canonicaljson.CanonicalJSONAssumeValid(d)
- err = json.Unmarshal(d, &fields)
- return fields, err
-}
-
-func marshalField(val any) json.RawMessage {
- data, _ := json.Marshal(val)
- if len(data) > 0 && (data[0] == '{' || data[0] == '[') {
- return canonicaljson.CanonicalJSONAssumeValid(data)
- }
- return data
-}
-
-var nullJSON = json.RawMessage("null")
-
func (as *ASIntent) SetExtraProfileMeta(ctx context.Context, data any) error {
- if as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) {
- return as.Matrix.BeeperUpdateProfile(ctx, data)
- } else if as.Connector.SpecVersions.Supports(mautrix.FeatureArbitraryProfileFields) && as.Connector.Config.Matrix.GhostExtraProfileInfo {
- fields, err := dataToFields(data)
- if err != nil {
- return fmt.Errorf("failed to marshal fields: %w", err)
- }
- currentProfile, err := as.Matrix.GetProfile(ctx, as.Matrix.UserID)
- if err != nil {
- return fmt.Errorf("failed to get current profile: %w", err)
- }
- for key, val := range fields {
- existing, ok := currentProfile.Extra[key]
- if !ok {
- if bytes.Equal(val, nullJSON) {
- continue
- }
- err = as.Matrix.SetProfileField(ctx, key, val)
- } else if !bytes.Equal(marshalField(existing), val) {
- if bytes.Equal(val, nullJSON) {
- err = as.Matrix.DeleteProfileField(ctx, key)
- } else {
- err = as.Matrix.SetProfileField(ctx, key, val)
- }
- }
- if err != nil {
- return fmt.Errorf("failed to set profile field %q: %w", key, err)
- }
- }
+ if !as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) {
+ return nil
}
- return nil
+ return as.Matrix.BeeperUpdateProfile(ctx, data)
}
func (as *ASIntent) GetMXID() id.UserID {
diff --git a/bridgev2/matrix/matrix.go b/bridgev2/matrix/matrix.go
index 954d0ad9..570ae5f1 100644
--- a/bridgev2/matrix/matrix.go
+++ b/bridgev2/matrix/matrix.go
@@ -68,10 +68,6 @@ func (br *Connector) handleEphemeralEvent(ctx context.Context, evt *event.Event)
case event.EphemeralEventTyping:
typingContent := evt.Content.AsTyping()
typingContent.UserIDs = slices.DeleteFunc(typingContent.UserIDs, br.shouldIgnoreEventFromUser)
- case event.BeeperEphemeralEventAIStream:
- if br.shouldIgnoreEvent(evt) {
- return
- }
}
br.Bridge.QueueMatrixEvent(ctx, evt)
}
@@ -235,6 +231,7 @@ func (br *Connector) postDecrypt(ctx context.Context, original, decrypted *event
go br.sendSuccessCheckpoint(ctx, decrypted, status.MsgStepDecrypted, retryCount)
decrypted.Mautrix.CheckpointSent = true
decrypted.Mautrix.DecryptionDuration = duration
+ decrypted.Mautrix.EventSource |= event.SourceDecrypted
br.EventProcessor.Dispatch(ctx, decrypted)
if errorEventID != nil && *errorEventID != "" {
_, _ = br.Bot.RedactEvent(ctx, decrypted.RoomID, *errorEventID)
diff --git a/bridgev2/matrix/mxmain/example-config.yaml b/bridgev2/matrix/mxmain/example-config.yaml
index ccc81c4b..b0e83696 100644
--- a/bridgev2/matrix/mxmain/example-config.yaml
+++ b/bridgev2/matrix/mxmain/example-config.yaml
@@ -29,9 +29,6 @@ bridge:
# How long after an unknown error should the bridge attempt a full reconnect?
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
unknown_error_auto_reconnect: null
- # Maximum number of times to do the auto-reconnect above.
- # The counter is per login, but is never reset except on logout and restart.
- unknown_error_max_auto_reconnects: 10
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
bridge_matrix_leave: false
@@ -244,9 +241,6 @@ matrix:
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
# rather than keeping the whole file in memory.
upload_file_threshold: 5242880
- # Should the bridge set additional custom profile info for ghosts?
- # This can make a lot of requests, as there's no batch profile update endpoint.
- ghost_extra_profile_info: false
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
analytics:
diff --git a/bridgev2/matrix/provisioning.go b/bridgev2/matrix/provisioning.go
index 243b91da..17e827e3 100644
--- a/bridgev2/matrix/provisioning.go
+++ b/bridgev2/matrix/provisioning.go
@@ -324,7 +324,7 @@ func (prov *ProvisioningAPI) GetWhoami(w http.ResponseWriter, r *http.Request) {
prevState.UserID = ""
prevState.RemoteID = ""
prevState.RemoteName = ""
- prevState.RemoteProfile = status.RemoteProfile{}
+ prevState.RemoteProfile = nil
resp.Logins[i] = RespWhoamiLogin{
StateEvent: prevState.StateEvent,
StateTS: prevState.Timestamp,
@@ -403,17 +403,10 @@ func (prov *ProvisioningAPI) PostLoginStart(w http.ResponseWriter, r *http.Reque
Override: overrideLogin,
}
prov.loginsLock.Unlock()
- zerolog.Ctx(r.Context()).Info().
- Any("first_step", firstStep).
- Msg("Created login process")
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: loginID, LoginStep: firstStep})
}
func (prov *ProvisioningAPI) handleCompleteStep(ctx context.Context, login *ProvLogin, step *bridgev2.LoginStep) {
- zerolog.Ctx(ctx).Info().
- Str("step_id", step.StepID).
- Str("user_login_id", string(step.CompleteParams.UserLoginID)).
- Msg("Login completed successfully")
prov.deleteLogin(login, false)
if login.Override == nil || login.Override.ID == step.CompleteParams.UserLoginID {
return
@@ -513,8 +506,6 @@ func (prov *ProvisioningAPI) PostLoginSubmitInput(w http.ResponseWriter, r *http
login.NextStep = nextStep
if nextStep.Type == bridgev2.LoginStepTypeComplete {
prov.handleCompleteStep(r.Context(), login, nextStep)
- } else {
- zerolog.Ctx(r.Context()).Debug().Any("next_step", nextStep).Msg("Returning next login step")
}
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: login.ID, LoginStep: nextStep})
}
@@ -534,8 +525,6 @@ func (prov *ProvisioningAPI) PostLoginWait(w http.ResponseWriter, r *http.Reques
login.NextStep = nextStep
if nextStep.Type == bridgev2.LoginStepTypeComplete {
prov.handleCompleteStep(r.Context(), login, nextStep)
- } else {
- zerolog.Ctx(r.Context()).Debug().Any("next_step", nextStep).Msg("Returning next login step")
}
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: login.ID, LoginStep: nextStep})
}
diff --git a/bridgev2/matrix/provisioning.yaml b/bridgev2/matrix/provisioning.yaml
index 26068db4..d19a7e83 100644
--- a/bridgev2/matrix/provisioning.yaml
+++ b/bridgev2/matrix/provisioning.yaml
@@ -740,41 +740,6 @@ components:
description: For fields of type select, the valid options.
items:
type: string
- attachments:
- type: array
- description: A list of media attachments to show the user alongside the form fields.
- items:
- type: object
- description: A media attachment to show the user.
- required: [ type, filename, content ]
- properties:
- type:
- type: string
- description: The type of media attachment, using the same media type identifiers as Matrix attachments. Only some are supported.
- enum: [ m.image, m.audio ]
- filename:
- type: string
- description: The filename for the media attachment.
- content:
- type: string
- description: The raw file content for the attachment encoded in base64.
- info:
- type: object
- description: Optional but recommended metadata for the attachment. Can generally be derived from the raw content if omitted.
- properties:
- mimetype:
- type: string
- description: The MIME type for the media content.
- examples: [ image/png, audio/mpeg ]
- w:
- type: number
- description: The width of the media in pixels. Only applicable for images and videos.
- h:
- type: number
- description: The height of the media in pixels. Only applicable for images and videos.
- size:
- type: number
- description: The size of the media content in number of bytes. Strongly recommended to include.
- description: Cookie login step
required: [ type, cookies ]
properties:
diff --git a/bridgev2/matrixinterface.go b/bridgev2/matrixinterface.go
index be26db49..57f786bb 100644
--- a/bridgev2/matrixinterface.go
+++ b/bridgev2/matrixinterface.go
@@ -28,7 +28,6 @@ type MatrixCapabilities struct {
AutoJoinInvites bool
BatchSending bool
ArbitraryMemberChange bool
- ExtraProfileMeta bool
}
type MatrixConnector interface {
@@ -218,8 +217,3 @@ type MarkAsDMMatrixAPI interface {
MatrixAPI
MarkAsDM(ctx context.Context, roomID id.RoomID, otherUser id.UserID) error
}
-
-type EphemeralSendingMatrixAPI interface {
- MatrixAPI
- BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, txnID string) (*mautrix.RespSendEvent, error)
-}
diff --git a/bridgev2/networkinterface.go b/bridgev2/networkinterface.go
index b706aedb..0e9a8543 100644
--- a/bridgev2/networkinterface.go
+++ b/bridgev2/networkinterface.go
@@ -726,11 +726,6 @@ type MessageRequestAcceptingNetworkAPI interface {
HandleMatrixAcceptMessageRequest(ctx context.Context, msg *MatrixAcceptMessageRequest) error
}
-type BeeperAIStreamHandlingNetworkAPI interface {
- NetworkAPI
- HandleMatrixBeeperAIStream(ctx context.Context, msg *MatrixBeeperAIStream) error
-}
-
type ResolveIdentifierResponse struct {
// Ghost is the ghost of the user that the identifier resolves to.
// This field should be set whenever possible. However, it is not required,
@@ -1115,11 +1110,6 @@ type RemoteEvent interface {
GetSender() EventSender
}
-type RemoteEventWithContextMutation interface {
- RemoteEvent
- MutateContext(ctx context.Context) context.Context
-}
-
type RemoteEventWithUncertainPortalReceiver interface {
RemoteEvent
PortalReceiverIsUncertain() bool
@@ -1449,7 +1439,6 @@ type MatrixViewingChat struct {
type MatrixDeleteChat = MatrixEventBase[*event.BeeperChatDeleteEventContent]
type MatrixAcceptMessageRequest = MatrixEventBase[*event.BeeperAcceptMessageRequestEventContent]
-type MatrixBeeperAIStream = MatrixEventBase[*event.BeeperAIStreamEventContent]
type MatrixMarkedUnread = MatrixRoomMeta[*event.MarkedUnreadEventContent]
type MatrixMute = MatrixRoomMeta[*event.BeeperMuteEventContent]
type MatrixRoomTag = MatrixRoomMeta[*event.TagEventContent]
diff --git a/bridgev2/portal.go b/bridgev2/portal.go
index 5ba29507..b72f00a6 100644
--- a/bridgev2/portal.go
+++ b/bridgev2/portal.go
@@ -169,9 +169,7 @@ func (br *Bridge) loadPortal(ctx context.Context, dbPortal *database.Portal, que
}
func (portal *Portal) updateLogger() {
- logWith := portal.Bridge.Log.With().
- Str("portal_id", string(portal.ID)).
- Str("portal_receiver", string(portal.Receiver))
+ logWith := portal.Bridge.Log.With().Str("portal_id", string(portal.ID))
if portal.MXID != "" {
logWith = logWith.Stringer("portal_mxid", portal.MXID)
}
@@ -448,23 +446,6 @@ func (portal *Portal) handleSingleEventWithDelayLogging(idx int, rawEvt any) (ou
return
}
-type contextKey int
-
-const (
- contextKeyRemoteEvent contextKey = iota
- contextKeyMatrixEvent
-)
-
-func GetMatrixEventFromContext(ctx context.Context) (evt *event.Event) {
- evt, _ = ctx.Value(contextKeyMatrixEvent).(*event.Event)
- return
-}
-
-func GetRemoteEventFromContext(ctx context.Context) (evt RemoteEvent) {
- evt, _ = ctx.Value(contextKeyRemoteEvent).(RemoteEvent)
- return
-}
-
func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
var logWith zerolog.Context
switch evt := rawEvt.(type) {
@@ -478,10 +459,6 @@ func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
Stringer("event_id", evt.evt.ID).
Stringer("sender", evt.sender.MXID)
}
- ctx := portal.Bridge.BackgroundCtx
- ctx = context.WithValue(ctx, contextKeyMatrixEvent, evt.evt)
- ctx = logWith.Logger().WithContext(ctx)
- return ctx
case *portalRemoteEvent:
evt.evtType = evt.evt.GetType()
logWith = portal.Log.With().Int("event_loop_index", idx).
@@ -507,23 +484,10 @@ func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
logWith = logWith.Int64("remote_stream_order", remoteStreamOrder)
}
}
- if remoteMsg, ok := evt.evt.(RemoteEventWithTimestamp); ok {
- if remoteTimestamp := remoteMsg.GetTimestamp(); !remoteTimestamp.IsZero() {
- logWith = logWith.Time("remote_timestamp", remoteTimestamp)
- }
- }
- ctx := portal.Bridge.BackgroundCtx
- ctx = context.WithValue(ctx, contextKeyRemoteEvent, evt.evt)
- ctx = logWith.Logger().WithContext(ctx)
- if ctxMut, ok := evt.evt.(RemoteEventWithContextMutation); ok {
- ctx = ctxMut.MutateContext(ctx)
- }
- return ctx
case *portalCreateEvent:
return evt.ctx
- default:
- panic(fmt.Errorf("invalid type %T in getEventCtxWithLog", evt))
}
+ return logWith.Logger().WithContext(portal.Bridge.BackgroundCtx)
}
func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCallback func(res EventHandlingResult)) {
@@ -728,8 +692,6 @@ func (portal *Portal) handleMatrixEvent(ctx context.Context, sender *User, evt *
return portal.handleMatrixReceipts(ctx, evt)
case event.EphemeralEventTyping:
return portal.handleMatrixTyping(ctx, evt)
- case event.BeeperEphemeralEventAIStream:
- return portal.handleMatrixAIStream(ctx, sender, evt)
default:
return EventHandlingResultIgnored
}
@@ -974,50 +936,6 @@ func (portal *Portal) handleMatrixTyping(ctx context.Context, evt *event.Event)
return EventHandlingResultSuccess
}
-func (portal *Portal) handleMatrixAIStream(ctx context.Context, sender *User, evt *event.Event) EventHandlingResult {
- log := zerolog.Ctx(ctx)
- if sender == nil {
- log.Error().Msg("Missing sender for Matrix AI stream event")
- return EventHandlingResultIgnored
- }
- login, _, err := portal.FindPreferredLogin(ctx, sender, true)
- if err != nil {
- log.Err(err).Msg("Failed to get user login to handle Matrix AI stream event")
- return EventHandlingResultFailed.WithMSSError(err)
- }
- var origSender *OrigSender
- if login == nil {
- if portal.Relay == nil {
- return EventHandlingResultIgnored
- }
- login = portal.Relay
- origSender = &OrigSender{
- User: sender,
- UserID: sender.MXID,
- }
- }
- content, ok := evt.Content.Parsed.(*event.BeeperAIStreamEventContent)
- if !ok {
- log.Error().Type("content_type", evt.Content.Parsed).Msg("Unexpected parsed content type")
- return EventHandlingResultFailed.WithMSSError(fmt.Errorf("%w: %T", ErrUnexpectedParsedContentType, evt.Content.Parsed))
- }
- api, ok := login.Client.(BeeperAIStreamHandlingNetworkAPI)
- if !ok {
- return EventHandlingResultIgnored.WithMSSError(ErrBeeperAIStreamNotSupported)
- }
- err = api.HandleMatrixBeeperAIStream(ctx, &MatrixBeeperAIStream{
- Event: evt,
- Content: content,
- Portal: portal,
- OrigSender: origSender,
- })
- if err != nil {
- log.Err(err).Msg("Failed to handle Matrix AI stream event")
- return EventHandlingResultFailed.WithMSSError(err)
- }
- return EventHandlingResultSuccess.WithMSS()
-}
-
func (portal *Portal) sendTypings(ctx context.Context, userIDs []id.UserID, typing bool) {
for _, userID := range userIDs {
login, ok := portal.currentlyTypingLogins[userID]
@@ -1618,12 +1536,6 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
if portal.Bridge.Config.OutgoingMessageReID {
deterministicID = portal.Bridge.Matrix.GenerateReactionEventID(portal.MXID, reactionTarget, preResp.SenderID, preResp.EmojiID)
}
- defer func() {
- // Do this in a defer so that it happens after any potential defer calls to removeOutdatedReaction
- if handleRes.Success {
- portal.sendSuccessStatus(ctx, evt, 0, deterministicID)
- }
- }()
removeOutdatedReaction := func(oldReact *database.Reaction, deleteDB bool) {
if !handleRes.Success {
return
@@ -1669,10 +1581,6 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
// Keep n-1 previous reactions and remove the rest
react.ExistingReactionsToKeep = allReactions[:preResp.MaxReactions-1]
for _, oldReaction := range allReactions[preResp.MaxReactions-1:] {
- if existing != nil && oldReaction.EmojiID == existing.EmojiID {
- // Don't double-delete on networks that only allow one emoji
- continue
- }
// Intentionally defer in a loop, there won't be that many items,
// and we want all of them to be done after this function completes successfully
//goland:noinspection GoDeferInLoop
@@ -1721,6 +1629,7 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
if err != nil {
log.Err(err).Msg("Failed to save reaction to database")
}
+ portal.sendSuccessStatus(ctx, evt, 0, deterministicID)
return EventHandlingResultSuccess.WithEventID(deterministicID)
}
@@ -2792,7 +2701,7 @@ func (portal *Portal) getRelationMeta(
log.Err(err).Msg("Failed to get last thread message from database")
}
if prevThreadEvent == nil {
- prevThreadEvent = ptr.Clone(threadRoot)
+ prevThreadEvent = threadRoot
}
}
return
@@ -5403,9 +5312,6 @@ func (portal *Portal) removeInPortalCache(ctx context.Context) {
}
func (portal *Portal) unlockedDelete(ctx context.Context) error {
- if portal.deleted.IsSet() {
- return nil
- }
err := portal.safeDBDelete(ctx)
if err != nil {
return err
diff --git a/bridgev2/portalreid.go b/bridgev2/portalreid.go
index c976d97c..6a5091fc 100644
--- a/bridgev2/portalreid.go
+++ b/bridgev2/portalreid.go
@@ -38,20 +38,17 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
Stringer("target_portal_key", target).
Logger()
ctx = log.WithContext(ctx)
+ if !br.cacheLock.TryLock() {
+ log.Debug().Msg("Waiting for cache lock")
+ br.cacheLock.Lock()
+ log.Debug().Msg("Acquired cache lock after waiting")
+ }
defer func() {
+ br.cacheLock.Unlock()
log.Debug().Msg("Finished handling portal re-ID")
}()
- acquireCacheLock := func() {
- if !br.cacheLock.TryLock() {
- log.Debug().Msg("Waiting for global cache lock")
- br.cacheLock.Lock()
- log.Debug().Msg("Acquired global cache lock after waiting")
- } else {
- log.Trace().Msg("Acquired global cache lock without waiting")
- }
- }
log.Debug().Msg("Re-ID'ing portal")
- sourcePortal, err := br.GetExistingPortalByKey(ctx, source)
+ sourcePortal, err := br.UnlockedGetPortalByKey(ctx, source, true)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to get source portal: %w", err)
} else if sourcePortal == nil {
@@ -78,24 +75,18 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Stringer("source_portal_mxid", sourcePortal.MXID)
})
-
- acquireCacheLock()
targetPortal, err := br.UnlockedGetPortalByKey(ctx, target, true)
if err != nil {
- br.cacheLock.Unlock()
return ReIDResultError, nil, fmt.Errorf("failed to get target portal: %w", err)
}
if targetPortal == nil {
log.Info().Msg("Target portal doesn't exist, re-ID'ing source portal")
err = sourcePortal.unlockedReID(ctx, target)
- br.cacheLock.Unlock()
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to re-ID source portal: %w", err)
}
return ReIDResultSourceReIDd, sourcePortal, nil
}
- br.cacheLock.Unlock()
-
if !targetPortal.roomCreateLock.TryLock() {
if cancelCreate := targetPortal.cancelRoomCreate.Swap(nil); cancelCreate != nil {
(*cancelCreate)()
@@ -107,8 +98,6 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
defer targetPortal.roomCreateLock.Unlock()
if targetPortal.MXID == "" {
log.Info().Msg("Target portal row exists, but doesn't have a Matrix room. Deleting target portal row and re-ID'ing source portal")
- acquireCacheLock()
- defer br.cacheLock.Unlock()
err = targetPortal.unlockedDelete(ctx)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to delete target portal: %w", err)
@@ -123,9 +112,6 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
return c.Stringer("target_portal_mxid", targetPortal.MXID)
})
log.Info().Msg("Both target and source portals have Matrix rooms, tombstoning source portal")
- sourcePortal.removeInPortalCache(ctx)
- acquireCacheLock()
- defer br.cacheLock.Unlock()
err = sourcePortal.unlockedDelete(ctx)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to delete source portal row: %w", err)
diff --git a/bridgev2/provisionutil/creategroup.go b/bridgev2/provisionutil/creategroup.go
index 72bacaff..fbe0a513 100644
--- a/bridgev2/provisionutil/creategroup.go
+++ b/bridgev2/provisionutil/creategroup.go
@@ -32,9 +32,6 @@ func CreateGroup(ctx context.Context, login *bridgev2.UserLogin, params *bridgev
if !ok {
return nil, bridgev2.RespError(mautrix.MUnrecognized.WithMessage("This bridge does not support creating groups"))
}
- zerolog.Ctx(ctx).Debug().
- Any("create_params", params).
- Msg("Creating group chat on remote network")
caps := login.Bridge.Network.GetCapabilities()
typeSpec, validType := caps.Provisioning.GroupCreation[params.Type]
if !validType {
@@ -101,9 +98,6 @@ func CreateGroup(ctx context.Context, login *bridgev2.UserLogin, params *bridgev
if resp.PortalKey.IsEmpty() {
return nil, ErrNoPortalKey
}
- zerolog.Ctx(ctx).Debug().
- Object("portal_key", resp.PortalKey).
- Msg("Successfully created group on remote network")
if resp.Portal == nil {
resp.Portal, err = login.Bridge.GetPortalByKey(ctx, resp.PortalKey)
if err != nil {
diff --git a/bridgev2/simplevent/meta.go b/bridgev2/simplevent/meta.go
index 96c8a9c5..449a8773 100644
--- a/bridgev2/simplevent/meta.go
+++ b/bridgev2/simplevent/meta.go
@@ -27,9 +27,8 @@ type EventMeta struct {
Timestamp time.Time
StreamOrder int64
- PreHandleFunc func(context.Context, *bridgev2.Portal)
- PostHandleFunc func(context.Context, *bridgev2.Portal)
- MutateContextFunc func(context.Context) context.Context
+ PreHandleFunc func(context.Context, *bridgev2.Portal)
+ PostHandleFunc func(context.Context, *bridgev2.Portal)
}
var (
@@ -40,7 +39,6 @@ var (
_ bridgev2.RemoteEventWithStreamOrder = (*EventMeta)(nil)
_ bridgev2.RemotePreHandler = (*EventMeta)(nil)
_ bridgev2.RemotePostHandler = (*EventMeta)(nil)
- _ bridgev2.RemoteEventWithContextMutation = (*EventMeta)(nil)
)
func (evt *EventMeta) AddLogContext(c zerolog.Context) zerolog.Context {
@@ -93,13 +91,6 @@ func (evt *EventMeta) PostHandle(ctx context.Context, portal *bridgev2.Portal) {
}
}
-func (evt *EventMeta) MutateContext(ctx context.Context) context.Context {
- if evt.MutateContextFunc == nil {
- return ctx
- }
- return evt.MutateContextFunc(ctx)
-}
-
func (evt EventMeta) WithType(t bridgev2.RemoteEventType) EventMeta {
evt.Type = t
return evt
diff --git a/bridgev2/status/bridgestate.go b/bridgev2/status/bridgestate.go
index 5925dd4f..430d4c7c 100644
--- a/bridgev2/status/bridgestate.go
+++ b/bridgev2/status/bridgestate.go
@@ -19,6 +19,7 @@ import (
"github.com/tidwall/sjson"
"go.mau.fi/util/jsontime"
+ "go.mau.fi/util/ptr"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/bridgev2/networkid"
@@ -111,7 +112,7 @@ func (rp *RemoteProfile) Merge(other RemoteProfile) RemoteProfile {
return other
}
-func (rp *RemoteProfile) IsZero() bool {
+func (rp *RemoteProfile) IsEmpty() bool {
return rp == nil || (rp.Phone == "" && rp.Email == "" && rp.Username == "" && rp.Name == "" && rp.Avatar == "" && rp.AvatarFile == nil)
}
@@ -129,7 +130,7 @@ type BridgeState struct {
UserID id.UserID `json:"user_id,omitempty"`
RemoteID networkid.UserLoginID `json:"remote_id,omitempty"`
RemoteName string `json:"remote_name,omitempty"`
- RemoteProfile RemoteProfile `json:"remote_profile,omitzero"`
+ RemoteProfile *RemoteProfile `json:"remote_profile,omitempty"`
Reason string `json:"reason,omitempty"`
Info map[string]interface{} `json:"info,omitempty"`
@@ -209,7 +210,7 @@ func (pong *BridgeState) ShouldDeduplicate(newPong *BridgeState) bool {
pong.StateEvent == newPong.StateEvent &&
pong.RemoteName == newPong.RemoteName &&
pong.UserAction == newPong.UserAction &&
- pong.RemoteProfile == newPong.RemoteProfile &&
+ ptr.Val(pong.RemoteProfile) == ptr.Val(newPong.RemoteProfile) &&
pong.Error == newPong.Error &&
maps.EqualFunc(pong.Info, newPong.Info, reflect.DeepEqual) &&
pong.Timestamp.Add(time.Duration(pong.TTL)*time.Second).After(time.Now())
diff --git a/bridgev2/userlogin.go b/bridgev2/userlogin.go
index d56dc4cc..35443025 100644
--- a/bridgev2/userlogin.go
+++ b/bridgev2/userlogin.go
@@ -512,7 +512,7 @@ func (ul *UserLogin) FillBridgeState(state status.BridgeState) status.BridgeStat
state.UserID = ul.UserMXID
state.RemoteID = ul.ID
state.RemoteName = ul.RemoteName
- state.RemoteProfile = ul.RemoteProfile
+ state.RemoteProfile = &ul.RemoteProfile
filler, ok := ul.Client.(status.BridgeStateFiller)
if ok {
return filler.FillBridgeState(state)
diff --git a/client.go b/client.go
index 045d7b8e..2503556a 100644
--- a/client.go
+++ b/client.go
@@ -386,14 +386,7 @@ func (cli *Client) LogRequestDone(req *http.Request, resp *http.Response, err er
}
}
if body := req.Context().Value(LogBodyContextKey); body != nil {
- switch typedLogBody := body.(type) {
- case json.RawMessage:
- evt.RawJSON("req_body", typedLogBody)
- case string:
- evt.Str("req_body", typedLogBody)
- default:
- panic(fmt.Errorf("invalid type for LogBodyContextKey: %T", body))
- }
+ evt.Interface("req_body", body)
}
if errors.Is(err, context.Canceled) {
evt.Msg("Request canceled")
@@ -457,10 +450,8 @@ func (params *FullRequest) compileRequest(ctx context.Context) (*http.Request, e
}
if params.SensitiveContent && !logSensitiveContent {
logBody = "