mirror of
https://github.com/aykhans/azal-bot.git
synced 2025-12-13 08:39:20 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dd151bc10 | |||
| 1efac78b98 | |||
| 8cd132d8fa | |||
| 8b1ec13e78 |
196
main.go
196
main.go
@@ -14,12 +14,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RequestURL = "https://azal.az/book/api/flights/search/by-deeplink"
|
RequestURL = "https://azal.az/book/api/flights/search/by-deeplink"
|
||||||
Version = "0.1.0"
|
TelegramAPIURL = "https://api.telegram.org/bot%s/sendMessage"
|
||||||
|
Version = "0.2.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrorNoFlightsAvailable = fmt.Errorf("no flights available")
|
ErrorNoFlightsAvailable = fmt.Errorf("no flights available")
|
||||||
|
ErrorFlowInterrupted = fmt.Errorf("flow interrupted")
|
||||||
)
|
)
|
||||||
|
|
||||||
var Colors = struct {
|
var Colors = struct {
|
||||||
@@ -50,7 +52,84 @@ func Colored(color string, a ...any) string {
|
|||||||
return color + fmt.Sprint(a...) + Colors.reset
|
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 {
|
type UserInput struct {
|
||||||
FirstDate time.Time
|
FirstDate time.Time
|
||||||
@@ -89,12 +168,14 @@ func (responseTime *ResponseTime) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
type SuccessResponse struct {
|
type SuccessResponse struct {
|
||||||
Warnings []any `json:"warnings"`
|
Warnings []any `json:"warnings"`
|
||||||
Search struct {
|
Search struct {
|
||||||
OptionSets []struct {
|
OptionSets []struct {
|
||||||
Options []struct {
|
Options []struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
Route struct {
|
CheapestEconomySolutionId string `json:"cheapestEconomySolutionId"`
|
||||||
|
CheapestBusinessSolutionId string `json:"cheapestBusinessSolutionId"`
|
||||||
|
Route struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
DepartureDate ResponseTime `json:"departureDate"`
|
DepartureDate ResponseTime `json:"departureDate"`
|
||||||
} `json:"route"`
|
} `json:"route"`
|
||||||
@@ -245,12 +326,14 @@ func handleErrorResponse(errorResponse *ErrorResponse) error {
|
|||||||
switch errorResponse.Error.Code {
|
switch errorResponse.Error.Code {
|
||||||
case "no.flights.available":
|
case "no.flights.available":
|
||||||
return ErrorNoFlightsAvailable
|
return ErrorNoFlightsAvailable
|
||||||
|
case "flow.interrupted.error":
|
||||||
|
return ErrorFlowInterrupted
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown error: %s", errorResponse.Error.Code)
|
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)
|
req, err := http.NewRequest("GET", RequestURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -259,7 +342,6 @@ func sendRequest(queryConf *QueryConfig, headerConf *HeaderConfig) (*SuccessResp
|
|||||||
headerConf.setToRequest(req)
|
headerConf.setToRequest(req)
|
||||||
queryConf.setToRequest(req)
|
queryConf.setToRequest(req)
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -403,43 +485,7 @@ func getUserInput() *UserInput {
|
|||||||
return userInput
|
return userInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendTelegramMessage(avialableFlights AvialableFlights, botKey string, chatID string) error {
|
func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableFlights) error, ifError func(err error) 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) {
|
|
||||||
queryConf := QueryConfig{
|
queryConf := QueryConfig{
|
||||||
From: botConfig.From,
|
From: botConfig.From,
|
||||||
To: botConfig.To,
|
To: botConfig.To,
|
||||||
@@ -448,17 +494,24 @@ func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableF
|
|||||||
headerConf := HeaderConfig{}
|
headerConf := HeaderConfig{}
|
||||||
headerConf.setDefaults()
|
headerConf.setDefaults()
|
||||||
|
|
||||||
|
sendRequestClient := &http.Client{}
|
||||||
for {
|
for {
|
||||||
avialableFlights := make(AvialableFlights)
|
avialableFlights := make(AvialableFlights)
|
||||||
for _, day := range botConfig.days {
|
for _, day := range botConfig.days {
|
||||||
queryConf.DepartureDate = day
|
queryConf.DepartureDate = day
|
||||||
data, err := sendRequest(&queryConf, &headerConf)
|
data, err := sendRequest(sendRequestClient, &queryConf, &headerConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrorNoFlightsAvailable {
|
switch err {
|
||||||
|
case ErrorNoFlightsAvailable:
|
||||||
log.Println(Colored(Colors.Yellow, "No flights available for ", day))
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,8 +524,25 @@ func startBot(botConfig *BotConfig, ifAvailable func(avialableFlights AvialableF
|
|||||||
if (departureDate.After(botConfig.FirstDate) || departureDate.Equal(botConfig.FirstDate)) &&
|
if (departureDate.After(botConfig.FirstDate) || departureDate.Equal(botConfig.FirstDate)) &&
|
||||||
(departureDate.Before(botConfig.LastDate) || departureDate.Equal(botConfig.LastDate)) {
|
(departureDate.Before(botConfig.LastDate) || departureDate.Equal(botConfig.LastDate)) {
|
||||||
|
|
||||||
avialableFlights[day] = append(avialableFlights[day], departureDate.Time)
|
avialableFlights[day] = append(
|
||||||
log.Println(Colored(Colors.Green, "Flight available for ", departureDate))
|
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 {
|
} else {
|
||||||
log.Println(Colored(Colors.Yellow, "No flights available for ", departureDate))
|
log.Println(Colored(Colors.Yellow, "No flights available for ", departureDate))
|
||||||
}
|
}
|
||||||
@@ -499,14 +569,25 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ifAvailableFunc := func(avialableFlights AvialableFlights) error { return nil }
|
ifAvailableFunc := func(avialableFlights AvialableFlights) error { return nil }
|
||||||
|
ifErrorFunc := func(err error) error { return nil }
|
||||||
if userInput.TelegramBotKey != "" {
|
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 {
|
ifAvailableFunc = func(avialableFlights AvialableFlights) error {
|
||||||
if len(avialableFlights) > 0 {
|
if len(avialableFlights) > 0 {
|
||||||
return sendTelegramMessage(
|
return telegramRequest.sendTelegramFlightNotification(avialableFlights)
|
||||||
avialableFlights,
|
}
|
||||||
userInput.TelegramBotKey,
|
return nil
|
||||||
userInput.TelegramChatID,
|
}
|
||||||
)
|
ifErrorFunc = func(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return telegramRequest.sendTelegramErrorNotification(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -515,5 +596,6 @@ func main() {
|
|||||||
startBot(
|
startBot(
|
||||||
botConfig,
|
botConfig,
|
||||||
ifAvailableFunc,
|
ifAvailableFunc,
|
||||||
|
ifErrorFunc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user