113 lines
3.8 KiB
Go
113 lines
3.8 KiB
Go
// Package holy implements main business logic of bot service.
|
|
package holy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/carlmjohnson/requests"
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
// APIClient клиент сервиса https://celebratoday.com/.
|
|
func APIClient() (string, error) {
|
|
var (
|
|
ctx = context.Background()
|
|
url = "https://celebratoday.com/ru/today"
|
|
respBody string
|
|
)
|
|
|
|
// Формальная попытка притвориться браузером.
|
|
_, _, day := time.Now().Date()
|
|
userAgent := Config.UserAgents[day]
|
|
|
|
if err := requests.URL(url).UserAgent(userAgent).
|
|
Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
|
|
Header("Accept-Language", "en-US,en;q=0.5").
|
|
Header("User-Agent", "ru-RU").
|
|
Header("Accept-Charset", "utf-8").ToString(&respBody).Fetch(ctx); err != nil {
|
|
return "", fmt.Errorf("Unable to GET %s: %w", url, err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
|
}
|
|
|
|
holidays, err := parser(respBody)
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unable to parse response from celebratoday.com: %w", err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
|
}
|
|
|
|
if len(holidays) == 0 {
|
|
return "", errors.New("Unable to parse response from celebratoday.com: no holidays found") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
|
}
|
|
|
|
slices.Sort(holidays)
|
|
|
|
holidays = slices.Compact(holidays)
|
|
|
|
return "* " + strings.Join(holidays, "\n* "), 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 {
|
|
holidays = append(holidays, entity.AcceptedAnswer.Text)
|
|
|
|
for _, sugestedAnswer := range entity.SuggestedAnswer {
|
|
holidays = append(holidays, sugestedAnswer.Text)
|
|
}
|
|
}
|
|
}
|
|
|
|
return holidays, err
|
|
}
|
|
|
|
// traverse рекурсивная функция, ищущая <script type="application/ld+json"> в структуре документа. Там внутри будет json
|
|
// со списком праздников и это и есть цель нашего поиска.
|
|
func traverse(n *html.Node, h *[]Holy) {
|
|
// Если найденный элемент в поле Data содержит script (то есть это тэг <script>).
|
|
if n.Type == html.ElementNode && n.Data == "script" {
|
|
// Ищем атрибут type со значением application/ld+json.
|
|
for _, attr := range n.Attr {
|
|
if attr.Key == "type" && attr.Val == "application/ld+json" {
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
if c.Type == html.TextNode {
|
|
// Добавлять рановато, надо вначале попробовать распарсить этот json
|
|
// используя целевую структуру.
|
|
var holiday Holy
|
|
|
|
if err := json.Unmarshal([]byte(c.Data), &holiday); err == nil {
|
|
*h = append(*h, holiday)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
traverse(c, h)
|
|
}
|
|
}
|
|
|
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|