Thumbnail

rani/matterbridge.git

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

Viewing file on branch master

1package bmsteams
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "regexp"
8 "strings"
9 "time"
10
11 "github.com/davecgh/go-spew/spew"
12 "github.com/matterbridge-org/matterbridge/bridge"
13 "github.com/matterbridge-org/matterbridge/bridge/config"
14
15 "github.com/mattn/godown"
16 msgraph "github.com/yaegashi/msgraph.go/beta"
17 "github.com/yaegashi/msgraph.go/msauth"
18
19 "golang.org/x/oauth2"
20)
21
22var (
23 defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
24 attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
25)
26
27type Bmsteams struct {
28 gc *msgraph.GraphServiceRequestBuilder
29 ctx context.Context
30 botID string
31 *bridge.Config
32}
33
34func New(cfg *bridge.Config) bridge.Bridger {
35 return &Bmsteams{Config: cfg}
36}
37
38func (b *Bmsteams) Connect() error {
39 tokenCachePath := b.GetString("sessionFile")
40 if tokenCachePath == "" {
41 tokenCachePath = "msteams_session.json"
42 }
43 ctx := context.Background()
44 m := msauth.NewManager()
45 m.LoadFile(tokenCachePath) //nolint:errcheck
46 ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil)
47 if err != nil {
48 return err
49 }
50 err = m.SaveFile(tokenCachePath)
51 if err != nil {
52 b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
53 }
54 // make file readable only for matterbridge user
55 err = os.Chmod(tokenCachePath, 0o600)
56 if err != nil {
57 b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
58 }
59 httpClient := oauth2.NewClient(ctx, ts)
60 graphClient := msgraph.NewClient(httpClient)
61 b.gc = graphClient
62 b.ctx = ctx
63
64 err = b.setBotID()
65 if err != nil {
66 return err
67 }
68 b.Log.Info("Connection succeeded")
69 return nil
70}
71
72func (b *Bmsteams) Disconnect() error {
73 return nil
74}
75
76func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
77 go func(name string) {
78 for {
79 err := b.poll(name)
80 if err != nil {
81 b.Log.Errorf("polling failed for %s: %s. retrying in 5 seconds", name, err)
82 }
83 time.Sleep(time.Second * 5)
84 }
85 }(channel.Name)
86 return nil
87}
88
89func (b *Bmsteams) Send(msg config.Message) (string, error) {
90 b.Log.Debugf("=> Receiving %#v", msg)
91 if msg.ParentValid() {
92 return b.sendReply(msg)
93 }
94
95 // Handle prefix hint for unthreaded messages.
96 if msg.ParentNotFound() {
97 msg.ParentID = ""
98 msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
99 }
100
101 ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
102 text := msg.Username + msg.Text
103 content := &msgraph.ItemBody{Content: &text}
104 rmsg := &msgraph.ChatMessage{Body: content}
105 res, err := ct.Add(b.ctx, rmsg)
106 if err != nil {
107 return "", err
108 }
109 return *res.ID, nil
110}
111
112func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
113 ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
114 // Handle prefix hint for unthreaded messages.
115
116 text := msg.Username + msg.Text
117 content := &msgraph.ItemBody{Content: &text}
118 rmsg := &msgraph.ChatMessage{Body: content}
119 res, err := ct.Add(b.ctx, rmsg)
120 if err != nil {
121 return "", err
122 }
123 return *res.ID, nil
124}
125
126func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
127 ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request()
128 rct, err := ct.Get(b.ctx)
129 if err != nil {
130 return nil, err
131 }
132 b.Log.Debugf("got %#v messages", len(rct))
133 return rct, nil
134}
135
136//nolint:gocognit
137func (b *Bmsteams) poll(channelName string) error {
138 msgmap := make(map[string]time.Time)
139 b.Log.Debug("getting initial messages")
140 res, err := b.getMessages(channelName)
141 if err != nil {
142 return err
143 }
144 for _, msg := range res {
145 msgmap[*msg.ID] = *msg.CreatedDateTime
146 if msg.LastModifiedDateTime != nil {
147 msgmap[*msg.ID] = *msg.LastModifiedDateTime
148 }
149 }
150 time.Sleep(time.Second * 5)
151 b.Log.Debug("polling for messages")
152 for {
153 res, err := b.getMessages(channelName)
154 if err != nil {
155 return err
156 }
157 for i := len(res) - 1; i >= 0; i-- {
158 msg := res[i]
159 if mtime, ok := msgmap[*msg.ID]; ok {
160 if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil {
161 continue
162 }
163 if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime {
164 continue
165 }
166 }
167
168 if b.GetBool("debug") {
169 b.Log.Debug("Msg dump: ", spew.Sdump(msg))
170 }
171
172 // skip non-user message for now.
173 if msg.From == nil || msg.From.User == nil {
174 continue
175 }
176
177 if *msg.From.User.ID == b.botID {
178 b.Log.Debug("skipping own message")
179 msgmap[*msg.ID] = *msg.CreatedDateTime
180 continue
181 }
182
183 msgmap[*msg.ID] = *msg.CreatedDateTime
184 if msg.LastModifiedDateTime != nil {
185 msgmap[*msg.ID] = *msg.LastModifiedDateTime
186 }
187 b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account)
188 text := b.convertToMD(*msg.Body.Content)
189 rmsg := config.Message{
190 Username: *msg.From.User.DisplayName,
191 Text: text,
192 Channel: channelName,
193 Account: b.Account,
194 Avatar: "",
195 UserID: *msg.From.User.ID,
196 ID: *msg.ID,
197 Extra: make(map[string][]interface{}),
198 }
199
200 b.handleAttachments(&rmsg, msg)
201 b.Log.Debugf("<= Message is %#v", rmsg)
202 b.Remote <- rmsg
203 }
204 time.Sleep(time.Second * 5)
205 }
206}
207
208func (b *Bmsteams) setBotID() error {
209 req := b.gc.Me().Request()
210 r, err := req.Get(b.ctx)
211 if err != nil {
212 return err
213 }
214 b.botID = *r.ID
215 return nil
216}
217
218func (b *Bmsteams) convertToMD(text string) string {
219 if !strings.Contains(text, "<div>") {
220 return text
221 }
222 var sb strings.Builder
223 err := godown.Convert(&sb, strings.NewReader(text), nil)
224 if err != nil {
225 b.Log.Errorf("Couldn't convert message to markdown %s", text)
226 return text
227 }
228 return sb.String()
229}
230