2019-12-29 14:57:41 -08:00
|
|
|
package bmsteams
|
2019-12-26 14:12:28 -08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-12-30 10:46:48 -08:00
|
|
|
"fmt"
|
2019-12-26 14:12:28 -08:00
|
|
|
"os"
|
2019-12-29 14:57:41 -08:00
|
|
|
"regexp"
|
2019-12-26 14:12:28 -08:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/42wim/matterbridge/bridge"
|
|
|
|
"github.com/42wim/matterbridge/bridge/config"
|
2020-05-24 06:49:24 -07:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2019-12-30 10:46:48 -08:00
|
|
|
|
2019-12-26 14:12:28 -08:00
|
|
|
"github.com/mattn/godown"
|
2020-03-15 15:43:25 -07:00
|
|
|
msgraph "github.com/yaegashi/msgraph.go/beta"
|
2020-03-28 15:44:49 -07:00
|
|
|
"github.com/yaegashi/msgraph.go/msauth"
|
2019-12-26 14:12:28 -08:00
|
|
|
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
2020-01-16 12:16:35 -08:00
|
|
|
var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
|
2020-01-01 13:08:11 -08:00
|
|
|
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)
|
2019-12-26 14:12:28 -08:00
|
|
|
|
|
|
|
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 {
|
2020-04-21 10:29:24 -07:00
|
|
|
go func(name string) {
|
|
|
|
for {
|
|
|
|
err := b.poll(name)
|
|
|
|
if err != nil {
|
|
|
|
b.Log.Errorf("polling failed for %s: %s. retrying in 5 seconds", name, err)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second * 5)
|
|
|
|
}
|
|
|
|
}(channel.Name)
|
2019-12-26 14:12:28 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bmsteams) Send(msg config.Message) (string, error) {
|
|
|
|
b.Log.Debugf("=> Receiving %#v", msg)
|
2019-12-30 10:46:48 -08:00
|
|
|
if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
|
|
|
|
return b.sendReply(msg)
|
|
|
|
}
|
|
|
|
if msg.ParentID == "msg-parent-not-found" {
|
|
|
|
msg.ParentID = ""
|
|
|
|
msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
|
|
|
|
}
|
2019-12-26 14:12:28 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-30 10:46:48 -08:00
|
|
|
func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
|
|
|
|
ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
|
|
|
|
// Handle prefix hint for unthreaded messages.
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-26 14:12:28 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-01-16 15:46:11 -08:00
|
|
|
//nolint:gocognit
|
2020-04-21 10:29:24 -07:00
|
|
|
func (b *Bmsteams) poll(channelName string) error {
|
2019-12-26 14:12:28 -08:00
|
|
|
msgmap := make(map[string]time.Time)
|
|
|
|
b.Log.Debug("getting initial messages")
|
|
|
|
res, err := b.getMessages(channelName)
|
|
|
|
if err != nil {
|
2020-04-21 10:29:24 -07:00
|
|
|
return err
|
2019-12-26 14:12:28 -08:00
|
|
|
}
|
|
|
|
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 {
|
2020-04-21 10:29:24 -07:00
|
|
|
return err
|
2019-12-26 14:12:28 -08:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2020-05-24 06:49:24 -07:00
|
|
|
|
|
|
|
if b.GetBool("debug") {
|
|
|
|
b.Log.Debug("Msg dump: ", spew.Sdump(msg))
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip non-user message for now.
|
|
|
|
if msg.From.User == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-12-26 14:12:28 -08:00
|
|
|
if *msg.From.User.ID == b.botID {
|
|
|
|
b.Log.Debug("skipping own message")
|
|
|
|
msgmap[*msg.ID] = *msg.CreatedDateTime
|
|
|
|
continue
|
|
|
|
}
|
2020-05-24 06:49:24 -07:00
|
|
|
|
2019-12-26 14:12:28 -08:00
|
|
|
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)
|
2019-12-29 14:57:41 -08:00
|
|
|
rmsg := config.Message{
|
|
|
|
Username: *msg.From.User.DisplayName,
|
|
|
|
Text: text,
|
|
|
|
Channel: channelName,
|
|
|
|
Account: b.Account,
|
|
|
|
Avatar: "",
|
|
|
|
UserID: *msg.From.User.ID,
|
|
|
|
ID: *msg.ID,
|
|
|
|
Extra: make(map[string][]interface{}),
|
|
|
|
}
|
|
|
|
|
2020-01-01 13:08:11 -08:00
|
|
|
b.handleAttachments(&rmsg, msg)
|
2019-12-26 14:12:28 -08:00
|
|
|
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, "<div>") {
|
|
|
|
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()
|
|
|
|
}
|