Skip to content

Commit

Permalink
Merge branch 'main' into tulir/db-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Dec 27, 2023
2 parents b453b08 + fb18dce commit 6d5f7ef
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 76 deletions.
6 changes: 5 additions & 1 deletion .envrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
if [[ $(uname -s) == "Linux" && $(uname --kernel-version | grep "NixOS") ]]; then
echo "The best OS (NixOS) has been detected. Using nice tools."
use nix
if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg="
fi

use flake
fi
47 changes: 47 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func (br *SignalBridge) RegisterCommands() {
cmdLogin,
cmdPM,
cmdDisconnect,
cmdSetRelay,
cmdUnsetRelay,
)
}

Expand All @@ -66,6 +68,51 @@ func wrapCommand(handler func(*WrappedCommandEvent)) func(*commands.Event) {
}
}

var cmdSetRelay = &commands.FullHandler{
Func: wrapCommand(fnSetRelay),
Name: "set-relay",
Help: commands.HelpMeta{
Section: HelpSectionPortalManagement,
Description: "Relay messages in this room through your Signal account.",
},
RequiresPortal: true,
RequiresLogin: true,
}

func fnSetRelay(ce *WrappedCommandEvent) {
if !ce.Bridge.Config.Bridge.Relay.Enabled {
ce.Reply("Relay mode is not enabled on this instance of the bridge")
} else if ce.Bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin {
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
} else {
ce.Portal.RelayUserID = ce.User.MXID
ce.Portal.Update(context.TODO())
ce.Reply("Messages from non-logged-in users in this room will now be bridged through your Signal account")
}
}

var cmdUnsetRelay = &commands.FullHandler{
Func: wrapCommand(fnUnsetRelay),
Name: "unset-relay",
Help: commands.HelpMeta{
Section: HelpSectionPortalManagement,
Description: "Stop relaying messages in this room.",
},
RequiresPortal: true,
}

func fnUnsetRelay(ce *WrappedCommandEvent) {
if !ce.Bridge.Config.Bridge.Relay.Enabled {
ce.Reply("Relay mode is not enabled on this instance of the bridge")
} else if ce.Bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin {
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
} else {
ce.Portal.RelayUserID = ""
ce.Portal.Update(context.TODO())
ce.Reply("Messages from non-logged-in users will no longer be bridged in this room")
}
}

var cmdDisconnect = &commands.FullHandler{
Func: wrapCommand(fnDisconnect),
Name: "disconnect",
Expand Down
58 changes: 58 additions & 0 deletions config/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"time"

"maunium.net/go/mautrix/bridge/bridgeconfig"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"

"go.mau.fi/mautrix-signal/pkg/signalmeow"
)
Expand Down Expand Up @@ -68,6 +70,8 @@ type BridgeConfig struct {

Permissions bridgeconfig.PermissionConfig `yaml:"permissions"`

Relay RelaybotConfig `yaml:"relay"`

usernameTemplate *template.Template `yaml:"-"`
displaynameTemplate *template.Template `yaml:"-"`
}
Expand Down Expand Up @@ -169,3 +173,57 @@ func (bc BridgeConfig) FormatDisplayname(contact *signalmeow.Contact) string {
})
return buffer.String()
}

type RelaybotConfig struct {
Enabled bool `yaml:"enabled"`
AdminOnly bool `yaml:"admin_only"`
MessageFormats map[event.MessageType]string `yaml:"message_formats"`
messageTemplates *template.Template `yaml:"-"`
}

type umRelaybotConfig RelaybotConfig

func (rc *RelaybotConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
err := unmarshal((*umRelaybotConfig)(rc))
if err != nil {
return err
}

rc.messageTemplates = template.New("messageTemplates")
for key, format := range rc.MessageFormats {
_, err := rc.messageTemplates.New(string(key)).Parse(format)
if err != nil {
return err
}
}

return nil
}

type Sender struct {
UserID string
event.MemberEventContent
}

type formatData struct {
Sender Sender
Message string
Content *event.MessageEventContent
}

func (rc *RelaybotConfig) FormatMessage(content *event.MessageEventContent, sender id.UserID, member event.MemberEventContent) (string, error) {
if len(member.Displayname) == 0 {
member.Displayname = sender.String()
}
member.Displayname = template.HTMLEscapeString(member.Displayname)
var output strings.Builder
err := rc.messageTemplates.ExecuteTemplate(&output, string(content.MsgType), formatData{
Sender: Sender{
UserID: template.HTMLEscapeString(sender.String()),
MemberEventContent: member,
},
Content: content,
Message: content.FormattedBody,
})
return output.String(), err
}
3 changes: 3 additions & 0 deletions config/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ func DoUpgrade(helper *up.Helper) {
helper.Copy(up.Bool, "bridge", "provisioning", "debug_endpoints")

helper.Copy(up.Map, "bridge", "permissions")
helper.Copy(up.Bool, "bridge", "relay", "enabled")
helper.Copy(up.Bool, "bridge", "relay", "admin_only")
helper.Copy(up.Map, "bridge", "relay", "message_formats")
}

