summaryrefslogtreecommitdiffstats
path: root/bridge/matrix/appservice.go
blob: ef172bff4e37d11ffdc9aec54ee6aa45aa33be82 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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)
	processor.On(event.EventMessage, 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
}