2023-05-26 04:43:51 +03:00
package telegram
import (
"context"
2023-05-29 08:29:21 +03:00
"errors"
2023-05-26 04:43:51 +03:00
"fmt"
2023-05-29 08:29:21 +03:00
"strings"
2023-05-26 04:43:51 +03:00
"time"
"go.uber.org/zap"
2023-09-17 17:55:13 +03:00
"github.com/usememos/memos/common/log"
2023-05-26 04:43:51 +03:00
)
type Handler interface {
2023-05-29 14:49:05 +03:00
BotToken ( ctx context . Context ) string
2023-07-13 19:18:44 +03:00
MessageHandle ( ctx context . Context , bot * Bot , message Message , attachments [ ] Attachment ) error
2023-06-14 17:10:01 +03:00
CallbackQueryHandle ( ctx context . Context , bot * Bot , callbackQuery CallbackQuery ) error
2023-05-26 04:43:51 +03:00
}
2023-05-29 14:49:05 +03:00
type Bot struct {
2023-05-26 04:43:51 +03:00
handler Handler
}
2023-05-29 14:49:05 +03:00
// NewBotWithHandler create a telegram bot with specified handler.
func NewBotWithHandler ( h Handler ) * Bot {
return & Bot { handler : h }
2023-05-26 04:43:51 +03:00
}
const noTokenWait = 30 * time . Second
2023-05-26 14:16:51 +03:00
const errRetryWait = 10 * time . Second
2023-05-26 04:43:51 +03:00
2023-08-11 17:34:08 +03:00
// Start start a long polling using getUpdates to get Update, call r.MessageHandle while get new message updates.
2023-05-29 14:49:05 +03:00
func ( b * Bot ) Start ( ctx context . Context ) {
2023-09-06 16:14:07 +03:00
var offset int64
2023-05-26 04:43:51 +03:00
for {
2023-05-29 14:49:05 +03:00
updates , err := b . GetUpdates ( ctx , offset )
2023-05-29 08:29:21 +03:00
if err == ErrInvalidToken {
2023-05-26 04:43:51 +03:00
time . Sleep ( noTokenWait )
continue
}
if err != nil {
log . Warn ( "fail to telegram.GetUpdates" , zap . Error ( err ) )
2023-05-26 14:16:51 +03:00
time . Sleep ( errRetryWait )
2023-05-26 04:43:51 +03:00
continue
}
2023-06-14 17:10:01 +03:00
singleMessages := make ( [ ] Message , 0 , len ( updates ) )
2023-05-26 04:43:51 +03:00
groupMessages := make ( [ ] Message , 0 , len ( updates ) )
for _ , update := range updates {
offset = update . UpdateID + 1
2023-06-14 17:10:01 +03:00
// handle CallbackQuery update
if update . CallbackQuery != nil {
err := b . handler . CallbackQueryHandle ( ctx , b , * update . CallbackQuery )
2023-05-26 04:43:51 +03:00
if err != nil {
2023-06-14 17:10:01 +03:00
log . Error ( "fail to handle CallbackQuery" , zap . Error ( err ) )
2023-05-26 04:43:51 +03:00
}
continue
}
2023-06-14 17:10:01 +03:00
// handle Message update
if update . Message != nil {
message := * update . Message
2023-07-13 19:18:44 +03:00
// skip unsupported message
if ! message . IsSupported ( ) {
_ , err := b . SendReplyMessage ( ctx , message . Chat . ID , message . MessageID , "Supported messages: animation, audio, text, document, photo, video, video note, voice, other messages with caption" )
2023-06-14 17:10:01 +03:00
if err != nil {
log . Error ( fmt . Sprintf ( "fail to telegram.SendReplyMessage for messageID=%d" , message . MessageID ) , zap . Error ( err ) )
}
continue
}
// Group message need do more
if message . MediaGroupID != nil {
groupMessages = append ( groupMessages , message )
continue
}
singleMessages = append ( singleMessages , message )
2023-05-26 04:43:51 +03:00
continue
}
}
2023-06-14 17:10:01 +03:00
err = b . handleSingleMessages ( ctx , singleMessages )
if err != nil {
log . Error ( "fail to handle singleMessage" , zap . Error ( err ) )
}
2023-05-29 14:49:05 +03:00
err = b . handleGroupMessages ( ctx , groupMessages )
2023-05-26 04:43:51 +03:00
if err != nil {
log . Error ( "fail to handle plain text message" , zap . Error ( err ) )
}
}
}
2023-05-29 08:29:21 +03:00
var ErrInvalidToken = errors . New ( "token is invalid" )
2023-05-29 14:49:05 +03:00
func ( b * Bot ) apiURL ( ctx context . Context ) ( string , error ) {
token := b . handler . BotToken ( ctx )
2023-05-29 08:29:21 +03:00
if token == "" {
return "" , ErrInvalidToken
}
if strings . HasPrefix ( token , "http" ) {
return token , nil
}
return "https://api.telegram.org/bot" + token , nil
}