package bot import ( "context" "fmt" "strings" "time" "git.dev.m-and-m.ovh/mderasse/tesla/alert" "git.dev.m-and-m.ovh/mderasse/tesla/api" log "github.com/sirupsen/logrus" tele "gopkg.in/telebot.v3" ) var apiClient *api.Client // Init will initialize telegram bot. func Init(alertChan chan api.Availability) { log.Info("Starting bot initialization") log.Debug("Loading bot configuration") config, err := initBotConfig() if err != nil { log.Fatalf("Fail to initialize bot configuration. Error: %s", err.Error()) } apiClient, err = api.NewClient() if err != nil { log.Fatalf("Fail to instantiate the HTTP Client. Error: %s", err.Error()) } pref := tele.Settings{ Token: config.Token, Poller: &tele.LongPoller{Timeout: 10 * time.Second}, ParseMode: tele.ModeMarkdownV2, } bot, err := tele.NewBot(pref) if err != nil { log.Fatal(err) return } log.Debug("Initializing bot middlewares") initMiddlewares(bot, config) log.Debug("Initializing bot commands") initCommands(bot, config) log.Debug("Launching alert handler") go handleAlert(bot, config, alertChan) bot.Start() } func handleAlert(bot *tele.Bot, config *botConfig, alertChan chan api.Availability) { for availability := range alertChan { for _, chatId := range config.AlertChatIds { log.Infof("Sending alert to chat %d", chatId) demoStr := "" if availability.IsDemo { demoStr = fmt.Sprintf("Demo \\(%d %s\\) ", availability.Odometer, availability.OdometerTypeShort) } _, err := bot.Send( tele.ChatID(chatId), fmt.Sprintf( "ALERT\\!\\! Found a %s%s in *%s* color is: *%d*€ [View](%s)", demoStr, cleanString(availability.TrimName), cleanString(strings.Join(availability.Paint, " and ")), availability.Price, availability.GetOrderLink(), ), ) if err != nil { log.Warnf("Fail to send alert message. Error: %s", err.Error()) } } } } func initMiddlewares(bot *tele.Bot, config *botConfig) { log.Infof("Trusting the following chats: %v", config.WhiteListChatIds) bot.Use( middlewareAllowChat(config.WhiteListChatIds...), ) } func middlewareAllowChat(chatIds ...int64) tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { for _, chat := range chatIds { if chat == c.Chat().ID { return next(c) } } return nil } } } func initCommands(bot *tele.Bot, config *botConfig) { bot.Handle(tele.OnText, func(c tele.Context) error { msg := c.Text() if c.Chat().ID < 0 { if !strings.HasPrefix(msg, fmt.Sprintf("@%s", config.BotName)) { return nil } msg = strings.TrimPrefix(msg, fmt.Sprintf("@%s ", config.BotName)) } log.Debugf("receive telegram message: %s", msg) switch strings.ToLower(msg) { case "help": return help(c) case "price": return price(c) case "list": return list(c) default: return home(c) } }) bot.Handle(&tele.Btn{Unique: "list"}, list) bot.Handle(&tele.Btn{Unique: "price"}, price) bot.Handle(&tele.Btn{Unique: "help"}, help) bot.Handle(&tele.Btn{Unique: "home"}, home) } func home(c tele.Context) error { return c.EditOrReply( "Welcome to Tesla Alerter\\. What can i do for you ?", &homeMenu, ) } func help(c tele.Context) error { return c.EditOrReply( fmt.Sprintf(`I'm a bot that have for main objective to inform you about tesla price\. I will also send you alert when tesla car are at a price under %d€`, alert.PriceAlert), &helpMenu, ) } func price(c tele.Context) error { availabilities, err := apiClient.GetAvailabilities(context.Background(), &api.AvailabilityParams{ Query: carFilter, Count: 100, }) if err != nil { log.Warnf("Fail to retrieve availability from tesla website. Error: %s", err.Error()) return c.Send("Fail to retrieve availability from tesla website :(") } var longRange api.Availability var propulsion api.Availability for _, availability := range availabilities.Results { if longRange.TrimName == "" && availability.Trim[0] == "LRAWD" { longRange = availability } if propulsion.TrimName == "" && availability.Trim[0] == "SRRWD" { propulsion = availability } if propulsion.TrimName != "" && longRange.TrimName != "" { break } } var msg string if propulsion.TrimName == "" { msg = "There is no Tesla Propulsion available\n" } else { msg = fmt.Sprintf("The lowest price currently found for a *%s* in *%s* color is: *%d*€ [View](%s)\n", cleanString(propulsion.TrimName), cleanString(strings.Join(propulsion.Paint, " and ")), propulsion.Price, propulsion.GetOrderLink(), ) } if longRange.TrimName == "" { msg = fmt.Sprintf("%sThere is no Tesla Long Range available", msg) } else { msg = fmt.Sprintf("%sThe lowest price currently found for a *%s* in *%s* color is: *%d*€ [View](%s)\n", msg, cleanString(longRange.TrimName), cleanString(strings.Join(longRange.Paint, " and ")), longRange.Price, longRange.GetOrderLink(), ) } return c.Reply( msg, &homeMenu, ) } func list(c tele.Context) error { availabilities, err := apiClient.GetAvailabilities(context.Background(), &api.AvailabilityParams{ Query: carFilter, Count: 30, }) if err != nil { log.Warnf("Fail to retrieve availability from tesla website. Error: %s", err.Error()) return c.Send("Fail to retrieve availability from tesla website :(") } availabilitiesStr := fmt.Sprintf("Found *%s* cars\n", availabilities.TotalMatchesFound) for _, availability := range availabilities.Results { demoStr := "" if availability.IsDemo { demoStr = fmt.Sprintf("Demo \\(%d %s\\) ", availability.Odometer, availability.OdometerTypeShort) } availabilitiesStr = fmt.Sprintf( "%s%s%s, color *%s* available in *%s* for *%d*€ [View](%s)\n", availabilitiesStr, demoStr, cleanString(availability.TrimName), cleanString(strings.Join(availability.Paint, " and ")), cleanString(availability.City), availability.Price, availability.GetOrderLink(), ) } return c.Reply(availabilitiesStr, &homeMenu) } func cleanString(in string) string { for _, str := range specialChars { in = strings.ReplaceAll(in, str, fmt.Sprintf("\\%s", str)) } return in }