Tesla/bot/bot.go
Matthieu 'JP' DERASSE 0825774d36
All checks were successful
continuous-integration/drone/push Build is passing
fix(forbidden): Lower ticker to avoid server forbidden
2023-07-20 18:23:11 +00:00

247 lines
6.1 KiB
Go

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
}