Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ type Bridger interface {
Disconnect() error
}

// additional methods for bridges that are not started when Connect() is called,
// because we must join the channels prior to starting processing events
type BridgerWithChannelDependency interface {
Start() error
}

type Bridge struct {
Bridger
*sync.RWMutex
Expand Down
12 changes: 9 additions & 3 deletions bridge/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ const (
EventAPIConnected = "api_connected"
EventUserTyping = "user_typing"
EventGetChannelMembers = "get_channel_members"
EventNoticeIRC = "notice_irc"
EventNotice = "notice"
)

const ParentIDNotFound = "msg-parent-not-found"

//nolint: tagliatelle
type Message struct {
Text string `json:"text"`
Channel string `json:"channel"`
Expand Down Expand Up @@ -146,11 +147,15 @@ type Protocol struct {
ReplaceNicks [][]string // all protocols
RemoteNickFormat string // all protocols
RunCommands []string // IRC
UseAppService bool // matrix
AppServiceHost string // matrix
AppServicePort uint16 // matrix
AppServiceConfigPath string // matrix
Server string // IRC,mattermost,XMPP,discord,matrix
SessionFile string // msteams,whatsapp
ShowJoinPart bool // all protocols
ShowTopicChange bool // slack
ShowUserTyping bool // slack
ShowUserTyping bool // discord, matrix, slack
ShowEmbeds bool // discord
SkipTLSVerify bool // IRC, mattermost
SkipVersionCheck bool // mattermost
Expand Down Expand Up @@ -269,7 +274,8 @@ func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
cfgtype := detectConfigType(cfgfile)
mycfg := newConfigFromString(logger, input, cfgtype)
if mycfg.cv.General.LogFile != "" {
logfile, err := os.OpenFile(mycfg.cv.General.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
//nolint:nosnakecase
logfile, err := os.OpenFile(mycfg.cv.General.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
if err == nil {
logger.Info("Opening log file ", mycfg.cv.General.LogFile)
rootLogger.Out = logfile
Expand Down
15 changes: 12 additions & 3 deletions bridge/discord/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,12 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
return b.handleEventWebhook(&msg, channelID)
}

return b.handleEventBotUser(&msg, channelID)
return b.handleEventBotUser(&msg, channelID, useWebhooks)
}

// handleEventDirect handles events via the bot user
func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (string, error) {
//nolint:funlen
func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string, webhookPreferred bool) (string, error) {
b.Log.Debugf("Broadcasting using token (API)")

// Delete message
Expand Down Expand Up @@ -336,8 +337,16 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st
return msg.ID, err
}

content := msg.Username + msg.Text
// we would have preferred to use webhooks but we can't (e.g. we are replying
// to a message, as webhooks doesn't support that behavior)
// in that case, the username is probably not properly formatted for appending
// directly, so we add a line return as a stopgap mechanism
if webhookPreferred {
content = msg.Username + ":\n" + msg.Text
}
m := discordgo.MessageSend{
Content: msg.Username + msg.Text,
Content: content,
AllowedMentions: b.getAllowedMentions(),
}

Expand Down
1 change: 1 addition & 0 deletions bridge/discord/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart)
return
}

//nolint:exhaustruct
rmsg := config.Message{Account: b.Account, Event: config.EventUserTyping}
rmsg.Channel = b.getChannelName(m.ChannelID)
b.Remote <- rmsg
Expand Down
2 changes: 1 addition & 1 deletion bridge/irc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {

// set NOTICE event
if event.Command == "NOTICE" {
rmsg.Event = config.EventNoticeIRC
rmsg.Event = config.EventNotice
}

// strip action, we made an event if it was an action
Expand Down
2 changes: 1 addition & 1 deletion bridge/irc/irc.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func (b *Birc) doSend() {
switch msg.Event {
case config.EventUserAction:
b.i.Cmd.Action(msg.Channel, username+msg.Text)
case config.EventNoticeIRC:
case config.EventNotice:
b.Log.Debugf("Sending notice to channel %s", msg.Channel)
b.i.Cmd.Notice(msg.Channel, username+msg.Text)
default:
Expand Down
149 changes: 149 additions & 0 deletions bridge/matrix/appservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package bmatrix

import (
"fmt"
"regexp"

"github.com/sirupsen/logrus"

"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)

type AppServiceNamespaces struct {
rooms []*regexp.Regexp
usernames []*regexp.Regexp
prefixes []string
}

type AppServiceWrapper struct {
appService *appservice.AppService
namespaces AppServiceNamespaces
stop chan struct{}
stopAck chan struct{}
}

func (w *AppServiceWrapper) ParseNamespaces(logger *logrus.Entry) error {
if w.appService.Registration != nil {
// TODO: handle non-exclusive registrations
for _, v := range w.appService.Registration.Namespaces.RoomIDs {
re, err := regexp.Compile(v.Regex)
if err != nil {
logger.Warnf("couldn't parse the appservice regex '%s'", v.Regex)
continue
}

w.namespaces.rooms = append(w.namespaces.rooms, re)
}

for _, v := range w.appService.Registration.Namespaces.UserIDs {
re, err := regexp.Compile(v.Regex)
if err != nil {
logger.Warnf("couldn't parse the appservice regex '%s'", v.Regex)
continue
}

// we assume that the user regexes will be of the form '@<some prefix>.*'
// where '.*' will be replaced by the username we spoof
prefix, _ := re.LiteralPrefix()
if prefix == "" || prefix == "@" {
logger.Warnf("couldn't find an acceptable prefix in the appservice regex '%s'", v.Regex)
continue
}

if v.Regex != fmt.Sprintf("%s.*", prefix) {
logger.Warnf("complex regexpes are not supported for appServices, the regexp '%s' does not match the format '@<prefix>.*'", v.Regex)
continue
}

w.namespaces.usernames = append(w.namespaces.usernames, re)
// drop the '@' in the prefix
w.namespaces.prefixes = append(w.namespaces.prefixes, prefix[1:])
}
}

return nil
}

func (b *Bmatrix) NewAppService() (*AppServiceWrapper, error) {
w := &AppServiceWrapper{
appService: appservice.Create(),
namespaces: AppServiceNamespaces{
rooms: []*regexp.Regexp{},
usernames: []*regexp.Regexp{},
prefixes: []string{},
},
stop: make(chan struct{}, 1),
stopAck: make(chan struct{}, 1),
}

err := w.appService.SetHomeserverURL(b.mc.HomeserverURL.String())
if err != nil {
return nil, err
}

_, homeServerDomain, _ := b.mc.UserID.Parse()
w.appService.HomeserverDomain = homeServerDomain
//nolint:exhaustruct
w.appService.Host = appservice.HostConfig{
Hostname: b.GetString("AppServiceHost"),
Port: uint16(b.GetInt("AppServicePort")),
}
w.appService.Registration, err = appservice.LoadRegistration(b.GetString("AppServiceConfigPath"))
if err != nil {
return nil, err
}

// forward logs from the appService to the matterbridge logger
w.appService.Log = NewZerologWrapper(b.Log)

if err = w.ParseNamespaces(b.Log); err != nil {
return nil, err
}

return w, nil
}

func (a *AppServiceNamespaces) containsRoom(roomID id.RoomID) bool {
// no room specified: we check all the rooms
if len(a.rooms) == 0 {
return true
}

for _, room := range a.rooms {
if room.MatchString(roomID.String()) {
return true
}
}

return false
}

//nolint:wrapcheck
func (b *Bmatrix) startAppService() error {
wrapper := b.appService
// TODO: detect service completion and rerun automatically
go wrapper.appService.Start()
b.Log.Debug("appservice launched")

processor := appservice.NewEventProcessor(wrapper.appService)
for _, eventType := range []event.Type{event.EventRedaction, event.EventMessage, event.EventSticker, event.EphemeralEventTyping} {
processor.On(eventType, func(ev *event.Event) {
b.handleEvent(originAppService, ev)
})
}
go processor.Start()
b.Log.Debug("appservice event dispatcher launched")

// handle service stopping/restarting
go func(appService *appservice.AppService, processor *appservice.EventProcessor) {
<-wrapper.stop

appService.Stop()
processor.Stop()
wrapper.stopAck <- struct{}{}
}(wrapper.appService, processor)

return nil
}
Loading