var SpacedBlocks = [][]string{
Expand Down
18 changes: 18 additions & 0 deletions example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,24 @@ bridge:
"example.com": user
"@admin:example.com": admin

# Settings for relay mode
relay:
# Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any
# authenticated user into a relaybot for that chat.
enabled: false
# Should only admins be allowed to set themselves as relay users?
admin_only: true
# The formats to use when sending messages to Signal via the relaybot.
message_formats:
m.text: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.notice: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.emote: "* <b>{{ .Sender.Displayname }}</b> {{ .Message }}"
m.file: "<b>{{ .Sender.Displayname }}</b> sent a file"
m.image: "<b>{{ .Sender.Displayname }}</b> sent an image"
m.audio: "<b>{{ .Sender.Displayname }}</b> sent an audio file"
m.video: "<b>{{ .Sender.Displayname }}</b> sent a video"
m.location: "<b>{{ .Sender.Displayname }}</b> sent a location"

# Logging config. See https://github.com/tulir/zeroconfig for details.
logging:
min_level: debug
Expand Down
61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
description = "mautrix-signal development environment";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils }:
(flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pkgs.mkShell {
LIBCLANG_PATH = "${pkgs.llvmPackages_11.libclang.lib}/lib";

buildInputs = with pkgs; [
clang
cmake
gnumake
protobuf
rust-cbindgen
rustup
olm

go_1_20
go-tools
gotools

pre-commit
];
};
}));
}
7 changes: 4 additions & 3 deletions messagetracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
errUserNotConnected = errors.New("you are not connected to Signal")
errDifferentUser = errors.New("user is not the recipient of this private chat portal")
errUserNotLoggedIn = errors.New("user is not logged in and chat has no relay bot")
errRelaybotNotLoggedIn = errors.New("neither user nor relay bot of chat are logged in")
errMNoticeDisabled = errors.New("bridging m.notice messages is disabled")
errUnexpectedParsedContentType = errors.New("unexpected parsed content type")
errInvalidGeoURI = errors.New("invalid `geo:` URI in message")
Expand Down Expand Up @@ -96,7 +97,8 @@ func errorToStatusReason(err error) (reason event.MessageStatusReason, status ev
case errors.Is(err, errUserNotConnected):
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, true, ""
case errors.Is(err, errUserNotLoggedIn),
errors.Is(err, errDifferentUser):
errors.Is(err, errDifferentUser),
errors.Is(err, errRelaybotNotLoggedIn):
return event.MessageStatusGenericError, event.MessageStatusRetriable, true, false, ""
default:
return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, ""
Expand Down Expand Up @@ -267,8 +269,7 @@ func (mt *messageTimings) String() string {
mt.preproc = niceRound(mt.preproc)
mt.convert = niceRound(mt.convert)
mt.totalSend = niceRound(mt.totalSend)
whatsmeowTimings := "N/A"
return fmt.Sprintf("BRIDGE: receive: %s, decrypt: %s, queue: %s, total hs->portal: %s, implicit rr: %s -- PORTAL: preprocess: %s, convert: %s, total send: %s -- WHATSMEOW: %s", mt.initReceive, mt.decrypt, mt.implicitRR, mt.portalQueue, mt.totalReceive, mt.preproc, mt.convert, mt.totalSend, whatsmeowTimings)
return fmt.Sprintf("BRIDGE: receive: %s, decrypt: %s, queue: %s, total hs->portal: %s, implicit rr: %s -- PORTAL: preprocess: %s, convert: %s, total send: %s ", mt.initReceive, mt.decrypt, mt.implicitRR, mt.portalQueue, mt.totalReceive, mt.preproc, mt.convert, mt.totalSend)
}

type metricSender struct {
Expand Down
6 changes: 3 additions & 3 deletions pkg/signalmeow/receiving.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,9 +904,9 @@ func incomingDataMessage(ctx context.Context, device *Device, dataMessage *signa
Quote: quoteData,
ExpiresIn: expiresIn,
},
Width: *dataMessage.Sticker.Data.Width,
Height: *dataMessage.Sticker.Data.Height,
ContentType: *dataMessage.Sticker.Data.ContentType,
Width: dataMessage.Sticker.Data.GetWidth(),
Height: dataMessage.Sticker.Data.GetHeight(),
ContentType: dataMessage.Sticker.Data.GetContentType(),
Filename: dataMessage.Sticker.Data.GetFileName(),
Sticker: bytes,
Emoji: dataMessage.GetSticker().GetEmoji(),
Expand Down
10 changes: 7 additions & 3 deletions pkg/signalmeow/sending.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ func DataMessageForAttachment(attachmentPointer *AttachmentPointer, caption stri
}
if caption != "" {
ap.Caption = proto.String(caption)
dm.Body = proto.String(caption)
dm.BodyRanges = ranges
}
dm.Attachments = append(dm.Attachments, ap)
return wrapDataMessageInContent(dm)
Expand Down Expand Up @@ -462,13 +464,15 @@ func DataMessageForDelete(targetMessageTimestamp uint64) *SignalContent {
}

func AddQuoteToDataMessage(content *SignalContent, quotedMessageSender uuid.UUID, quotedMessageTimestamp uint64) {
// Note: We're supposed to send the quoted message content too as a fallback,
// but it only seems to be necessary to quote image messages on iOS and Desktop.
// Android seems to render every quote fine, and iOS and Desktop render text quotes fine.
content.DataMessage.Quote = &signalpb.DataMessage_Quote{
AuthorAci: proto.String(quotedMessageSender.String()),
Id: proto.Uint64(quotedMessageTimestamp),
Type: signalpb.DataMessage_Quote_NORMAL.Enum(),

// This is a hack to make Signal iOS and desktop render replies to file messages.
// Unfortunately it also makes Signal Desktop show a file icon on replies to text messages.
// TODO store file or text flag in database and fill this field only when replying to file messages.
Attachments: []*signalpb.DataMessage_Quote_QuotedAttachment{{}},
}
}

Expand Down
Loading

0 comments on commit 6d5f7ef

Please sign in to comment.