diff --git a/connector/connector.go b/connector/connector.go new file mode 100644 index 00000000..9667ae50 --- /dev/null +++ b/connector/connector.go @@ -0,0 +1,440 @@ +// mautrix-signal - A Matrix-Signal puppeting bridge. +// Copyright (C) 2024 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 . + +package connector + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/rs/zerolog" + "go.mau.fi/util/dbutil" + "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + + legacydb "go.mau.fi/mautrix-signal/database" + "go.mau.fi/mautrix-signal/msgconv" + "go.mau.fi/mautrix-signal/msgconv/matrixfmt" + "go.mau.fi/mautrix-signal/msgconv/signalfmt" + "go.mau.fi/mautrix-signal/pkg/libsignalgo" + "go.mau.fi/mautrix-signal/pkg/signalmeow" + "go.mau.fi/mautrix-signal/pkg/signalmeow/events" + signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf" + "go.mau.fi/mautrix-signal/pkg/signalmeow/store" + "go.mau.fi/mautrix-signal/pkg/signalmeow/types" +) + +type SignalConnector struct { + MsgConv *msgconv.MessageConverter + Store *store.Container + Bridge *bridgev2.Bridge +} + +func NewConnector() *SignalConnector { + return &SignalConnector{} +} + +func (s *SignalConnector) Init(bridge *bridgev2.Bridge) { + s.Store = store.NewStore(bridge.DB.Database, dbutil.ZeroLogger(bridge.Log.With().Str("db_section", "signalmeow").Logger())) + s.Bridge = bridge + s.MsgConv = &msgconv.MessageConverter{ + PortalMethods: &msgconvPortalMethods{}, + SignalFmtParams: &signalfmt.FormatParams{ + GetUserInfo: func(ctx context.Context, uuid uuid.UUID) signalfmt.UserInfo { + ghost, err := s.Bridge.GetGhostByID(ctx, makeUserID(uuid)) + if err != nil { + // TODO log? + return signalfmt.UserInfo{} + } + userInfo := signalfmt.UserInfo{ + MXID: ghost.MXID, + Name: ghost.Name, + } + userLogin := s.Bridge.GetCachedUserLoginByID(networkid.UserLoginID(uuid.String())) + if userLogin != nil { + userInfo.MXID = userLogin.UserMXID + // TODO find matrix user displayname? + } + return userInfo + }, + }, + MatrixFmtParams: &matrixfmt.HTMLParser{ + GetUUIDFromMXID: func(ctx context.Context, userID id.UserID) uuid.UUID { + parsed, ok := s.Bridge.Matrix.ParseGhostMXID(userID) + if ok { + u, _ := uuid.Parse(string(parsed)) + return u + } + user, _ := s.Bridge.GetExistingUserByMXID(ctx, userID) + // TODO log errors? + if user != nil { + preferredLogin, _ := ctx.Value(msgconvContextKey).(*msgconvContext).Portal.FindPreferredLogin(ctx, user) + if preferredLogin != nil { + u, _ := uuid.Parse(string(preferredLogin.ID)) + return u + } + } + return uuid.Nil + }, + }, + ConvertVoiceMessages: true, + ConvertGIFToAPNG: true, + MaxFileSize: 100 * 1024 * 1024, + AsyncFiles: true, + LocationFormat: "", + } +} + +func (s *SignalConnector) Start(ctx context.Context) error { + return s.Store.Upgrade(ctx) +} + +var _ bridgev2.NetworkConnector = (*SignalConnector)(nil) +var _ bridgev2.NetworkAPI = (*SignalClient)(nil) +var _ msgconv.PortalMethods = (*msgconvPortalMethods)(nil) + +func (s *SignalConnector) PrepareLogin(ctx context.Context, login *bridgev2.UserLogin) error { + aci, err := uuid.Parse(string(login.ID)) + if err != nil { + return fmt.Errorf("failed to parse user login ID: %w", err) + } + device, err := s.Store.DeviceByACI(ctx, aci) + if err != nil { + return fmt.Errorf("failed to get device from store: %w", err) + } else if device == nil { + return fmt.Errorf("%w: device not found in store", bridgev2.ErrNotLoggedIn) + } + sc := &SignalClient{ + Main: s, + UserLogin: login, + Client: &signalmeow.Client{ + Store: device, + }, + } + sc.Client.EventHandler = sc.handleSignalEvent + login.Client = sc + return nil +} + +type SignalClient struct { + Main *SignalConnector + UserLogin *bridgev2.UserLogin + Client *signalmeow.Client +} + +func (s *SignalClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.PortalInfo, error) { + return &bridgev2.PortalInfo{}, nil +} + +func (s *SignalClient) Connect(ctx context.Context) error { + _, err := s.Client.StartReceiveLoops(ctx) + if err != nil { + return err + } + // TODO status + return nil +} + +func (s *SignalClient) IsLoggedIn() bool { + return s.Client.IsLoggedIn() +} + +func (s *SignalClient) parsePortalID(portalID networkid.PortalID) (string, error) { + parts := strings.Split(string(portalID), "|") + if len(parts) == 1 { + if len(parts[0]) == 44 { + return parts[0], nil + } + return "", fmt.Errorf("invalid portal ID: expected group ID to be 44 characters") + } else if len(parts) == 2 { + ourACI := s.Client.Store.ACI.String() + if parts[0] == ourACI { + return parts[1], nil + } else if parts[1] == ourACI { + return parts[0], nil + } else { + return "", fmt.Errorf("invalid portal ID: expected one side to be our ACI") + } + } + return "", fmt.Errorf("invalid portal ID: unexpected number of pipe-separated parts") +} + +func (s *SignalClient) getPortalID(chatID string) networkid.PortalID { + if len(chatID) == 44 { + // Group ID + return networkid.PortalID(chatID) + } else if strings.HasPrefix(chatID, "PNI:") { + // Temporary new DM ID: always put our own ACI first, the portal will never be shared anyway + return networkid.PortalID(fmt.Sprintf("%s|%s", s.Client.Store.ACI, chatID)) + } else { + // DM ID: sort the two parts so the ID is always the same regardless of which side is receiving the message + parts := []string{s.Client.Store.ACI.String(), chatID} + slices.Sort(parts) + return networkid.PortalID(strings.Join(parts, "|")) + } +} + +func makeMessageID(sender uuid.UUID, timestamp uint64) networkid.MessageID { + return networkid.MessageID(fmt.Sprintf("%s|%d", sender, timestamp)) +} + +func makeUserID(user uuid.UUID) networkid.UserID { + return networkid.UserID(user.String()) +} + +func makeUserLoginID(user uuid.UUID) networkid.UserLoginID { + return networkid.UserLoginID(user.String()) +} + +func (s *SignalClient) makeEventSender(sender uuid.UUID) bridgev2.EventSender { + return bridgev2.EventSender{ + IsFromMe: sender == s.Client.Store.ACI, + SenderLogin: makeUserLoginID(sender), + Sender: makeUserID(sender), + } +} + +func makeMessagePartID(index int) networkid.PartID { + if index == 0 { + return "" + } + return networkid.PartID(strconv.Itoa(index)) +} + +type contextKey int + +var msgconvContextKey contextKey + +type msgconvContext struct { + Connector *SignalConnector + Intent bridgev2.MatrixAPI + Client *SignalClient + Portal *bridgev2.Portal + ReplyTo *database.Message +} + +func (s *SignalClient) convertMessage(ctx context.Context, portal *bridgev2.Portal, data *events.ChatEvent) (*bridgev2.ConvertedMessage, error) { + dataMsg := data.Event.(*signalpb.DataMessage) + converted := s.Main.MsgConv.ToMatrix(ctx, dataMsg) + var replyTo *networkid.MessageOptionalPartID + if dataMsg.GetQuote() != nil { + quoteAuthor, _ := uuid.Parse(dataMsg.Quote.GetAuthorAci()) + replyTo = &networkid.MessageOptionalPartID{ + MessageID: makeMessageID(quoteAuthor, dataMsg.Quote.GetId()), + } + } + convertedParts := make([]*bridgev2.ConvertedMessagePart, len(converted.Parts)) + for i, part := range converted.Parts { + convertedParts[i] = &bridgev2.ConvertedMessagePart{ + ID: makeMessagePartID(i), + Type: part.Type, + Content: part.Content, + Extra: part.Extra, + } + + } + return &bridgev2.ConvertedMessage{ + ID: makeMessageID(data.Info.Sender, dataMsg.GetTimestamp()), + EventSender: s.makeEventSender(data.Info.Sender), + Timestamp: time.UnixMilli(int64(converted.Timestamp)), + ReplyTo: replyTo, + Parts: convertedParts, + }, nil +} + +func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) { + switch evt := rawEvt.(type) { + case *events.ChatEvent: + switch innerEvt := evt.Event.(type) { + case *signalpb.DataMessage: + s.Main.Bridge.QueueRemoteEvent(s.UserLogin, &bridgev2.SimpleRemoteEvent[*events.ChatEvent]{ + Type: bridgev2.RemoteEventMessage, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Uint64("message_id", innerEvt.GetTimestamp()). + Stringer("sender_id", evt.Info.Sender) + }, + PortalID: s.getPortalID(evt.Info.ChatID), + Data: evt, + CreatePortal: true, + + ConvertMessageFunc: s.convertMessage, + }) + case *signalpb.EditMessage: + case *signalpb.TypingMessage: + } + case *events.DecryptionError: + case *events.Receipt: + case *events.ReadSelf: + case *events.Call: + case *events.ContactList: + case *events.ACIFound: + } +} + +func (s *SignalClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (message *database.Message, err error) { + mcCtx := &msgconvContext{ + Connector: s.Main, + Intent: nil, + Client: s, + Portal: msg.Portal, + ReplyTo: msg.ReplyTo, + } + ctx = context.WithValue(ctx, msgconvContextKey, mcCtx) + chatID, err := s.parsePortalID(msg.Portal.ID) + if err != nil { + return nil, err + } + var userID libsignalgo.ServiceID + var groupID types.GroupIdentifier + if len(chatID) == 44 { + groupID = types.GroupIdentifier(chatID) + } else { + userID, err = libsignalgo.ServiceIDFromString(chatID) + if err != nil { + return nil, err + } + } + converted, err := s.Main.MsgConv.ToSignal(ctx, msg.Event, msg.Content, msg.OrigSender != nil) + if err != nil { + return nil, err + } + wrappedContent := &signalpb.Content{ + DataMessage: converted, + } + if groupID != "" { + res, err := s.Client.SendGroupMessage(ctx, groupID, wrappedContent) + if err != nil { + return nil, err + } + // TODO check result + fmt.Println(res) + } else { + res := s.Client.SendMessage(ctx, userID, wrappedContent) + // TODO check result + fmt.Println(res) + } + meta := map[string]any{ + "reply_to_file": len(converted.Attachments) > 0, + } + dbMsg := &database.Message{ + ID: makeMessageID(s.Client.Store.ACI, converted.GetTimestamp()), + MXID: msg.Event.ID, + RoomID: msg.Portal.ID, + SenderID: makeUserID(s.Client.Store.ACI), + Timestamp: time.UnixMilli(int64(converted.GetTimestamp())), + Metadata: meta, + } + if msg.ReplyTo != nil { + dbMsg.RelatesToRowID = msg.ReplyTo.RowID + } + return dbMsg, nil +} + +func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.MatrixEdit) error { + //TODO implement me + panic("implement me") +} + +func (s *SignalClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (emojiID networkid.EmojiID, err error) { + //TODO implement me + panic("implement me") +} + +func (s *SignalClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error { + //TODO implement me + panic("implement me") +} + +func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error { + //TODO implement me + panic("implement me") +} + +type msgconvPortalMethods struct{} + +func (mpm *msgconvPortalMethods) UploadMatrixMedia(ctx context.Context, data []byte, fileName, contentType string) (id.ContentURIString, error) { + mcCtx := ctx.Value(msgconvContextKey).(*msgconvContext) + uri, _, err := mcCtx.Intent.UploadMedia(ctx, "", data, fileName, contentType) + return uri, err +} + +func (mpm *msgconvPortalMethods) DownloadMatrixMedia(ctx context.Context, uri id.ContentURIString) ([]byte, error) { + return ctx.Value(msgconvContextKey).(*msgconvContext).Connector.Bridge.Bot.DownloadMedia(ctx, uri, nil) +} + +func (mpm *msgconvPortalMethods) GetMatrixReply(ctx context.Context, msg *signalpb.DataMessage_Quote) (replyTo id.EventID, replyTargetSender id.UserID) { + // Matrix replies are handled in bridgev2 code + return "", "" +} + +func (mpm *msgconvPortalMethods) GetSignalReply(ctx context.Context, content *event.MessageEventContent) *signalpb.DataMessage_Quote { + mcCtx := ctx.Value(msgconvContextKey).(*msgconvContext) + if mcCtx.ReplyTo == nil { + return nil + } + quote := &signalpb.DataMessage_Quote{ + Id: proto.Uint64(uint64(mcCtx.ReplyTo.Timestamp.UnixMilli())), + AuthorAci: proto.String(string(mcCtx.ReplyTo.SenderID)), + Type: signalpb.DataMessage_Quote_NORMAL.Enum(), + } + if mcCtx.ReplyTo.Metadata["reply_to_file"] != false { + quote.Attachments = make([]*signalpb.DataMessage_Quote_QuotedAttachment, 1) + } + return quote +} + +func (mpm *msgconvPortalMethods) GetClient(ctx context.Context) *signalmeow.Client { + return ctx.Value(msgconvContextKey).(*msgconvContext).Client.Client +} + +func (mpm *msgconvPortalMethods) GetData(ctx context.Context) *legacydb.Portal { + mcCtx := ctx.Value(msgconvContextKey).(*msgconvContext) + portal := mcCtx.Portal + chatID, _ := mcCtx.Client.parsePortalID(portal.ID) + pk := legacydb.PortalKey{ + ChatID: chatID, + } + if len(chatID) != 44 { + pk.Receiver = mcCtx.Client.Client.Store.ACI + } + return &legacydb.Portal{ + PortalKey: pk, + MXID: portal.MXID, + Name: portal.Name, + Topic: portal.Topic, + //AvatarPath: "", + //AvatarHash: "", + //AvatarURL: id.ContentURI{}, + NameSet: portal.NameSet, + AvatarSet: portal.AvatarSet, + TopicSet: portal.TopicSet, + //Revision: portal.Metadata["revision"].(uint32), + Encrypted: true, + //RelayUserID: portal.Relay.UserMXID, + //ExpirationTime: portal.Metadata["expiration_timer"].(uint32), + } +} diff --git a/connector/mautrix-signal-v2/main.go b/connector/mautrix-signal-v2/main.go new file mode 100644 index 00000000..929a9c1a --- /dev/null +++ b/connector/mautrix-signal-v2/main.go @@ -0,0 +1,207 @@ +// mautrix-signal - A Matrix-Signal puppeting bridge. +// Copyright (C) 2024 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 . + +package main + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/google/uuid" + "github.com/skip2/go-qrcode" + "go.mau.fi/util/dbutil" + "go.mau.fi/util/exerrors" + "go.mau.fi/util/exzerolog" + "gopkg.in/yaml.v3" + + "go.mau.fi/mautrix-signal/pkg/signalmeow" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/matrix" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + + "go.mau.fi/mautrix-signal/connector" +) + +func main() { + var cfg bridgeconfig.Config + config := exerrors.Must(os.ReadFile("config.yaml")) + exerrors.PanicIfNotNil(yaml.Unmarshal(config, &cfg)) + log := exerrors.Must(cfg.Logging.Compile()) + exzerolog.SetupDefaults(log) + db := exerrors.Must(dbutil.NewFromConfig("mautrix-signal", cfg.Database, dbutil.ZeroLogger(log.With().Str("db_section", "main").Logger()))) + bridge := bridgev2.NewBridge("", db, *log, matrix.NewConnector(&cfg), connector.NewConnector()) + bridge.CommandPrefix = "!signal" + bridge.Commands.AddHandlers(&bridgev2.FullHandler{ + Func: fnLogin, + Name: "login", + Help: bridgev2.HelpMeta{ + Section: bridgev2.HelpSectionAuth, + Description: "Log into Signal", + }, + }) + bridge.Start() +} + +func sendQR(ce *bridgev2.CommandEvent, code string, prevQR, prevMsg id.EventID) (qr, msg id.EventID) { + content, ok := uploadQR(ce, code) + if !ok { + return prevQR, prevMsg + } + if len(prevQR) != 0 { + content.SetEdit(prevQR) + } + resp, err := ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: &content}, time.Now()) + if err != nil { + ce.Log.Err(err).Msg("Failed to send QR code to user") + } else if len(prevQR) == 0 { + prevQR = resp.EventID + } + content = event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: fmt.Sprintf("Raw linking URI: %s", code), + Format: event.FormatHTML, + FormattedBody: fmt.Sprintf("Raw linking URI: %s", code), + } + if len(prevMsg) != 0 { + content.SetEdit(prevMsg) + } + resp, err = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: &content}, time.Now()) + if err != nil { + ce.Log.Err(err).Msg("Failed to send raw code to user") + } else if len(prevMsg) == 0 { + prevMsg = resp.EventID + } + return prevQR, prevMsg +} + +func uploadQR(ce *bridgev2.CommandEvent, code string) (event.MessageEventContent, bool) { + const size = 512 + qrCode, err := qrcode.Encode(code, qrcode.Low, size) + if err != nil { + ce.Log.Err(err).Msg("Failed to encode QR code") + ce.Reply("Failed to encode QR code: %v", err) + return event.MessageEventContent{}, false + } + + uri, file, err := ce.Bot.UploadMedia(ce.Ctx, ce.RoomID, qrCode, "qr.png", "image/png") + if err != nil { + ce.Log.Err(err).Msg("Failed to upload QR code") + ce.Reply("Failed to upload QR code: %v", err) + return event.MessageEventContent{}, false + } + return event.MessageEventContent{ + MsgType: event.MsgImage, + Info: &event.FileInfo{ + MimeType: "image/png", + Width: size, + Height: size, + Size: len(qrCode), + }, + Body: "qr.png", + URL: uri, + File: file, + }, true +} +func fnLogin(ce *bridgev2.CommandEvent) { + signal := ce.Bridge.Network.(*connector.SignalConnector) + // TODO configurable device name + provChan := signalmeow.PerformProvisioning(ce.Ctx, signal.Store, "Mautrix-Signal Megabridge") + + resp := <-provChan + if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { + ce.Reply("Error getting provisioning URL: %v", resp.Err) + return + } + var qrEventID, msgEventID id.EventID + if resp.State == signalmeow.StateProvisioningURLReceived { + qrEventID, msgEventID = sendQR(ce, resp.ProvisioningURL, qrEventID, msgEventID) + } else { + ce.Reply("Unexpected state: %v", resp.State) + return + } + + // Next, get the results of finishing registration + resp = <-provChan + _, _ = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventRedaction, &event.Content{ + Parsed: &event.RedactionEventContent{ + Redacts: qrEventID, + }, + }, time.Now()) + _, _ = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventRedaction, &event.Content{ + Parsed: &event.RedactionEventContent{ + Redacts: msgEventID, + }, + }, time.Now()) + if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { + if resp.Err != nil && strings.HasSuffix(resp.Err.Error(), " EOF") { + ce.Reply("Logging in timed out, please try again.") + } else { + ce.Reply("Error finishing registration: %v", resp.Err) + } + return + } + var signalID uuid.UUID + var signalPhone string + if resp.State == signalmeow.StateProvisioningDataReceived { + signalID = resp.ProvisioningData.ACI + signalPhone = resp.ProvisioningData.Number + } else { + ce.Reply("Unexpected state: %v", resp.State) + return + } + + // Finally, get the results of generating and registering prekeys + resp = <-provChan + if resp.Err != nil || resp.State == signalmeow.StateProvisioningError { + ce.Reply("Error with prekeys: %v", resp.Err) + return + } else if resp.State != signalmeow.StateProvisioningPreKeysRegistered { + ce.Reply("Unexpected state: %v", resp.State) + return + } + + if signalID == uuid.Nil { + ce.Reply("Problem logging in - No SignalID received") + return + } + ul, err := ce.User.NewLogin(ce.Ctx, &database.UserLogin{ + ID: networkid.UserLoginID(signalID.String()), + Metadata: map[string]any{ + "phone": signalPhone, + }, + }, nil) + if err != nil { + ce.Reply("Failed to save new login: %v", err) + return + } + err = ce.Bridge.Network.PrepareLogin(ce.Ctx, ul) + if err != nil { + ce.Reply("Failed to prepare connection after login: %v", err) + return + } + err = ul.Client.Connect(ce.Ctx) + if err != nil { + ce.Reply("Failed to connect after login: %v", err) + return + } + ce.Reply("Successfully logged in as %s (UUID: %s)", signalPhone, signalID) +} diff --git a/connector/mautrix-signal-v2/start b/connector/mautrix-signal-v2/start new file mode 100755 index 00000000..85c97d10 --- /dev/null +++ b/connector/mautrix-signal-v2/start @@ -0,0 +1,6 @@ +#!/bin/bash +export LIBRARY_PATH=$(dirname $(dirname $(pwd))):$LIBRARY_PATH +export MAUTRIX_VERSION=$(cat ../../go.mod | grep 'maunium.net/go/mautrix ' | head -n1 | awk '{ print $2 }') +go install -ldflags "-X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date '+%b %_d %Y, %H:%M:%S'`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'" || exit 2 +mkdir -p logs +mautrix-signal-v2 "$@" diff --git a/go.mod b/go.mod index ac97321c..62f65063 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,13 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.1 - go.mau.fi/util v0.4.2 + go.mau.fi/util v0.4.3-0.20240430151139-91c8483608d4 golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 golang.org/x/net v0.24.0 google.golang.org/protobuf v1.33.0 - maunium.net/go/mautrix v0.18.1 + gopkg.in/yaml.v3 v3.0.1 + maunium.net/go/mautrix v0.18.2-0.20240513130344-1b56af00b0cd nhooyr.io/websocket v1.8.11 ) @@ -45,6 +46,5 @@ require ( go.mau.fi/zeroconfig v0.1.2 // indirect golang.org/x/sys v0.19.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index aaf41a79..87508354 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30= -go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= +go.mau.fi/util v0.4.3-0.20240430151139-91c8483608d4 h1:nNIMwMiqJmb18o4+OPDC946DNtds0sb1fbmaw0xsvPE= +go.mau.fi/util v0.4.3-0.20240430151139-91c8483608d4/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto= go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= @@ -95,7 +95,7 @@ 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.18.1 h1:a6mUsJixegBNTXUoqC5RQ9gsumIPzKvCubKwF+zmCt4= -maunium.net/go/mautrix v0.18.1/go.mod h1:2oHaq792cSXFGvxLvYw3Gf1L4WVVP4KZcYys5HVk/h8= +maunium.net/go/mautrix v0.18.2-0.20240513130344-1b56af00b0cd h1:Hos4XdspFRN20IOw0FzdPyHGnY6ynovqtVywRf9dfwA= +maunium.net/go/mautrix v0.18.2-0.20240513130344-1b56af00b0cd/go.mod h1:r3vg2vlvbT6nyB8GdSPgYb9C6HKo3ZW3OBEoOsTdK18= nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=