From 300e7862932ebc411c23fad251ed84a579b3da85 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 7 Mar 2026 20:42:51 +0000 Subject: [PATCH] celebratoday.go: parse out holidays via another method --- internal/celebratoday/celebratoday.go | 179 +++++++++++++------------- 1 file changed, 87 insertions(+), 92 deletions(-) diff --git a/internal/celebratoday/celebratoday.go b/internal/celebratoday/celebratoday.go index d92d71f..af3649c 100644 --- a/internal/celebratoday/celebratoday.go +++ b/internal/celebratoday/celebratoday.go @@ -6,9 +6,9 @@ import ( "aleesa-webapp-go/internal/log" "aleesa-webapp-go/internal/pcachedb" "context" + "encoding/json" "errors" "fmt" - "io" "slices" "strconv" "strings" @@ -18,6 +18,30 @@ import ( "golang.org/x/net/html" ) +type ( + // Holy структура, описывающая искомый json-чик. + Holy struct { + AtContext string `json:"@context"` + AtType string `json:"@type"` + MainEntity []struct { + AtType string `json:"@type"` + Name string `json:"name"` + AcceptedAnswer struct { + AtType string `json:"@type"` + Text string `json:"text"` + UpVoteCount int `json:"upvoteCount"` + URL string `json:"url,omitempty"` + } `json:"acceptedAnswer"` + SuggestedAnswer []struct { + AtType string `json:"@type"` + Text string `json:"text"` + UpVoteCount int `json:"upvoteCount"` + URL string `json:"url,omitempty"` + } `json:"suggestedAnswer"` + } `json:"mainEntity"` + } +) + // Client обёртка для клиента сервиса celebratoday.com, работающая с кэшем. Запросы надо делать // именно через неё. func Client(cfg *config.MyConfig) (string, error) { @@ -98,11 +122,9 @@ func Client(cfg *config.MyConfig) (string, error) { // APIClient клиент сервиса https://celebratoday.com/. func APIClient(cfg *config.MyConfig) (string, error) { var ( - ctx = context.Background() - url = "https://celebratoday.com/ru/today" - respBody string - orangeClass int8 - tgHref int8 + ctx = context.Background() + url = "https://celebratoday.com/ru/today" + respBody string ) // Формальная попытка притвориться браузером. @@ -117,93 +139,10 @@ func APIClient(cfg *config.MyConfig) (string, error) { return "", fmt.Errorf("unable to GET %s: %w", url, err) } - var holidays []string + holidays, err := parser(respBody) - // Парсим body ответа от сервера. - tokenizer := html.NewTokenizer(strings.NewReader(respBody)) - - for { - tokenType := tokenizer.Next() - - if tokenType == html.ErrorToken { - err := tokenizer.Err() - - if errors.Is(err, io.EOF) { - // End of the file, break out of the loop. - break - } - - return "", err - } - - if tokenType == html.StartTagToken { - token := tokenizer.Token() - - if token.Data == "a" { - for _, attribute := range token.Attr { - // Кнопка о том, что у нас было вчера. - if attribute.Key == "class" && attribute.Val == "text-orange-500 underline hover:no-underline" { - orangeClass++ - - continue - } - - // Наши ссылки находятся после первого и до второго упоминания о телеграме. - if attribute.Key == "href" && attribute.Val == "https://t.me/celebratoday" { - tgHref++ - - continue - } - - if attribute.Key == "title" && tgHref == 1 && orangeClass == 2 { - switch { - case strings.HasPrefix(attribute.Val, "Праздники "): - continue - - case strings.HasPrefix(attribute.Val, "Международные праздники "): - continue - - case strings.HasPrefix(attribute.Val, "Всемирные праздники "): - continue - - case strings.HasPrefix(attribute.Val, "Спортивные праздники "): - continue - - case strings.HasPrefix(attribute.Val, "Медицинские праздники"): - continue - - case strings.HasPrefix(attribute.Val, "Подробнее про"): - continue - - case strings.HasPrefix(attribute.Val, "Необычные праздники"): - continue - - case strings.HasPrefix(attribute.Val, "Профессиональные праздники"): - continue - - case strings.HasPrefix(attribute.Val, "Национальные праздники"): - continue - - case strings.HasPrefix(attribute.Val, "Народные праздники"): - continue - - case strings.HasPrefix(attribute.Val, "Религиозные праздники"): - continue - - default: - holidays = append(holidays, strings.TrimSuffix(attribute.Val, " | celebratoday.com")) - } - - continue - } - } - } - } - - // Если мы встретили вторую ссылку на телеграм, то конечная, дальше нам не надо. - if tgHref >= 2 { - break - } + if err != nil { + return "", fmt.Errorf("Unable to parse response from celebratoday.com: %w", err) } if len(holidays) == 0 { @@ -235,4 +174,60 @@ func UpdateCache(cfg *config.MyConfig, eTime int64, value string) error { return nil } +// parser разбирает буфер с html-ем, выискивая праздники. +func parser(data string) ([]string, error) { + var ( + holidays = make([]string, 0, 1) + err error + s = make([]Holy, 0, 1) + ) + + doc, err := html.Parse(strings.NewReader(data)) + + if err != nil { + return holidays, err + } + + traverse(doc, &s) + + // А теперь пробежимся по полученной структуре и вытянем из неё все строки с праздниками. + for _, ass := range s { + for _, entity := range ass.MainEntity { + for _, sugestedAnswer := range entity.SuggestedAnswer { + holidays = append(holidays, sugestedAnswer.Text) + } + } + } + + return holidays, err +} + +// traverse рекурсивная функция, ищущая