|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"dcdev.ro/CfrTrainInfoTelegramBot/pkg/utils"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"dcdev.ro/CfrTrainInfoTelegramBot/pkg/api"
|
|
|
|
"github.com/go-telegram/bot"
|
|
|
|
"github.com/go-telegram/bot/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
TrainInfoChooseDateCallbackQuery = "TI_CHOOSE_DATE"
|
|
|
|
TrainInfoChooseGroupCallbackQuery = "TI_CHOOSE_GROUP"
|
|
|
|
TrainInfoSubscribeCallbackQuery = "TI_SUB"
|
|
|
|
TrainInfoUnsubscribeCallbackQuery = "TI_UNSUB"
|
|
|
|
|
|
|
|
viewInKaiBaseUrl = "https://kai.infotren.dcdev.ro/view-train.html"
|
|
|
|
|
|
|
|
subscribeButton = "Subscribe to updates"
|
|
|
|
unsubscribeButton = "Unsubscribe from updates"
|
|
|
|
openInWebAppButton = "Open in WebApp"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
TrainInfoResponseButtonExcludeSub = iota
|
|
|
|
TrainInfoResponseButtonIncludeSub
|
|
|
|
TrainInfoResponseButtonIncludeUnsub
|
|
|
|
)
|
|
|
|
|
|
|
|
func HandleTrainNumberCommand(ctx context.Context, trainNumber string, date time.Time, groupIndex int, isSubscribed bool) (*HandlerResponse, bool) {
|
|
|
|
trainData, err := api.GetTrain(ctx, trainNumber, date)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
break
|
|
|
|
case errors.Is(err, api.TrainNotFound):
|
|
|
|
log.Printf("ERROR: In handle train number: %s", err.Error())
|
|
|
|
return &HandlerResponse{
|
|
|
|
Message: &bot.SendMessageParams{
|
|
|
|
Text: fmt.Sprintf("The train %s was not found.", trainNumber),
|
|
|
|
},
|
|
|
|
ShouldUnsubscribe: func() bool {
|
|
|
|
now := time.Now().In(utils.Location)
|
|
|
|
midnightYesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, utils.Location)
|
|
|
|
return date.Before(midnightYesterday)
|
|
|
|
}(),
|
|
|
|
}, false
|
|
|
|
case errors.Is(err, api.ServerError):
|
|
|
|
log.Printf("ERROR: In handle train number: %s", err.Error())
|
|
|
|
return &HandlerResponse{
|
|
|
|
Message: &bot.SendMessageParams{
|
|
|
|
Text: fmt.Sprintf("Unknown server error when searching for train %s.", trainNumber),
|
|
|
|
},
|
|
|
|
ShouldUnsubscribe: func() bool {
|
|
|
|
now := time.Now().In(utils.Location)
|
|
|
|
midnightYesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, utils.Location)
|
|
|
|
return date.Before(midnightYesterday)
|
|
|
|
}(),
|
|
|
|
}, false
|
|
|
|
default:
|
|
|
|
log.Printf("ERROR: In handle train number: %s", err.Error())
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(trainData.Groups) == 1 {
|
|
|
|
groupIndex = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
shouldUnsubscribe := func() bool {
|
|
|
|
if groupIndex == -1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if len(trainData.Groups) <= groupIndex {
|
|
|
|
groupIndex = 0
|
|
|
|
}
|
|
|
|
now := time.Now().In(utils.Location)
|
|
|
|
midnightYesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, utils.Location)
|
|
|
|
lastStation := trainData.Groups[groupIndex].
|
|
|
|
Stations[len(trainData.Groups[groupIndex].Stations)-1]
|
|
|
|
if now.After(lastStation.Arrival.
|
|
|
|
ScheduleTime.Add(time.Hour * 6)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if trainData.Groups[groupIndex].
|
|
|
|
Status != nil && trainData.Groups[groupIndex].
|
|
|
|
Status.Station == lastStation.Name &&
|
|
|
|
trainData.Groups[groupIndex].Status.
|
|
|
|
State == "arrival" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if date.Before(midnightYesterday) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}()
|
|
|
|
|
|
|
|
message := bot.SendMessageParams{}
|
|
|
|
if groupIndex == -1 {
|
|
|
|
message.Text = fmt.Sprintf("Train %s %s contains multiple groups. Please choose one.", trainData.Rank, trainData.Number)
|
|
|
|
replyButtons := make([][]models.InlineKeyboardButton, 0, len(trainData.Groups)+1)
|
|
|
|
for i, group := range trainData.Groups {
|
|
|
|
replyButtons = append(replyButtons, []models.InlineKeyboardButton{
|
|
|
|
{
|
|
|
|
Text: fmt.Sprintf("%s ➔ %s", group.Route.From, group.Route.To),
|
|
|
|
CallbackData: fmt.Sprintf(TrainInfoChooseGroupCallbackQuery+"\x1b%s\x1b%d\x1b%d", trainNumber, date.Unix(), i),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
kaiUrl, _ := url.Parse(viewInKaiBaseUrl)
|
|
|
|
kaiUrlQuery := kaiUrl.Query()
|
|
|
|
kaiUrlQuery.Add("train", trainData.Number)
|
|
|
|
kaiUrlQuery.Add("date", trainData.Groups[0].Stations[0].Departure.ScheduleTime.Format(time.RFC3339))
|
|
|
|
kaiUrl.RawQuery = kaiUrlQuery.Encode()
|
|
|
|
replyButtons = append(replyButtons, []models.InlineKeyboardButton{
|
|
|
|
{
|
|
|
|
Text: "Open in WebApp",
|
|
|
|
URL: kaiUrl.String(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
message.ReplyMarkup = models.InlineKeyboardMarkup{
|
|
|
|
InlineKeyboard: replyButtons,
|
|
|
|
}
|
|
|
|
} else if len(trainData.Groups) > groupIndex {
|
|
|
|
group := &trainData.Groups[groupIndex]
|
|
|
|
|
|
|
|
messageText := strings.Builder{}
|
|
|
|
messageText.WriteString(fmt.Sprintf("Train %s %s\n%s ➔ %s\n\n", trainData.Rank, trainData.Number, group.Route.From, group.Route.To))
|
|
|
|
|
|
|
|
messageText.WriteString(fmt.Sprintf("Date: %s\n", trainData.Date))
|
|
|
|
messageText.WriteString(fmt.Sprintf("Operator: %s\n", trainData.Operator))
|
|
|
|
nextStopIdx := -1
|
|
|
|
for i, station := range group.Stations {
|
|
|
|
if station.Arrival != nil && time.Now().Before(station.Arrival.ScheduleTime.Add(func() time.Duration {
|
|
|
|
if station.Arrival.Status != nil {
|
|
|
|
return time.Minute * time.Duration(station.Arrival.Status.Delay)
|
|
|
|
} else {
|
|
|
|
return time.Nanosecond * 0
|
|
|
|
}
|
|
|
|
}())) {
|
|
|
|
nextStopIdx = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if station.Departure != nil && time.Now().Before(station.Departure.ScheduleTime.Add(func() time.Duration {
|
|
|
|
if station.Departure.Status != nil {
|
|
|
|
return time.Minute * time.Duration(station.Departure.Status.Delay)
|
|
|
|
} else {
|
|
|
|
return time.Nanosecond * 0
|
|
|
|
}
|
|
|
|
}())) {
|
|
|
|
nextStopIdx = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if nextStopIdx != -1 {
|
|
|
|
nextStop := &group.Stations[nextStopIdx]
|
|
|
|
arrTime := func() *time.Time {
|
|
|
|
if nextStop.Arrival == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if nextStop.Arrival.Status != nil {
|
|
|
|
result := nextStop.Arrival.ScheduleTime.Add(time.Minute * time.Duration(nextStop.Arrival.Status.Delay))
|
|
|
|
return &result
|
|
|
|
}
|
|
|
|
return &nextStop.Arrival.ScheduleTime
|
|
|
|
}()
|
|
|
|
if arrTime != nil && time.Now().Before(*arrTime) {
|
|
|
|
arrStr := "less than 1m"
|
|
|
|
arrDiff := arrTime.Sub(time.Now())
|
|
|
|
if arrDiff/time.Hour >= 1 {
|
|
|
|
arrStr = fmt.Sprintf("%dh%dm", arrDiff/time.Hour, (arrDiff%time.Hour)/time.Minute)
|
|
|
|
} else if arrDiff/time.Minute >= 1 {
|
|
|
|
arrStr = fmt.Sprintf("%dm", arrDiff/time.Minute)
|
|
|
|
}
|
|
|
|
messageText.WriteString(fmt.Sprintf("Next stop: %s, arriving in %s at %s\n", nextStop.Name, arrStr, arrTime.In(utils.Location).Format("15:04")))
|
|
|
|
} else {
|
|
|
|
depStr := "less than 1m"
|
|
|
|
depDiff := nextStop.Departure.ScheduleTime.Add(func() time.Duration {
|
|
|
|
if nextStop.Departure.Status != nil {
|
|
|
|
return time.Minute * time.Duration(nextStop.Departure.Status.Delay)
|
|
|
|
} else {
|
|
|
|
return time.Nanosecond * 0
|
|
|
|
}
|
|
|
|
}()).Sub(time.Now())
|
|
|
|
if depDiff/time.Hour >= 1 {
|
|
|
|
depStr = fmt.Sprintf("%dh%dm", depDiff/time.Hour, (depDiff%time.Hour)/time.Minute)
|
|
|
|
} else if depDiff/time.Minute >= 1 {
|
|
|
|
depStr = fmt.Sprintf("%dm", depDiff/time.Minute)
|
|
|
|
}
|
|
|
|
messageText.WriteString(fmt.Sprintf("Currently stopped at: %s, departing in %s\n", nextStop.Name, depStr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if group.Status != nil {
|
|
|
|
messageText.WriteString("Status: ")
|
|
|
|
if group.Status.Delay == 0 {
|
|
|
|
messageText.WriteString("on time when ")
|
|
|
|
} else {
|
|
|
|
messageText.WriteString(fmt.Sprintf("%d min ", func(x int) int {
|
|
|
|
if x < 0 {
|
|
|
|
return -x
|
|
|
|
} else {
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
}(group.Status.Delay)))
|
|
|
|
if group.Status.Delay < 0 {
|
|
|
|
messageText.WriteString("early when ")
|
|
|
|
} else {
|
|
|
|
messageText.WriteString("late when ")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch group.Status.State {
|
|
|
|
case "arrival":
|
|
|
|
messageText.WriteString("arriving at ")
|
|
|
|
case "departure":
|
|
|
|
messageText.WriteString("departing from ")
|
|
|
|
case "passing":
|
|
|
|
messageText.WriteString("passing through ")
|
|
|
|
}
|
|
|
|
messageText.WriteString(group.Status.Station)
|
|
|
|
messageText.WriteString("\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
message.Text = messageText.String()
|
|
|
|
message.Entities = []models.MessageEntity{
|
|
|
|
{
|
|
|
|
Type: models.MessageEntityTypeBold,
|
|
|
|
Offset: 6,
|
|
|
|
Length: len(fmt.Sprintf("%s %s", trainData.Rank, trainData.Number)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
buttonKind := TrainInfoResponseButtonIncludeSub
|
|
|
|
if shouldUnsubscribe {
|
|
|
|
buttonKind = TrainInfoResponseButtonExcludeSub
|
|
|
|
} else if isSubscribed {
|
|
|
|
buttonKind = TrainInfoResponseButtonIncludeUnsub
|
|
|
|
}
|
|
|
|
message.ReplyMarkup = GetTrainNumberCommandResponseButtons(trainData.Number, group.Stations[0].Departure.ScheduleTime, groupIndex, buttonKind)
|
|
|
|
} else {
|
|
|
|
message.Text = fmt.Sprintf("The status of the train %s %s is unknown.", trainData.Rank, trainData.Number)
|
|
|
|
message.Entities = []models.MessageEntity{
|
|
|
|
{
|
|
|
|
Type: models.MessageEntityTypeBold,
|
|
|
|
Offset: 24,
|
|
|
|
Length: len(fmt.Sprintf("%s %s", trainData.Rank, trainData.Number)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
message.ReplyMarkup = GetTrainNumberCommandResponseButtons(trainData.Number, trainData.Groups[0].Stations[0].Departure.ScheduleTime, groupIndex, TrainInfoResponseButtonExcludeSub)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &HandlerResponse{
|
|
|
|
Message: &message,
|
|
|
|
ShouldUnsubscribe: shouldUnsubscribe,
|
|
|
|
}, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetTrainNumberCommandResponseButtons(trainNumber string, date time.Time, groupIndex int, responseButton int) models.ReplyMarkup {
|
|
|
|
kaiUrl, _ := url.Parse(viewInKaiBaseUrl)
|
|
|
|
kaiUrlQuery := kaiUrl.Query()
|
|
|
|
kaiUrlQuery.Add("train", trainNumber)
|
|
|
|
kaiUrlQuery.Add("date", date.Format(time.RFC3339))
|
|
|
|
if groupIndex != -1 {
|
|
|
|
kaiUrlQuery.Add("groupIndex", strconv.Itoa(groupIndex))
|
|
|
|
}
|
|
|
|
kaiUrl.RawQuery = kaiUrlQuery.Encode()
|
|
|
|
|
|
|
|
result := make([][]models.InlineKeyboardButton, 0)
|
|
|
|
if responseButton == TrainInfoResponseButtonIncludeSub {
|
|
|
|
result = append(result, []models.InlineKeyboardButton{
|
|
|
|
{
|
|
|
|
Text: subscribeButton,
|
|
|
|
CallbackData: fmt.Sprintf(TrainInfoSubscribeCallbackQuery+"\x1b%s\x1b%d\x1b%d", trainNumber, date.Unix(), groupIndex),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else if responseButton == TrainInfoResponseButtonIncludeUnsub {
|
|
|
|
result = append(result, []models.InlineKeyboardButton{
|
|
|
|
{
|
|
|
|
Text: unsubscribeButton,
|
|
|
|
CallbackData: fmt.Sprintf(TrainInfoUnsubscribeCallbackQuery+"\x1b%s\x1b%d\x1b%d", trainNumber, date.Unix(), groupIndex),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
result = append(result, []models.InlineKeyboardButton{
|
|
|
|
{
|
|
|
|
Text: openInWebAppButton,
|
|
|
|
URL: kaiUrl.String(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return models.InlineKeyboardMarkup{
|
|
|
|
InlineKeyboard: result,
|
|
|
|
}
|
|
|
|
}
|