2023-06-05 13:41:48 +00:00
|
|
|
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 {
|
2023-06-19 21:04:59 +00:00
|
|
|
|
2023-06-05 13:41:48 +00:00
|
|
|
for _, chatId := range config.AlertChatIds {
|
|
|
|
log.Infof("Sending alert to chat %d", chatId)
|
|
|
|
|
2023-06-26 07:59:36 +00:00
|
|
|
demoStr := ""
|
|
|
|
if availability.IsDemo {
|
|
|
|
demoStr = fmt.Sprintf("Demo \\(%d %s\\) ", availability.Odometer, availability.OdometerTypeShort)
|
|
|
|
}
|
|
|
|
|
2023-06-05 13:41:48 +00:00
|
|
|
_, err := bot.Send(
|
|
|
|
tele.ChatID(chatId),
|
|
|
|
fmt.Sprintf(
|
2023-06-26 07:59:36 +00:00
|
|
|
"ALERT\\!\\! Found a %s%s in *%s* color is: *%d*€ [View](%s)",
|
|
|
|
demoStr,
|
2023-07-17 13:54:58 +00:00
|
|
|
cleanString(availability.TrimName),
|
|
|
|
cleanString(strings.Join(availability.Paint, " and ")),
|
2023-06-05 13:41:48 +00:00
|
|
|
availability.Price,
|
2023-06-05 20:35:55 +00:00
|
|
|
availability.GetOrderLink(),
|
2023-06-05 13:41:48 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
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(
|
2023-06-05 19:01:52 +00:00
|
|
|
middlewareAllowChat(config.WhiteListChatIds...),
|
2023-06-05 13:41:48 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-05 19:01:52 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 13:41:48 +00:00
|
|
|
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,
|
2023-06-19 21:04:59 +00:00
|
|
|
Count: 100,
|
2023-06-05 13:41:48 +00:00
|
|
|
})
|
|
|
|
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 :(")
|
|
|
|
}
|
|
|
|
|
2023-06-19 21:04:59 +00:00
|
|
|
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",
|
2023-07-17 13:54:58 +00:00
|
|
|
cleanString(propulsion.TrimName),
|
|
|
|
cleanString(strings.Join(propulsion.Paint, " and ")),
|
2023-06-19 21:04:59 +00:00
|
|
|
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,
|
2023-07-17 13:54:58 +00:00
|
|
|
cleanString(longRange.TrimName),
|
|
|
|
cleanString(strings.Join(longRange.Paint, " and ")),
|
2023-06-19 21:04:59 +00:00
|
|
|
longRange.Price,
|
|
|
|
longRange.GetOrderLink(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-05 13:41:48 +00:00
|
|
|
return c.Reply(
|
2023-06-19 21:04:59 +00:00
|
|
|
msg, &homeMenu,
|
2023-06-05 13:41:48 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2023-06-26 07:59:36 +00:00
|
|
|
|
|
|
|
demoStr := ""
|
|
|
|
if availability.IsDemo {
|
|
|
|
demoStr = fmt.Sprintf("Demo \\(%d %s\\) ", availability.Odometer, availability.OdometerTypeShort)
|
|
|
|
}
|
|
|
|
|
2023-06-05 13:41:48 +00:00
|
|
|
availabilitiesStr = fmt.Sprintf(
|
2023-06-26 07:59:36 +00:00
|
|
|
"%s%s%s, color *%s* available in *%s* for *%d*€ [View](%s)\n",
|
2023-06-05 13:41:48 +00:00
|
|
|
availabilitiesStr,
|
2023-06-26 07:59:36 +00:00
|
|
|
demoStr,
|
2023-07-17 13:54:58 +00:00
|
|
|
cleanString(availability.TrimName),
|
|
|
|
cleanString(strings.Join(availability.Paint, " and ")),
|
|
|
|
cleanString(availability.City),
|
2023-06-05 13:41:48 +00:00
|
|
|
availability.Price,
|
2023-06-05 20:35:55 +00:00
|
|
|
availability.GetOrderLink(),
|
2023-06-05 13:41:48 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-09 07:37:08 +00:00
|
|
|
return c.Reply(availabilitiesStr, &homeMenu)
|
2023-06-05 13:41:48 +00:00
|
|
|
}
|
2023-07-17 13:54:58 +00:00
|
|
|
|
|
|
|
func cleanString(in string) string {
|
|
|
|
for _, str := range specialChars {
|
|
|
|
in = strings.ReplaceAll(in, str, fmt.Sprintf("\\%s", str))
|
|
|
|
}
|
|
|
|
|
|
|
|
return in
|
|
|
|
}
|