2023-09-27 04:31:02 +02:00
package handlers
import (
"context"
2023-12-26 04:35:28 +01:00
"dcdev.ro/CfrTrainInfoTelegramBot/pkg/utils"
2023-09-27 04:31:02 +02:00
"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"
2023-09-27 21:45:58 +02:00
TrainInfoSubscribeCallbackQuery = "TI_SUB"
TrainInfoUnsubscribeCallbackQuery = "TI_UNSUB"
2023-09-27 04:31:02 +02:00
viewInKaiBaseUrl = "https://kai.infotren.dcdev.ro/view-train.html"
2023-09-27 21:45:58 +02:00
subscribeButton = "Subscribe to updates"
unsubscribeButton = "Unsubscribe from updates"
2023-12-28 02:43:23 +01:00
viewInWebAppButton = "View in WebApp"
2023-09-27 21:45:58 +02:00
)
const (
TrainInfoResponseButtonExcludeSub = iota
TrainInfoResponseButtonIncludeSub
TrainInfoResponseButtonIncludeUnsub
2023-09-27 04:31:02 +02:00
)
2023-09-27 21:45:58 +02:00
func HandleTrainNumberCommand ( ctx context . Context , trainNumber string , date time . Time , groupIndex int , isSubscribed bool ) ( * HandlerResponse , bool ) {
2023-09-27 04:31:02 +02:00
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 ) ,
} ,
2023-12-26 04:35:04 +01:00
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 )
} ( ) ,
2023-09-27 21:45:58 +02:00
} , false
2023-09-27 04:31:02 +02:00
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 ) ,
} ,
2023-12-26 04:35:04 +01:00
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 )
} ( ) ,
2023-09-27 21:45:58 +02:00
} , false
2023-09-27 04:31:02 +02:00
default :
log . Printf ( "ERROR: In handle train number: %s" , err . Error ( ) )
2023-09-27 21:45:58 +02:00
return nil , false
2023-09-27 04:31:02 +02:00
}
if len ( trainData . Groups ) == 1 {
groupIndex = 0
}
2023-12-26 04:35:04 +01:00
shouldUnsubscribe := func ( ) bool {
2023-12-26 06:30:01 +01:00
if groupIndex == - 1 {
return false
}
2023-12-26 04:35:04 +01:00
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
} ( )
2023-09-27 04:31:02 +02:00
message := bot . SendMessageParams { }
if groupIndex == - 1 {
message . Text = fmt . Sprintf ( "Train %s %s contains multiple groups. Please choose one." , trainData . Rank , trainData . Number )
2023-09-27 21:45:58 +02:00
replyButtons := make ( [ ] [ ] models . InlineKeyboardButton , 0 , len ( trainData . Groups ) + 1 )
for i , group := range trainData . Groups {
replyButtons = append ( replyButtons , [ ] models . InlineKeyboardButton {
{
2023-09-27 04:31:02 +02:00
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 ) ,
2023-09-27 21:45:58 +02:00
} ,
} )
2023-09-27 04:31:02 +02:00
}
2023-09-27 21:45:58 +02:00
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 ( ) ,
} ,
} )
2023-09-27 04:31:02 +02:00
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 ) )
2023-12-26 04:35:28 +01:00
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"
2023-12-26 07:30:43 +01:00
depTime := nextStop . Departure . ScheduleTime . Add ( func ( ) time . Duration {
2023-12-26 04:35:28 +01:00
if nextStop . Departure . Status != nil {
return time . Minute * time . Duration ( nextStop . Departure . Status . Delay )
} else {
return time . Nanosecond * 0
}
2023-12-26 07:30:43 +01:00
} ( ) )
depDiff := depTime . Sub ( time . Now ( ) )
2023-12-26 04:35:28 +01:00
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 )
}
2023-12-26 07:30:43 +01:00
messageText . WriteString ( fmt . Sprintf ( "Currently stopped at: %s, departing in %s at %s\n" , nextStop . Name , depStr , depTime . In ( utils . Location ) . Format ( "15:04" ) ) )
2023-12-26 04:35:28 +01:00
}
}
2023-09-27 04:31:02 +02:00
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 ) ) ,
} ,
}
2023-09-27 21:45:58 +02:00
buttonKind := TrainInfoResponseButtonIncludeSub
2023-12-26 04:35:04 +01:00
if shouldUnsubscribe {
buttonKind = TrainInfoResponseButtonExcludeSub
} else if isSubscribed {
2023-09-27 21:45:58 +02:00
buttonKind = TrainInfoResponseButtonIncludeUnsub
2023-09-27 04:31:02 +02:00
}
2023-09-27 21:45:58 +02:00
message . ReplyMarkup = GetTrainNumberCommandResponseButtons ( trainData . Number , group . Stations [ 0 ] . Departure . ScheduleTime , groupIndex , buttonKind )
2023-09-27 04:31:02 +02:00
} 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 ) ) ,
} ,
}
2023-09-27 21:45:58 +02:00
message . ReplyMarkup = GetTrainNumberCommandResponseButtons ( trainData . Number , trainData . Groups [ 0 ] . Stations [ 0 ] . Departure . ScheduleTime , groupIndex , TrainInfoResponseButtonExcludeSub )
2023-09-27 04:31:02 +02:00
}
return & HandlerResponse {
2023-12-26 04:35:04 +01:00
Message : & message ,
ShouldUnsubscribe : shouldUnsubscribe ,
2023-09-27 21:45:58 +02:00
} , 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 {
{
2023-12-28 02:43:23 +01:00
Text : viewInWebAppButton ,
WebApp : & models . WebAppInfo {
URL : func ( ) string {
miniAppUrl := * kaiUrl
miniAppUrlQuery := miniAppUrl . Query ( )
miniAppUrlQuery . Add ( "tg" , "1" )
miniAppUrl . RawQuery = miniAppUrlQuery . Encode ( )
return miniAppUrl . String ( )
} ( ) ,
} ,
2023-09-27 21:45:58 +02:00
} ,
} )
return models . InlineKeyboardMarkup {
InlineKeyboard : result ,
2023-09-27 04:31:02 +02:00
}
}