package main import ( "fmt" "time" telegram "github.com/go-telegram-bot-api/telegram-bot-api/v5" log "github.com/sirupsen/logrus" "gorm.io/gorm" ) // TelegramMessage to store relationship between a Product and a Telegram notification type TelegramMessage struct { gorm.Model MessageID int `gorm:"not null;unique"` ProductURL string Product Product `gorm:"foreignKey:ProductURL"` } // TelegramNotifier to manage notifications to Twitter type TelegramNotifier struct { db *gorm.DB bot *telegram.BotAPI chatID int64 channelName string timeout int } // NewTelegramNotifier to create a Notifier with Telegram capabilities func NewTelegramNotifier(config *TelegramConfig, db *gorm.DB) (*TelegramNotifier, error) { // create table err := db.AutoMigrate(&TelegramMessage{}) if err != nil { return nil, err } // create client bot, err := telegram.NewBotAPI(config.Token) if err != nil { return nil, err } log.Debugf("connected to telegram as %s", bot.Self.UserName) return &TelegramNotifier{ db: db, bot: bot, chatID: config.ChatID, channelName: config.ChannelName, }, nil } // NotifyWhenAvailable create a Telegram message for announcing that a product is available // implements the Notifier interface func (n *TelegramNotifier) NotifyWhenAvailable(shopName string, productName string, productPrice float64, productCurrency string, productURL string) error { // TODO: check if message exists in the database to avoid flood // send message to telegram formattedPrice := formatPrice(productPrice, productCurrency) message := fmt.Sprintf("🟢 %s: %s for %s is available at %s", shopName, productName, formattedPrice, productURL) messageID, err := n.sendMessage(message, 0) if err != nil { return err } // save telegram message to database m := TelegramMessage{MessageID: messageID, ProductURL: productURL} trx := n.db.Create(&m) if trx.Error != nil { return fmt.Errorf("failed to save telegram message %d to database: %s", m.MessageID, trx.Error) } log.Debugf("telegram message %d saved to database", m.MessageID) return nil } // NotifyWhenNotAvailable create a Telegram message replying to the NotifyWhenAvailable message to say it's gone // implements the Notifier interface func (n *TelegramNotifier) NotifyWhenNotAvailable(productURL string, duration time.Duration) error { // find message in the database var m TelegramMessage trx := n.db.Where(TelegramMessage{ProductURL: productURL}).First(&m) if trx.Error != nil { return fmt.Errorf("failed to find telegram message in database for product with url %s: %s", productURL, trx.Error) } if m.MessageID == 0 { log.Warnf("telegram message for product with url %s not found, skipping close notification", productURL) return nil } // format message text := fmt.Sprintf("🔴 And it's gone (%s)", duration) // send reply on telegram _, err := n.sendMessage(text, m.MessageID) if err != nil { return fmt.Errorf("failed to reply on telegram: %s", err) } log.Infof("reply to telegram message %d sent", m.MessageID) // remove message from database trx = n.db.Unscoped().Delete(&m) if trx.Error != nil { return fmt.Errorf("failed to remove message %d from database: %s", m.MessageID, trx.Error) } log.Debugf("telegram message removed from database") return nil } func (n *TelegramNotifier) sendMessage(text string, reply int) (int, error) { log.Debugf("sending message %s to telegram", text) var request telegram.MessageConfig if n.chatID != 0 { request = telegram.NewMessage(n.chatID, text) } else { request = telegram.NewMessageToChannel(n.channelName, text) } request.DisableWebPagePreview = true if reply != 0 { request.ReplyToMessageID = reply } response, err := n.bot.Send(request) if err != nil { return 0, err } log.Infof("message %d sent to telegram", response.MessageID) return response.MessageID, nil }