You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

300 lines
9.7 KiB

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"
depTime := 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
}
}())
depDiff := depTime.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 at %s\n", nextStop.Name, depStr, depTime.In(utils.Location).Format("15:04")))
}
}
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,
}
}