From 795a8705c3fdc5bf55e83d382e7d3ff233896a0b Mon Sep 17 00:00:00 2001 From: Wim Date: Thu, 26 Dec 2019 23:12:28 +0100 Subject: Add initial Microsoft Teams support Documentation on https://github.com/42wim/matterbridge/wiki/MS-Teams-setup --- bridge/config/config.go | 4 ++ bridge/msteams/msteams.go | 169 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 bridge/msteams/msteams.go (limited to 'bridge') diff --git a/bridge/config/config.go b/bridge/config/config.go index 2d20c11f..efbb8e1f 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -76,6 +76,7 @@ type Protocol struct { BindAddress string // mattermost, slack // DEPRECATED Buffer int // api Charset string // irc + ClientID string // msteams ColorNicks bool // only irc for now Debug bool // general DebugLevel int // only for irc now @@ -124,6 +125,7 @@ type Protocol struct { RemoteNickFormat string // all protocols RunCommands []string // IRC Server string // IRC,mattermost,XMPP,discord + SessionFile string // msteams,whatsapp ShowJoinPart bool // all protocols ShowTopicChange bool // slack ShowUserTyping bool // slack @@ -134,6 +136,8 @@ type Protocol struct { SyncTopic bool // slack TengoModifyMessage string // general Team string // mattermost, keybase + TeamID string // msteams + TenantID string // msteams Token string // gitter, slack, discord, api Topic string // zulip URL string // mattermost, slack // DEPRECATED diff --git a/bridge/msteams/msteams.go b/bridge/msteams/msteams.go new file mode 100644 index 00000000..f2f0308b --- /dev/null +++ b/bridge/msteams/msteams.go @@ -0,0 +1,169 @@ +package bgitter + +import ( + "context" + "os" + "strings" + "time" + + "github.com/42wim/matterbridge/bridge" + "github.com/42wim/matterbridge/bridge/config" + "github.com/mattn/godown" + msgraph "github.com/yaegashi/msgraph.go/beta" + "github.com/yaegashi/msgraph.go/msauth" + + "golang.org/x/oauth2" +) + +//var defaultScopes = []string{"openid", "profile", "offline_access", "User.Read", "Files.Read", "ChannelMessage.Read.All", "Chat.ReadWrite", "User.Read.All", "User.ReadWrite.All", "Group.Read.All", "Group.ReadWrite.All"} +var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"} + +type Bmsteams struct { + gc *msgraph.GraphServiceRequestBuilder + ctx context.Context + botID string + *bridge.Config +} + +func New(cfg *bridge.Config) bridge.Bridger { + return &Bmsteams{Config: cfg} +} + +func (b *Bmsteams) Connect() error { + tokenCachePath := b.GetString("sessionFile") + if tokenCachePath == "" { + tokenCachePath = "msteams_session.json" + } + ctx := context.Background() + m := msauth.NewManager() + m.LoadFile(tokenCachePath) //nolint:errcheck + ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil) + if err != nil { + return err + } + err = m.SaveFile(tokenCachePath) + if err != nil { + b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err) + } + // make file readable only for matterbridge user + err = os.Chmod(tokenCachePath, 0600) + if err != nil { + b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err) + } + httpClient := oauth2.NewClient(ctx, ts) + graphClient := msgraph.NewClient(httpClient) + b.gc = graphClient + b.ctx = ctx + + err = b.setBotID() + if err != nil { + return err + } + b.Log.Info("Connection succeeded") + return nil +} + +func (b *Bmsteams) Disconnect() error { + return nil +} + +func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error { + go b.poll(channel.Name) + return nil +} + +func (b *Bmsteams) Send(msg config.Message) (string, error) { + b.Log.Debugf("=> Receiving %#v", msg) + ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request() + text := msg.Username + msg.Text + content := &msgraph.ItemBody{Content: &text} + rmsg := &msgraph.ChatMessage{Body: content} + res, err := ct.Add(b.ctx, rmsg) + if err != nil { + return "", err + } + return *res.ID, nil +} + +func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) { + ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request() + rct, err := ct.Get(b.ctx) + if err != nil { + return nil, err + } + b.Log.Debugf("got %#v messages", len(rct)) + return rct, nil +} + +func (b *Bmsteams) poll(channelName string) { + msgmap := make(map[string]time.Time) + b.Log.Debug("getting initial messages") + res, err := b.getMessages(channelName) + if err != nil { + panic(err) + } + for _, msg := range res { + msgmap[*msg.ID] = *msg.CreatedDateTime + if msg.LastModifiedDateTime != nil { + msgmap[*msg.ID] = *msg.LastModifiedDateTime + } + } + time.Sleep(time.Second * 5) + b.Log.Debug("polling for messages") + for { + res, err := b.getMessages(channelName) + if err != nil { + panic(err) + } + for i := len(res) - 1; i >= 0; i-- { + msg := res[i] + if mtime, ok := msgmap[*msg.ID]; ok { + if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil { + continue + } + if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime { + continue + } + } + if *msg.From.User.ID == b.botID { + b.Log.Debug("skipping own message") + msgmap[*msg.ID] = *msg.CreatedDateTime + continue + } + msgmap[*msg.ID] = *msg.CreatedDateTime + if msg.LastModifiedDateTime != nil { + msgmap[*msg.ID] = *msg.LastModifiedDateTime + } + b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account) + text := b.convertToMD(*msg.Body.Content) + rmsg := config.Message{Username: *msg.From.User.DisplayName, Text: text, Channel: channelName, + Account: b.Account, Avatar: "", UserID: *msg.From.User.ID, ID: *msg.ID} + b.Log.Debugf("<= Message is %#v", rmsg) + b.Remote <- rmsg + } + time.Sleep(time.Second * 5) + } +} + +func (b *Bmsteams) setBotID() error { + req := b.gc.Me().Request() + r, err := req.Get(b.ctx) + if err != nil { + return err + } + b.botID = *r.ID + return nil +} + +func (b *Bmsteams) convertToMD(text string) string { + if !strings.Contains(text, "
") { + return text + } + var sb strings.Builder + err := godown.Convert(&sb, strings.NewReader(text), nil) + if err != nil { + b.Log.Errorf("Couldn't convert message to markdown %s", text) + return text + } + return sb.String() +} -- cgit v1.2.3