| 1 | package btelegram |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "html" |
| 6 | "log" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | |
| 10 | "github.com/matterbridge-org/matterbridge/bridge" |
| 11 | "github.com/matterbridge-org/matterbridge/bridge/config" |
| 12 | "github.com/matterbridge-org/matterbridge/bridge/helper" |
| 13 | // Seems not much different from upstream https://github.com/go-telegram-bot-api/telegram-bot-api replace? |
| 14 | tgbotapi "github.com/matterbridge/telegram-bot-api/v6" |
| 15 | ) |
| 16 | |
| 17 | const ( |
| 18 | unknownUser = "unknown" |
| 19 | HTMLFormat = "HTML" |
| 20 | HTMLNick = "htmlnick" |
| 21 | MarkdownV2 = "MarkdownV2" |
| 22 | ) |
| 23 | |
| 24 | type Btelegram struct { |
| 25 | c *tgbotapi.BotAPI |
| 26 | *bridge.Config |
| 27 | avatarMap map[string]string // keep cache of userid and avatar sha |
| 28 | } |
| 29 | |
| 30 | func New(cfg *bridge.Config) bridge.Bridger { |
| 31 | tgsConvertFormat := cfg.GetString("MediaConvertTgs") |
| 32 | if tgsConvertFormat != "" { |
| 33 | err := helper.CanConvertTgsToX() |
| 34 | if err != nil { |
| 35 | log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s does not appear to work:\n%#v", tgsConvertFormat, helper.LottieBackend(), err) |
| 36 | } |
| 37 | if !helper.SupportsFormat(tgsConvertFormat) { |
| 38 | log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s doesn't support it.", tgsConvertFormat, helper.LottieBackend()) |
| 39 | } |
| 40 | } |
| 41 | return &Btelegram{Config: cfg, avatarMap: make(map[string]string)} |
| 42 | } |
| 43 | |
| 44 | func (b *Btelegram) Connect() error { |
| 45 | var err error |
| 46 | b.Log.Info("Connecting") |
| 47 | b.c, err = tgbotapi.NewBotAPI(b.GetString("Token")) |
| 48 | if err != nil { |
| 49 | b.Log.Debugf("%#v", err) |
| 50 | return err |
| 51 | } |
| 52 | u := tgbotapi.NewUpdate(0) |
| 53 | u.Timeout = 60 |
| 54 | updates := b.c.GetUpdatesChan(u) |
| 55 | b.Log.Info("Connection succeeded") |
| 56 | go b.handleRecv(updates) |
| 57 | return nil |
| 58 | } |
| 59 | |
| 60 | func (b *Btelegram) Disconnect() error { |
| 61 | return nil |
| 62 | } |
| 63 | |
| 64 | func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { |
| 65 | return nil |
| 66 | } |
| 67 | |
| 68 | func TGGetParseMode(b *Btelegram, username string, text string) (textout string, parsemode string) { |
| 69 | textout = username + text |
| 70 | if b.GetString("MessageFormat") == HTMLFormat { |
| 71 | b.Log.Debug("Using mode HTML") |
| 72 | parsemode = tgbotapi.ModeHTML |
| 73 | } |
| 74 | if b.GetString("MessageFormat") == "Markdown" { |
| 75 | b.Log.Debug("Using mode markdown") |
| 76 | parsemode = tgbotapi.ModeMarkdown |
| 77 | } |
| 78 | if b.GetString("MessageFormat") == MarkdownV2 { |
| 79 | b.Log.Debug("Using mode MarkdownV2") |
| 80 | parsemode = MarkdownV2 |
| 81 | } |
| 82 | if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick { |
| 83 | b.Log.Debug("Using mode HTML - nick only") |
| 84 | textout = username + html.EscapeString(text) |
| 85 | parsemode = tgbotapi.ModeHTML |
| 86 | } |
| 87 | return textout, parsemode |
| 88 | } |
| 89 | |
| 90 | func (b *Btelegram) getIds(channel string) (int64, int, error) { |
| 91 | var chatid int64 |
| 92 | topicid := 0 |
| 93 | |
| 94 | // get the chatid |
| 95 | if strings.Contains(channel, "/") { //nolint:nestif |
| 96 | s := strings.Split(channel, "/") |
| 97 | if len(s) < 2 { |
| 98 | b.Log.Errorf("Invalid channel format: %#v\n", channel) |
| 99 | return 0, 0, nil |
| 100 | } |
| 101 | id, err := strconv.ParseInt(s[0], 10, 64) |
| 102 | if err != nil { |
| 103 | return 0, 0, err |
| 104 | } |
| 105 | chatid = id |
| 106 | tid, err := strconv.Atoi(s[1]) |
| 107 | if err != nil { |
| 108 | return 0, 0, err |
| 109 | } |
| 110 | topicid = tid |
| 111 | } else { |
| 112 | id, err := strconv.ParseInt(channel, 10, 64) |
| 113 | if err != nil { |
| 114 | return 0, 0, err |
| 115 | } |
| 116 | chatid = id |
| 117 | } |
| 118 | return chatid, topicid, nil |
| 119 | } |
| 120 | |
| 121 | func (b *Btelegram) Send(msg config.Message) (string, error) { |
| 122 | b.Log.Debugf("=> Receiving %#v", msg) |
| 123 | |
| 124 | chatid, topicid, err := b.getIds(msg.Channel) |
| 125 | if err != nil { |
| 126 | return "", err |
| 127 | } |
| 128 | |
| 129 | // map the file SHA to our user (caches the avatar) |
| 130 | if msg.Event == config.EventAvatarDownload { |
| 131 | return b.cacheAvatar(&msg) |
| 132 | } |
| 133 | |
| 134 | if b.GetString("MessageFormat") == HTMLFormat { |
| 135 | msg.Text = makeHTML(html.EscapeString(msg.Text)) |
| 136 | } |
| 137 | |
| 138 | // Delete message |
| 139 | if msg.Event == config.EventMsgDelete { |
| 140 | return b.handleDelete(&msg, chatid) |
| 141 | } |
| 142 | |
| 143 | // Handle prefix hint for unthreaded messages. |
| 144 | if msg.ParentNotFound() { |
| 145 | msg.ParentID = "" |
| 146 | msg.Text = fmt.Sprintf("[reply]: %s", msg.Text) |
| 147 | } |
| 148 | |
| 149 | var parentID int |
| 150 | if msg.ParentID != "" { |
| 151 | parentID, _ = b.intParentID(msg.ParentID) |
| 152 | } |
| 153 | |
| 154 | // Upload a file if it exists |
| 155 | if msg.Extra != nil { |
| 156 | for _, rmsg := range helper.HandleExtra(&msg, b.General) { |
| 157 | if _, msgErr := b.sendMessage(chatid, topicid, rmsg.Username, rmsg.Text, parentID); msgErr != nil { |
| 158 | b.Log.Errorf("sendMessage failed: %s", msgErr) |
| 159 | } |
| 160 | } |
| 161 | // check if we have files to upload (from slack, telegram or mattermost) |
| 162 | if len(msg.Extra["file"]) > 0 { |
| 163 | return b.handleUploadFile(&msg, chatid, topicid, parentID) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | // edit the message if we have a msg ID |
| 168 | if msg.ID != "" { |
| 169 | return b.handleEdit(&msg, chatid) |
| 170 | } |
| 171 | |
| 172 | // Post normal message |
| 173 | // TODO: recheck it. |
| 174 | // Ignore empty text field needs for prevent double messages from whatsapp to telegram |
| 175 | // when sending media with text caption |
| 176 | if msg.Text != "" { |
| 177 | return b.sendMessage(chatid, topicid, msg.Username, msg.Text, parentID) |
| 178 | } |
| 179 | |
| 180 | return "", nil |
| 181 | } |
| 182 | |
| 183 | func (b *Btelegram) getFileDirectURL(id string) string { |
| 184 | res, err := b.c.GetFileDirectURL(id) |
| 185 | if err != nil { |
| 186 | return "" |
| 187 | } |
| 188 | return res |
| 189 | } |
| 190 | |
| 191 | func (b *Btelegram) sendMessage(chatid int64, topicid int, username, text string, parentID int) (string, error) { |
| 192 | m := tgbotapi.NewMessage(chatid, "") |
| 193 | m.Text, m.ParseMode = TGGetParseMode(b, username, text) |
| 194 | if topicid != 0 { |
| 195 | m.BaseChat.MessageThreadID = topicid |
| 196 | } |
| 197 | m.ReplyToMessageID = parentID |
| 198 | m.DisableWebPagePreview = b.GetBool("DisableWebPagePreview") |
| 199 | |
| 200 | res, err := b.c.Send(m) |
| 201 | if err != nil { |
| 202 | return "", err |
| 203 | } |
| 204 | return strconv.Itoa(res.MessageID), nil |
| 205 | } |
| 206 | |
| 207 | // sendMediaFiles native upload media files via media group |
| 208 | func (b *Btelegram) sendMediaFiles(msg *config.Message, chatid int64, threadid int, parentID int, media []interface{}) (string, error) { |
| 209 | if len(media) == 0 { |
| 210 | return "", nil |
| 211 | } |
| 212 | mg := tgbotapi.MediaGroupConfig{ |
| 213 | BaseChat: tgbotapi.BaseChat{ |
| 214 | ChatID: chatid, |
| 215 | MessageThreadID: threadid, |
| 216 | ChannelUsername: msg.Username, |
| 217 | ReplyToMessageID: parentID, |
| 218 | }, |
| 219 | Media: media, |
| 220 | } |
| 221 | messages, err := b.c.SendMediaGroup(mg) |
| 222 | if err != nil { |
| 223 | return "", err |
| 224 | } |
| 225 | // return first message id |
| 226 | return strconv.Itoa(messages[0].MessageID), nil |
| 227 | } |
| 228 | |
| 229 | // intParentID return integer parent id for telegram message |
| 230 | func (b *Btelegram) intParentID(parentID string) (int, error) { |
| 231 | pid, err := strconv.Atoi(parentID) |
| 232 | if err != nil { |
| 233 | return 0, err |
| 234 | } |
| 235 | return pid, nil |
| 236 | } |
| 237 | |
| 238 | func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) { |
| 239 | fi := msg.Extra["file"][0].(config.FileInfo) |
| 240 | /* if we have a sha we have successfully uploaded the file to the media server, |
| 241 | so we can now cache the sha */ |
| 242 | if fi.SHA != "" { |
| 243 | b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID) |
| 244 | b.avatarMap[msg.UserID] = fi.SHA |
| 245 | } |
| 246 | return "", nil |
| 247 | } |
| 248 | |