42wim/matterbridge

View on GitHub
bridge/telegram/telegram.go

Summary

Maintainability
A
1 hr
Test Coverage
package btelegram

import (
    "fmt"
    "html"
    "log"
    "strconv"
    "strings"

    "github.com/42wim/matterbridge/bridge"
    "github.com/42wim/matterbridge/bridge/config"
    "github.com/42wim/matterbridge/bridge/helper"
    tgbotapi "github.com/matterbridge/telegram-bot-api/v6"
)

const (
    unknownUser = "unknown"
    HTMLFormat  = "HTML"
    HTMLNick    = "htmlnick"
    MarkdownV2  = "MarkdownV2"
)

type Btelegram struct {
    c *tgbotapi.BotAPI
    *bridge.Config
    avatarMap map[string]string // keep cache of userid and avatar sha
}

func New(cfg *bridge.Config) bridge.Bridger {
    tgsConvertFormat := cfg.GetString("MediaConvertTgs")
    if tgsConvertFormat != "" {
        err := helper.CanConvertTgsToX()
        if err != nil {
            log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s does not appear to work:\n%#v", tgsConvertFormat, helper.LottieBackend(), err)
        }
        if !helper.SupportsFormat(tgsConvertFormat) {
            log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s doesn't support it.", tgsConvertFormat, helper.LottieBackend())
        }
    }
    return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
}

func (b *Btelegram) Connect() error {
    var err error
    b.Log.Info("Connecting")
    b.c, err = tgbotapi.NewBotAPI(b.GetString("Token"))
    if err != nil {
        b.Log.Debugf("%#v", err)
        return err
    }
    u := tgbotapi.NewUpdate(0)
    u.Timeout = 60
    updates := b.c.GetUpdatesChan(u)
    b.Log.Info("Connection succeeded")
    go b.handleRecv(updates)
    return nil
}

func (b *Btelegram) Disconnect() error {
    return nil
}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
    return nil
}

func TGGetParseMode(b *Btelegram, username string, text string) (textout string, parsemode string) {
    textout = username + text
    if b.GetString("MessageFormat") == HTMLFormat {
        b.Log.Debug("Using mode HTML")
        parsemode = tgbotapi.ModeHTML
    }
    if b.GetString("MessageFormat") == "Markdown" {
        b.Log.Debug("Using mode markdown")
        parsemode = tgbotapi.ModeMarkdown
    }
    if b.GetString("MessageFormat") == MarkdownV2 {
        b.Log.Debug("Using mode MarkdownV2")
        parsemode = MarkdownV2
    }
    if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
        b.Log.Debug("Using mode HTML - nick only")
        textout = username + html.EscapeString(text)
        parsemode = tgbotapi.ModeHTML
    }
    return textout, parsemode
}

func (b *Btelegram) getIds(channel string) (int64, int, error) {
    var chatid int64
    topicid := 0

    // get the chatid
    if strings.Contains(channel, "/") { //nolint:nestif
        s := strings.Split(channel, "/")
        if len(s) < 2 {
            b.Log.Errorf("Invalid channel format: %#v\n", channel)
            return 0, 0, nil
        }
        id, err := strconv.ParseInt(s[0], 10, 64)
        if err != nil {
            return 0, 0, err
        }
        chatid = id
        tid, err := strconv.Atoi(s[1])
        if err != nil {
            return 0, 0, err
        }
        topicid = tid
    } else {
        id, err := strconv.ParseInt(channel, 10, 64)
        if err != nil {
            return 0, 0, err
        }
        chatid = id
    }
    return chatid, topicid, nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
    b.Log.Debugf("=> Receiving %#v", msg)

    chatid, topicid, err := b.getIds(msg.Channel)
    if err != nil {
        return "", err
    }

    // map the file SHA to our user (caches the avatar)
    if msg.Event == config.EventAvatarDownload {
        return b.cacheAvatar(&msg)
    }

    if b.GetString("MessageFormat") == HTMLFormat {
        msg.Text = makeHTML(html.EscapeString(msg.Text))
    }

    // Delete message
    if msg.Event == config.EventMsgDelete {
        return b.handleDelete(&msg, chatid)
    }

    // Handle prefix hint for unthreaded messages.
    if msg.ParentNotFound() {
        msg.ParentID = ""
        msg.Text = fmt.Sprintf("[reply]: %s", msg.Text)
    }

    var parentID int
    if msg.ParentID != "" {
        parentID, _ = b.intParentID(msg.ParentID)
    }

    // Upload a file if it exists
    if msg.Extra != nil {
        for _, rmsg := range helper.HandleExtra(&msg, b.General) {
            if _, msgErr := b.sendMessage(chatid, topicid, rmsg.Username, rmsg.Text, parentID); msgErr != nil {
                b.Log.Errorf("sendMessage failed: %s", msgErr)
            }
        }
        // check if we have files to upload (from slack, telegram or mattermost)
        if len(msg.Extra["file"]) > 0 {
            return b.handleUploadFile(&msg, chatid, topicid, parentID)
        }
    }

    // edit the message if we have a msg ID
    if msg.ID != "" {
        return b.handleEdit(&msg, chatid)
    }

    // Post normal message
    // TODO: recheck it.
    // Ignore empty text field needs for prevent double messages from whatsapp to telegram
    // when sending media with text caption
    if msg.Text != "" {
        return b.sendMessage(chatid, topicid, msg.Username, msg.Text, parentID)
    }

    return "", nil
}

func (b *Btelegram) getFileDirectURL(id string) string {
    res, err := b.c.GetFileDirectURL(id)
    if err != nil {
        return ""
    }
    return res
}

func (b *Btelegram) sendMessage(chatid int64, topicid int, username, text string, parentID int) (string, error) {
    m := tgbotapi.NewMessage(chatid, "")
    m.Text, m.ParseMode = TGGetParseMode(b, username, text)
    if topicid != 0 {
        m.BaseChat.MessageThreadID = topicid
    }
    m.ReplyToMessageID = parentID
    m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview")

    res, err := b.c.Send(m)
    if err != nil {
        return "", err
    }
    return strconv.Itoa(res.MessageID), nil
}

// sendMediaFiles native upload media files via media group
func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, threadid int, parentID int, media []interface{}) (string, error) {
    if len(media) == 0 {
        return "", nil
    }
    mg := tgbotapi.MediaGroupConfig{
        BaseChat: tgbotapi.BaseChat{
            ChatID:           chatid,
            MessageThreadID:  threadid,
            ChannelUsername:  msg.Username,
            ReplyToMessageID: parentID,
        },
        Media: media,
    }
    messages, err := b.c.SendMediaGroup(mg)
    if err != nil {
        return "", err
    }
    // return first message id
    return strconv.Itoa(messages[0].MessageID), nil
}

// intParentID return integer parent id for telegram message
func (b *Btelegram) intParentID(parentID string) (int, error) {
    pid, err := strconv.Atoi(parentID)
    if err != nil {
        return 0, err
    }
    return pid, nil
}

func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
    fi := msg.Extra["file"][0].(config.FileInfo)
    /* if we have a sha we have successfully uploaded the file to the media server,
    so we can now cache the sha */
    if fi.SHA != "" {
        b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
        b.avatarMap[msg.UserID] = fi.SHA
    }
    return "", nil
}