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.
299 lines
9.7 KiB
299 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" |
|
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, |
|
} |
|
}
|
|
|