4 Commits
v0.1.0 ... main

Author SHA1 Message Date
4dd151bc10 add flight classes to notifications 2024-08-30 22:34:11 +04:00
1efac78b98 🔖 Bump version to 0.2.0 2024-08-25 17:18:30 +04:00
8cd132d8fa 🔨 General refactoring 2024-08-25 17:16:30 +04:00
8b1ec13e78 🔨 Refactor Telegram message sending logic 2024-08-25 17:00:36 +04:00

196
main.go
View File

@@ -14,12 +14,14 @@ import (
)
const (
RequestURL = "https://azal.az/book/api/flights/search/by-deeplink"
Version = "0.1.0"
RequestURL = "https://azal.az/book/api/flights/search/by-deeplink"
TelegramAPIURL = "https://api.telegram.org/bot%s/sendMessage"
Version = "0.2.1"
)
var (
ErrorNoFlightsAvailable = fmt.Errorf("no flights available")
ErrorFlowInterrupted = fmt.Errorf("flow interrupted")
)
var Colors = struct {
@@ -50,7 +52,84 @@ func Colored(color string, a ...any) string {
return color + fmt.Sprint(a...) + Colors.reset
}
type AvialableFlights map[string][]time.Time
type AvialableFlight struct {
Economy bool
Business bool
DepartureDate time.Time
}
type AvialableFlights map[string][]AvialableFlight
type TelegramRequest struct {
Client *http.Client
BotKey string
ChatID string
}
func (telegramRequest *TelegramRequest) sendTelegramMessage(message string) error {
url := fmt.Sprintf(TelegramAPIURL, telegramRequest.BotKey)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
q := req.URL.Query()
q.Add("chat_id", telegramRequest.ChatID)
q.Add("text", message)
q.Add("parse_mode", "HTML")
req.URL.RawQuery = q.Encode()
resp, err := telegramRequest.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("error: telegram send message status code: %d", resp.StatusCode)
}
return nil
}
func (telegramRequest *TelegramRequest) sendTelegramFlightNotification(avialableFlights AvialableFlights) error {
message := "Azal Bot Flights\n\n"
for day, flights := range avialableFlights {
message += fmt.Sprintf("%s\n-----------\n", day)
for _, flight := range flights {
classes := ""
if flight.Economy {
classes += "Economy"
}
if flight.Business {
if classes != "" {
classes += ", "
}
classes += "Business"
}
message += fmt.Sprintf("%s (%s)\n", flight.DepartureDate.Format("15:04:05"), classes)
}
message += "\n"
}
message = message[:len(message)-1]
return telegramRequest.sendTelegramMessage(message)
}
func (telegramRequest *TelegramRequest) sendTelegramStartNotification(botConfig *BotConfig) error {
return telegramRequest.sendTelegramMessage(
fmt.Sprintf(
"Azal Bot started\n\nFrom: %s\nTo: %s\nFirst Date: %s\nLast Date: %s\nRepetition Interval: %s",
botConfig.From,
botConfig.To,
botConfig.FirstDate.Format("2006-01-02T15:04:05"),
botConfig.LastDate.Format("2006-01-02T15:04:05"),
botConfig.RepetInterval.String(),
),
)
}
func (telegramRequest *TelegramRequest) sendTelegramErrorNotification(err error) error {
return telegramRequest.sendTelegramMessage(fmt.Sprintf("Azal Bot Error: %s", err.Error()))
}
type UserInput struct {
FirstDate time.Time
@@ -89,12 +168,14 @@ func (responseTime *ResponseTime) UnmarshalJSON(b []byte) error {
type SuccessResponse struct {
Warnings []any `json:"warnings"`
Search struct {
Search struct {
OptionSets []struct {
Options []struct {
ID string `json:"id"`
Available bool `json:"available"`
Route struct {
ID string `json:"id"`
Available bool `json:"available"`
CheapestEconomySolutionId string `json:"cheapestEconomySolutionId"`
CheapestBusinessSolutionId string `json:"cheapestBusinessSolutionId"`
Route struct {
ID string `json:"id"`
DepartureDate ResponseTime `json:"departureDate"`
} `json:"route"`
@@ -245,12 +326,14 @@ func handleErrorResponse(errorResponse *ErrorResponse) error {
switch errorResponse.Error.Code {
case "no.flights.available":
return ErrorNoFlightsAvailable
case "flow.interrupted.error":
return ErrorFlowInterrupted
default:
return fmt.Errorf("unknown error: %s", errorResponse.Error.Code)
}
}
func sendRequest(queryConf *QueryConfig, headerConf *HeaderConfig) (*SuccessResponse, error) {
func sendRequest(client *http.Client, queryConf *QueryConfig, headerConf *HeaderConfig) (*SuccessResponse, error) {
req, err := http.NewRequest("GET", RequestURL, nil)
if err != nil {
return nil, err
@@ -259,7 +342,6 @@ func sendRequest(queryConf *QueryConfig, headerConf *HeaderConfig) (*SuccessResp
headerConf.setToRequest(req)
queryConf.setToRequest(req)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
@@ -403,43 +485,7 @@ func getUserInput() *UserInput {
return userInput
}
func sendTelegramMessage(avialableFlights AvialableFlights, botKey string, chatID string) error {
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botKey)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
message := "Azal Bot\n\n"
for day, flights := range avialableFlights {
message += fmt.Sprintf("%s\n-----------\n", day)
for _, flight := range flights {
message += fmt.Sprintf("%s\n", flight.Format("15:04:05"))
}
message += "\n"
}
message = message[:len(message)-1]
q := req.URL.Query()
q.Add("chat_id", chatID)
q.Add("text", message)
q.Add("parse_mode", "HTML")
req.URL.RawQuery = q.Encode()
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("telegram send message status code: %d", resp.StatusCode)
}
return nil
}
func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableFlights) error) {
func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableFlights) error, ifError func(err error) error) {
queryConf := QueryConfig{
From: botConfig.From,
To: botConfig.To,
@@ -448,17 +494,24 @@ func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableF
headerConf := HeaderConfig{}
headerConf.setDefaults()
sendRequestClient := &http.Client{}
for {
avialableFlights := make(AvialableFlights)
for _, day := range botConfig.days {
queryConf.DepartureDate = day
data, err := sendRequest(&queryConf, &headerConf)
data, err := sendRequest(sendRequestClient, &queryConf, &headerConf)
if err != nil {
if err == ErrorNoFlightsAvailable {
switch err {
case ErrorNoFlightsAvailable:
log.Println(Colored(Colors.Yellow, "No flights available for ", day))
continue
case ErrorFlowInterrupted:
log.Println(Colored(Colors.Red, "The date entered has passed: ", day))
if err := ifError(fmt.Errorf("the date entered has passed: %s", day)); err != nil {
log.Println(Colored(Colors.Red, err.Error()))
}
default:
log.Println(Colored(Colors.Red, err.Error()))
}
log.Println(Colored(Colors.Red, err.Error()))
continue
}
@@ -471,8 +524,25 @@ func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableF
if (departureDate.After(botConfig.FirstDate) || departureDate.Equal(botConfig.FirstDate)) &&
(departureDate.Before(botConfig.LastDate) || departureDate.Equal(botConfig.LastDate)) {
avialableFlights[day] = append(avialableFlights[day], departureDate.Time)
log.Println(Colored(Colors.Green, "Flight available for ", departureDate))
avialableFlights[day] = append(
avialableFlights[day],
AvialableFlight{
Economy: option.CheapestEconomySolutionId != "",
Business: option.CheapestBusinessSolutionId != "",
DepartureDate: departureDate.Time,
},
)
classes := ""
if option.CheapestEconomySolutionId != "" {
classes += "Economy"
}
if option.CheapestBusinessSolutionId != "" {
if classes != "" {
classes += ", "
}
classes += "Business"
}
log.Println(Colored(Colors.Green, "Flight available for ", departureDate, " (" + classes + ")"))
} else {
log.Println(Colored(Colors.Yellow, "No flights available for ", departureDate))
}
@@ -499,14 +569,25 @@ func main() {
}
ifAvailableFunc := func(avialableFlights AvialableFlights) error { return nil }
ifErrorFunc := func(err error) error { return nil }
if userInput.TelegramBotKey != "" {
telegramRequest := &TelegramRequest{
Client: &http.Client{},
BotKey: userInput.TelegramBotKey,
ChatID: userInput.TelegramChatID,
}
if err := telegramRequest.sendTelegramStartNotification(botConfig); err != nil {
log.Println(Colored(Colors.Red, err.Error()))
}
ifAvailableFunc = func(avialableFlights AvialableFlights) error {
if len(avialableFlights) > 0 {
return sendTelegramMessage(
avialableFlights,
userInput.TelegramBotKey,
userInput.TelegramChatID,
)
return telegramRequest.sendTelegramFlightNotification(avialableFlights)
}
return nil
}
ifErrorFunc = func(err error) error {
if err != nil {
return telegramRequest.sendTelegramErrorNotification(err)
}
return nil
}
@@ -515,5 +596,6 @@ func main() {
startBot(
botConfig,
ifAvailableFunc,
ifErrorFunc,
)
}