Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

Viewing file on branch master

1package gateway
2
3import (
4 "crypto/sha1" //nolint:gosec
5 "fmt"
6 "os"
7 "path/filepath"
8 "regexp"
9 "strings"
10
11 "github.com/matterbridge-org/matterbridge/bridge"
12 "github.com/matterbridge-org/matterbridge/bridge/config"
13 "github.com/matterbridge-org/matterbridge/gateway/bridgemap"
14)
15
16// handleEventFailure handles failures and reconnects bridges.
17func (r *Router) handleEventFailure(msg *config.Message) {
18 if msg.Event != config.EventFailure {
19 return
20 }
21 for _, gw := range r.Gateways {
22 for _, br := range gw.Bridges {
23 if msg.Account == br.Account {
24 go gw.reconnectBridge(br)
25 return
26 }
27 }
28 }
29}
30
31// handleEventGetChannelMembers handles channel members
32func (r *Router) handleEventGetChannelMembers(msg *config.Message) {
33 if msg.Event != config.EventGetChannelMembers {
34 return
35 }
36 for _, gw := range r.Gateways {
37 for _, br := range gw.Bridges {
38 if msg.Account == br.Account {
39 cMembers := msg.Extra[config.EventGetChannelMembers][0].(config.ChannelMembers)
40 r.logger.Debugf("Syncing channelmembers from %s", msg.Account)
41 br.SetChannelMembers(&cMembers)
42 return
43 }
44 }
45 }
46}
47
48// handleEventRejoinChannels handles rejoining of channels.
49func (r *Router) handleEventRejoinChannels(msg *config.Message) {
50 if msg.Event != config.EventRejoinChannels {
51 return
52 }
53 for _, gw := range r.Gateways {
54 for _, br := range gw.Bridges {
55 if msg.Account == br.Account {
56 br.Joined = make(map[string]bool)
57 if err := br.JoinChannels(); err != nil {
58 r.logger.Errorf("channel join failed for %s: %s", msg.Account, err)
59 }
60 }
61 }
62 }
63}
64
65// handleFiles uploads or places all files on the given msg to the MediaServer and
66// adds the new URL of the file on the MediaServer onto the given msg.
67func (gw *Gateway) handleFiles(msg *config.Message) {
68 reg := regexp.MustCompile("[^a-zA-Z0-9]+")
69
70 // If we don't have a attachfield or we don't have a mediaserver configured return
71 if msg.Extra == nil || gw.BridgeValues().General.MediaDownloadPath == "" {
72 return
73 }
74
75 // If we don't have files, nothing to upload.
76 if len(msg.Extra["file"]) == 0 {
77 return
78 }
79
80 for i, f := range msg.Extra["file"] {
81 fi := f.(config.FileInfo)
82 ext := filepath.Ext(fi.Name)
83 fi.Name = fi.Name[0 : len(fi.Name)-len(ext)]
84 fi.Name = reg.ReplaceAllString(fi.Name, "_")
85 fi.Name += ext
86
87 sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))[:8] //nolint:gosec
88
89 // Use MediaServerPath. Place the file on the current filesystem.
90 err := gw.handleFilesLocal(&fi)
91 if err != nil {
92 gw.logger.Error(err)
93 continue
94 }
95
96 // Download URL.
97 durl := gw.BridgeValues().General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
98
99 gw.logger.Debugf("mediaserver download URL = %s", durl)
100
101 // We uploaded/placed the file successfully. Add the SHA and URL.
102 extra := msg.Extra["file"][i].(config.FileInfo)
103 extra.URL = durl
104 extra.SHA = sha1sum
105 msg.Extra["file"][i] = extra
106 }
107}
108
109// handleFilesLocal use MediaServerPath configuration, places the file on the current filesystem.
110// Returns error on failure.
111func (gw *Gateway) handleFilesLocal(fi *config.FileInfo) error {
112 sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))[:8] //nolint:gosec
113 dir := gw.BridgeValues().General.MediaDownloadPath + "/" + sha1sum
114 err := os.Mkdir(dir, os.ModePerm)
115 if err != nil && !os.IsExist(err) {
116 return fmt.Errorf("mediaserver path failed, could not mkdir: %s %#v", err, err)
117 }
118
119 path := dir + "/" + fi.Name
120 gw.logger.Debugf("mediaserver path placing file: %s", path)
121
122 err = os.WriteFile(path, *fi.Data, os.ModePerm) //nolint:gosec
123 if err != nil {
124 return fmt.Errorf("mediaserver path failed, could not writefile: %s %#v", err, err)
125 }
126 return nil
127}
128
129// ignoreEvent returns true if we need to ignore this event for the specified destination bridge.
130func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
131 switch event {
132 case config.EventAvatarDownload:
133 // Avatar downloads are only relevant for telegram and mattermost for now
134 if dest.Protocol != "mattermost" && dest.Protocol != "telegram" && dest.Protocol != "xmpp" {
135 return true
136 }
137 case config.EventJoinLeave:
138 // only relay join/part when configured
139 if !dest.GetBool("ShowJoinPart") {
140 return true
141 }
142 case config.EventTopicChange:
143 // only relay topic change when used in some way on other side
144 if !dest.GetBool("ShowTopicChange") && !dest.GetBool("SyncTopic") {
145 return true
146 }
147 }
148 return false
149}
150
151// handleMessage makes sure the message get sent to the correct bridge/channels.
152// Returns an array of msg ID's
153func (gw *Gateway) handleMessage(rmsg *config.Message, dest *bridge.Bridge) []*BrMsgID {
154 var brMsgIDs []*BrMsgID
155
156 // Not all bridges support "user is typing" indications so skip the message
157 // if the targeted bridge does not support it.
158 if rmsg.Event == config.EventUserTyping {
159 if _, ok := bridgemap.UserTypingSupport[dest.Protocol]; !ok {
160 return nil
161 }
162 }
163
164 // if we have an attached file, or other info
165 if rmsg.Extra != nil && len(rmsg.Extra[config.EventFileFailureSize]) != 0 && rmsg.Text == "" {
166 return brMsgIDs
167 }
168
169 if gw.ignoreEvent(rmsg.Event, dest) {
170 return brMsgIDs
171 }
172
173 // broadcast to every out channel (irc QUIT)
174 if rmsg.Channel == "" && rmsg.Event != config.EventJoinLeave {
175 gw.logger.Debug("empty channel")
176 return brMsgIDs
177 }
178
179 // Get the ID of the parent message in thread
180 var canonicalParentMsgID string
181 if rmsg.ParentID != "" && dest.GetBool("PreserveThreading") {
182 canonicalParentMsgID = gw.FindCanonicalMsgID(rmsg.Protocol, rmsg.ParentID)
183 }
184
185 channels := gw.getDestChannel(rmsg, *dest)
186 for idx := range channels {
187 channel := &channels[idx]
188 msgID, err := gw.SendMessage(rmsg, dest, channel, canonicalParentMsgID)
189 if err != nil {
190 gw.logger.Errorf("SendMessage failed: %s", err)
191 continue
192 }
193 if msgID == "" {
194 continue
195 }
196 brMsgIDs = append(brMsgIDs, &BrMsgID{dest, dest.Protocol + " " + msgID, channel.ID})
197 }
198 return brMsgIDs
199}
200
201func (gw *Gateway) handleExtractNicks(msg *config.Message) {
202 var err error
203 br := gw.Bridges[msg.Account]
204 for _, outer := range br.GetStringSlice2D("ExtractNicks") {
205 search := outer[0]
206 replace := outer[1]
207 msg.Username, msg.Text, err = extractNick(search, replace, msg.Username, msg.Text)
208 if err != nil {
209 gw.logger.Errorf("regexp in %s failed: %s", msg.Account, err)
210 break
211 }
212 }
213}
214
215// extractNick searches for a username (based on "search" a regular expression).
216// if this matches it extracts a nick (based on "extract" another regular expression) from text
217// and replaces username with this result.
218// returns error if the regexp doesn't compile.
219func extractNick(search, extract, username, text string) (string, string, error) {
220 re, err := regexp.Compile(search)
221 if err != nil {
222 return username, text, err
223 }
224 if re.MatchString(username) {
225 re, err = regexp.Compile(extract)
226 if err != nil {
227 return username, text, err
228 }
229 res := re.FindAllStringSubmatch(text, 1)
230 // only replace if we have exactly 1 match
231 if len(res) > 0 && len(res[0]) == 2 {
232 username = res[0][1]
233 text = strings.Replace(text, res[0][0], "", 1)
234 }
235 }
236 return username, text, nil
237}
238