diff --git a/.golangci.yml b/.golangci.yml index 741ac8e..5633122 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,9 @@ linters: linters-settings: lll: line-length: 150 + misspell: + ignore-words: + - autor issues: exclude-rules: diff --git a/go.mod b/go.mod index 31c1334..bff3b5b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,17 @@ module github.com/Kichiyaki/lubimyczytacrss go 1.18 + +require ( + github.com/PuerkitoBio/goquery v1.8.0 + github.com/go-chi/chi/v5 v5.0.7 + github.com/stretchr/testify v1.7.1 +) + +require ( + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7b8b357 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/lubimyczytac/client.go b/internal/lubimyczytac/client.go new file mode 100644 index 0000000..b6684bc --- /dev/null +++ b/internal/lubimyczytac/client.go @@ -0,0 +1,146 @@ +package lubimyczytac + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/PuerkitoBio/goquery" +) + +const ( + defaultBaseURL = "https://lubimyczytac.pl" +) + +var ( + ErrAuthorNotFound = errors.New("author not found") + ErrUnexpectedStatus = errors.New("unexpected http status was returned by lubimyczytac.pl") +) + +type Book struct { + Title string + URL string +} + +type Author struct { + ID string + Name string + ShortDescription string + URL string + Books []Book +} + +type Client struct { + http *http.Client + baseURL string +} + +type ClientOption func(c *Client) + +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) { + c.baseURL = baseURL + } +} + +func NewClient(client *http.Client, opts ...ClientOption) *Client { + c := &Client{http: client, baseURL: defaultBaseURL} + for _, opt := range opts { + opt(c) + } + return c +} + +func (c *Client) GetAuthor(ctx context.Context, id string) (Author, error) { + req, err := http.NewRequestWithContext( + ctx, + http.MethodGet, + fmt.Sprintf("%s/autor/%s/x", c.baseURL, id), + nil, + ) + if err != nil { + return Author{}, fmt.Errorf("http.NewRequestWithContext: %w", err) + } + + resp, err := c.http.Do(req) + if err != nil { + return Author{}, fmt.Errorf("httpClient.Do: %w", err) + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != http.StatusOK { + _, _ = io.Copy(io.Discard, resp.Body) + if resp.StatusCode == http.StatusNotFound { + return Author{}, ErrAuthorNotFound + } + return Author{}, ErrUnexpectedStatus + } + + p, err := newAuthorPageParser(resp.Body) + if err != nil { + _, _ = io.Copy(io.Discard, resp.Body) + return Author{}, fmt.Errorf("newAuthorPageParser: %w", err) + } + + return Author{ + ID: id, + Name: p.name(), + ShortDescription: p.shortDescription(), + URL: p.url(), + Books: p.books(), + }, nil +} + +type authorPageParser struct { + doc *goquery.Document + baseURL *url.URL +} + +func newAuthorPageParser(r io.Reader) (authorPageParser, error) { + doc, err := goquery.NewDocumentFromReader(r) + if err != nil { + return authorPageParser{}, fmt.Errorf("goquery.NewDocumentFromReader: %w", err) + } + baseURL, err := url.Parse(doc.Find("head base").AttrOr("href", "")) + if err != nil { + return authorPageParser{}, fmt.Errorf("url.Parse: %w", err) + } + return authorPageParser{ + doc: doc, + baseURL: baseURL, + }, nil +} + +func (p authorPageParser) name() string { + return strings.TrimSpace(p.doc.Find("#author-info .title-container").Text()) +} + +func (p authorPageParser) url() string { + return strings.TrimSpace(p.doc.Find(`meta[property="og:url"]`).AttrOr("content", "")) +} + +func (p authorPageParser) shortDescription() string { + return strings.TrimSpace(p.doc.Find(`meta[name="description"]`).AttrOr("content", "")) +} + +func (p authorPageParser) books() []Book { + booksSel := p.doc.Find("#authorBooks .authorAllBooks__single") + books := make([]Book, booksSel.Length()) + booksSel.Each(func(i int, selection *goquery.Selection) { + bookUrl := url.URL{ + Scheme: p.baseURL.Scheme, + Host: p.baseURL.Host, + Path: selection.Find(".authorAllBooks__singleTextTitle").AttrOr("href", ""), + } + books[i] = Book{ + Title: strings.TrimSpace(selection.Find(".authorAllBooks__singleTextTitle").Text()), + URL: bookUrl.String(), + } + }) + return books +} diff --git a/internal/lubimyczytac/client_test.go b/internal/lubimyczytac/client_test.go new file mode 100644 index 0000000..83ec801 --- /dev/null +++ b/internal/lubimyczytac/client_test.go @@ -0,0 +1,48 @@ +package lubimyczytac_test + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac/testdata" + + "github.com/stretchr/testify/assert" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac" +) + +func TestClient_GetAuthor(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + for _, author := range testdata.Authors { + author := author + + t.Run("authorID="+author.ID, func(t *testing.T) { + t.Parallel() + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != fmt.Sprintf("/autor/%s/x", author.ID) || r.Method != http.MethodGet { + w.WriteHeader(http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write(author.HTML) + })) + defer srv.Close() + + result, err := lubimyczytac. + NewClient(srv.Client(), lubimyczytac.WithBaseURL(srv.URL)). + GetAuthor(context.Background(), author.ID) + assert.NoError(t, err) + assert.Equal(t, author.Author, result) + }) + } + }) +} diff --git a/internal/lubimyczytac/testdata/john_flanagan.html b/internal/lubimyczytac/testdata/john_flanagan.html new file mode 100644 index 0000000..152e233 --- /dev/null +++ b/internal/lubimyczytac/testdata/john_flanagan.html @@ -0,0 +1 @@ +John Flanagan | Wszystkie książki, wywiady, artykuły | Lubimyczytać.pl
rozwińzwiń

John Flanagan

John Flanagan
Żródło zdjecia:
John Flanagan
Średnia ocena książek autora
7,8/107,8 / 10
  • 16 107 przeczytało książki autora
  • 38 338 chce przeczytać książki autora
1 840 fanów autora
pszczoolka
Uliana
Sylwia
Adam Król
Więcej
Reklama

DYSKUSJE

Tytuł posta
Wyświetleń
Postów
Ostatnia Aktywność

Książki i czasopisma

Sortuj:
Sortuj
Okładka książki Morska pogoń John Flanagan
Średnia ocen:
7,8 / 10
16 ocen
Czytelnicy: 80 Opinie: 3
Okładka książki Ruiny Gorlanu John Flanagan
Średnia ocen:
7,8 / 10
11018 ocen
Czytelnicy: 20116 Opinie: 1140
Okładka książki Ziemia skuta lodem John Flanagan
Średnia ocen:
7,8 / 10
7317 ocen
Czytelnicy: 12789 Opinie: 366
Okładka książki Bitwa o Skandię John Flanagan
Średnia ocen:
8,1 / 10
6899 ocen
Czytelnicy: 12288 Opinie: 352
Okładka książki Ucieczka z zamku Falaise John FlanaganPatronat LC
Średnia ocen:
7,6 / 10
226 ocen
Czytelnicy: 633 Opinie: 22
Okładka książki Płonący most John Flanagan
Średnia ocen:
7,9 / 10
8348 ocen
Czytelnicy: 14133 Opinie: 515
Reklama
Okładka książki Zaginiony książę John FlanaganPatronat LC
Średnia ocen:
7,4 / 10
375 ocen
Czytelnicy: 1116 Opinie: 39
Okładka książki Powrót Temudżeinów John Flanagan
Średnia ocen:
7,5 / 10
247 ocen
Czytelnicy: 575 Opinie: 17
Okładka książki Pojedynek w Araluenie John Flanagan
Średnia ocen:
7,5 / 10
801 ocen
Czytelnicy: 1983 Opinie: 59
Okładka książki Klan Czerwonego Lisa John Flanagan
Średnia ocen:
7,4 / 10
969 ocen
Czytelnicy: 2313 Opinie: 85
Okładka książki Kaldera John Flanagan
Średnia ocen:
7,5 / 10
446 ocen
Czytelnicy: 995 Opinie: 35
Okładka książki Bitwa na Wrzosowiskach John Flanagan
Średnia ocen:
7,9 / 10
838 ocen
Czytelnicy: 1717 Opinie: 65
Okładka książki Nieznany ląd John Flanagan
Średnia ocen:
7,8 / 10
661 ocen
Czytelnicy: 1523 Opinie: 45
Okładka książki Turniej w Gorlanie John Flanagan
Średnia ocen:
8,0 / 10
1209 ocen
Czytelnicy: 2428 Opinie: 107
Okładka książki Góra Skorpiona John Flanagan
Średnia ocen:
7,9 / 10
944 ocen
Czytelnicy: 2223 Opinie: 52
Okładka książki Niewolnicy z Socorro John Flanagan
Średnia ocen:
7,8 / 10
1108 ocen
Czytelnicy: 2468 Opinie: 66
Okładka książki Królewski zwiadowca John Flanagan
Średnia ocen:
7,9 / 10
2996 ocen
Czytelnicy: 7184 Opinie: 239
Okładka książki Pościg John Flanagan
Średnia ocen:
7,8 / 10
1522 ocen
Czytelnicy: 3121 Opinie: 86
Okładka książki Najeźdźcy John Flanagan
Średnia ocen:
7,7 / 10
1779 ocen
Czytelnicy: 3473 Opinie: 92
Okładka książki Zaginione historie John Flanagan
Średnia ocen:
7,8 / 10
3516 ocen
Czytelnicy: 8078 Opinie: 199

Cytaty

Więcej
John Flanagan Płonący most Zobacz więcej
John Flanagan Płonący most Zobacz więcej
John Flanagan Bitwa o Skandię Zobacz więcej
Więcej

Video

1
Video
Reklama

Popularni autorzy

Reklama
zgłoś błąd
diff --git a/internal/lubimyczytac/testdata/remigiusz_mroz.html b/internal/lubimyczytac/testdata/remigiusz_mroz.html new file mode 100644 index 0000000..30d5e39 --- /dev/null +++ b/internal/lubimyczytac/testdata/remigiusz_mroz.html @@ -0,0 +1 @@ +Remigiusz Mróz | Wszystkie książki, wywiady, artykuły | Lubimyczytać.pl
rozwińzwiń

Remigiusz Mróz

Żródło zdjecia: facebook.com/remigiuszmroz
Remigiusz Mróz
Znany również jako: Ove Løgmansbø
Średnia ocena książek autora
7,1/107,1 / 10
  • 56 858 przeczytało książki autora
  • 99 778 chce przeczytać książki autora
5 436 fanów autora
Dagooshya
Ola
kaskas
Ania
Więcej
Reklama

DYSKUSJE

Tytuł posta
Wyświetleń
Postów
Ostatnia Aktywność

Teksty redakcyjne

Kurs pisania #20 - 10 rad na koniec
Publicystyka | 28 czerwca 2015
Kurs pisania #20 - 10 rad na koniec

Nie godzi się zamykać kursu pisania bez podsumowania. I zanim dopadnie mnie nostalgia, a łezka w oku się zakręci, przejdę szybko do rzeczy – oto dziesięć rad, które powinny pomóc tym wszystkim,... Czytaj więcej Więcej

Kurs pisania #19 - Jak wydać książkę?
Publicystyka | 14 czerwca 2015
Kurs pisania #19 - Jak wydać książkę?

Pytanie klucz, które otwiera…Nie, zasadniczo niczego nie otwiera. Efekt ma dokładnie odwrotny. Po wpisaniu frazy w Google’u wyskakuje nam trylion linków do forów, po przestudiowaniu których... Czytaj więcej Więcej

Kurs pisania #18 - rytm tekstu
Publicystyka | 31 maja 2015
Kurs pisania #18 - rytm tekstu

Czytając jakąkolwiek książkę, większość z nas wyczuwa jej rytm. W dodatku ma on dwa wymiary – pierwszy to rytm warsztatowy, drugi to rytm fabularny. Obie te rzeczy można nazywać także na... Czytaj więcej Więcej

Pokazuje 3 z 20
Zobacz kolejne
Reklama

Książki i czasopisma

Sortuj:
Sortuj
Okładka książki Obrazy z przeszłości Remigiusz Mróz
Średnia ocen:
7,6 / 10
218 ocen
Czytelnicy: 610 Opinie: 31
Okładka książki Skazanie Remigiusz MrózPatronat LC
Średnia ocen:
6,9 / 10
1094 ocen
Czytelnicy: 1907 Opinie: 172
Okładka książki Behawiorysta Remigiusz Mróz
Średnia ocen:
7,4 / 10
12172 ocen
Czytelnicy: 20012 Opinie: 1534
Okładka książki Projekt Riese Remigiusz Mróz
Średnia ocen:
6,3 / 10
1636 ocen
Czytelnicy: 2598 Opinie: 338
Okładka książki Immunitet Remigiusz Mróz
Średnia ocen:
7,5 / 10
12011 ocen
Czytelnicy: 19606 Opinie: 995
Okładka książki Przepaść Remigiusz Mróz
Średnia ocen:
7,6 / 10
2061 ocen
Czytelnicy: 3177 Opinie: 255
Reklama
Okładka książki Egzekucja Remigiusz Mróz
Średnia ocen:
7,2 / 10
2423 ocen
Czytelnicy: 3705 Opinie: 317
Okładka książki Wybaczam ci Remigiusz MrózPatronat LC
Średnia ocen:
7,4 / 10
2919 ocen
Czytelnicy: 4588 Opinie: 542
Okładka książki Inwigilacja Remigiusz Mróz
Średnia ocen:
7,6 / 10
11093 ocen
Czytelnicy: 18673 Opinie: 900
Okładka książki Ekstremista Remigiusz Mróz
Średnia ocen:
7,1 / 10
2382 ocen
Czytelnicy: 3727 Opinie: 286
Okładka książki Afekt Remigiusz Mróz
Średnia ocen:
7,3 / 10
3286 ocen
Czytelnicy: 5046 Opinie: 388
Okładka książki Głębia osobliwości cz. 2 Remigiusz Mróz
Średnia ocen:
6,0 / 10
4 ocen
Czytelnicy: 15 Opinie: 0
Okładka książki Szepty spoza nicości Remigiusz Mróz
Średnia ocen:
6,9 / 10
2857 ocen
Czytelnicy: 4278 Opinie: 422
Okładka książki W cieniu prawa Remigiusz Mróz
Średnia ocen:
6,6 / 10
2574 ocen
Czytelnicy: 4999 Opinie: 339
Okładka książki Księgarenka przy ulicy Wiśniowej Liliana Fabisińska, Gabriela Gargaś, Agnieszka Krawczyk, Remigiusz Mróz, Marta Obuch, Alek Rogoziński, Magdalena Witkiewicz
Średnia ocen:
7,3 / 10
978 ocen
Czytelnicy: 2804 Opinie: 171
Okładka książki Rewizja Remigiusz Mróz
Średnia ocen:
7,5 / 10
14102 ocen
Czytelnicy: 22887 Opinie: 1179
Okładka książki Halny Remigiusz Mróz
Średnia ocen:
7,5 / 10
3296 ocen
Czytelnicy: 5225 Opinie: 406
Okładka książki Precedens Remigiusz Mróz
Średnia ocen:
7,5 / 10
4294 ocen
Czytelnicy: 6686 Opinie: 452
Okładka książki Osiedle RZNiW Remigiusz MrózPatronat LC
Średnia ocen:
7,4 / 10
3639 ocen
Czytelnicy: 5429 Opinie: 589
Okładka książki Lot 202 Remigiusz Mróz
Średnia ocen:
7,2 / 10
2969 ocen
Czytelnicy: 4685 Opinie: 444

Cytaty

Więcej
Remigiusz Mróz Inwigilacja Zobacz więcej
Remigiusz Mróz Kasacja Zobacz więcej
Remigiusz Mróz Kasacja Zobacz więcej
Więcej

Popularni autorzy

Reklama
zgłoś błąd
diff --git a/internal/lubimyczytac/testdata/testdata.go b/internal/lubimyczytac/testdata/testdata.go new file mode 100644 index 0000000..9a2889f --- /dev/null +++ b/internal/lubimyczytac/testdata/testdata.go @@ -0,0 +1,83 @@ +package testdata + +import ( + _ "embed" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac" +) + +//go:embed remigiusz_mroz.html +var remigiuszMrozHTML []byte + +//go:embed john_flanagan.html +var johnFlanaganHTML []byte + +type AuthorWithHTML struct { + lubimyczytac.Author + HTML []byte +} + +var Authors = []AuthorWithHTML{ + { + Author: lubimyczytac.Author{ + ID: "82094", + Name: "Remigiusz Mróz", + ShortDescription: "Polski pisarz, autor powieści kryminalnych oraz cyklu publicystycznego „Kurs pisania”.Ukończył z wyróżnieniem Akademię Leona Koźmińskiego w Warszawie, gd...", + URL: "https://lubimyczytac.pl/autor/82094/remigiusz-mroz", + Books: []lubimyczytac.Book{ + {Title: "Obrazy z przeszłości", URL: "https://lubimyczytac.pl/ksiazka/5016998/obrazy-z-przeszlosci"}, + {Title: "Skazanie", URL: "https://lubimyczytac.pl/ksiazka/5009453/skazanie"}, + {Title: "Behawiorysta", URL: "https://lubimyczytac.pl/ksiazka/5006528/behawiorysta"}, + {Title: "Projekt Riese", URL: "https://lubimyczytac.pl/ksiazka/4998407/projekt-riese"}, + {Title: "Immunitet", URL: "https://lubimyczytac.pl/ksiazka/4990168/immunitet"}, + {Title: "Przepaść", URL: "https://lubimyczytac.pl/ksiazka/4988766/przepasc"}, + {Title: "Egzekucja", URL: "https://lubimyczytac.pl/ksiazka/4983192/egzekucja"}, + {Title: "Wybaczam ci", URL: "https://lubimyczytac.pl/ksiazka/4975543/wybaczam-ci"}, + {Title: "Inwigilacja", URL: "https://lubimyczytac.pl/ksiazka/4968996/inwigilacja"}, + {Title: "Ekstremista", URL: "https://lubimyczytac.pl/ksiazka/4968712/ekstremista"}, + {Title: "Afekt", URL: "https://lubimyczytac.pl/ksiazka/4962195/afekt"}, + {Title: "Głębia osobliwości cz. 2", URL: "https://lubimyczytac.pl/ksiazka/5009924/glebia-osobliwosci-cz-2"}, + {Title: "Szepty spoza nicości", URL: "https://lubimyczytac.pl/ksiazka/4955433/szepty-spoza-nicosci"}, + {Title: "W cieniu prawa", URL: "https://lubimyczytac.pl/ksiazka/4947287/w-cieniu-prawa"}, + {Title: "Księgarenka przy ulicy Wiśniowej", URL: "https://lubimyczytac.pl/ksiazka/4944645/ksiegarenka-przy-ulicy-wisniowej"}, + {Title: "Rewizja", URL: "https://lubimyczytac.pl/ksiazka/4943912/rewizja"}, + {Title: "Halny", URL: "https://lubimyczytac.pl/ksiazka/4943948/halny"}, + {Title: "Precedens", URL: "https://lubimyczytac.pl/ksiazka/4939105/precedens"}, + {Title: "Osiedle RZNiW", URL: "https://lubimyczytac.pl/ksiazka/4932748/osiedle-rzniw"}, + {Title: "Lot 202", URL: "https://lubimyczytac.pl/ksiazka/4926011/lot-202"}, + }, + }, + HTML: remigiuszMrozHTML, + }, + { + Author: lubimyczytac.Author{ + ID: "19013", + Name: "John Flanagan", + ShortDescription: "John Flanagan Urodzony i wychowany w Sydney w Australii, John Flanagan od dzieciństwa marzył o tym, by zostać pisarzem. Nie było łatwo. Pracował w agencji r...", + URL: "https://lubimyczytac.pl/autor/19013/john-flanagan", + Books: []lubimyczytac.Book{ + {Title: "Morska pogoń", URL: "https://lubimyczytac.pl/ksiazka/5016250/morska-pogon"}, + {Title: "Ruiny Gorlanu", URL: "https://lubimyczytac.pl/ksiazka/4989803/ruiny-gorlanu"}, + {Title: "Ziemia skuta lodem", URL: "https://lubimyczytac.pl/ksiazka/4989807/ziemia-skuta-lodem"}, + {Title: "Bitwa o Skandię", URL: "https://lubimyczytac.pl/ksiazka/4989809/bitwa-o-skandie"}, + {Title: "Ucieczka z zamku Falaise", URL: "https://lubimyczytac.pl/ksiazka/4984975/ucieczka-z-zamku-falaise"}, + {Title: "Płonący most", URL: "https://lubimyczytac.pl/ksiazka/4989806/plonacy-most"}, + {Title: "Zaginiony książę", URL: "https://lubimyczytac.pl/ksiazka/4935503/zaginiony-ksiaze"}, + {Title: "Powrót Temudżeinów", URL: "https://lubimyczytac.pl/ksiazka/4897979/powrot-temudzeinow"}, + {Title: "Pojedynek w Araluenie", URL: "https://lubimyczytac.pl/ksiazka/4861058/pojedynek-w-araluenie"}, + {Title: "Klan Czerwonego Lisa", URL: "https://lubimyczytac.pl/ksiazka/4851290/klan-czerwonego-lisa"}, + {Title: "Kaldera", URL: "https://lubimyczytac.pl/ksiazka/4811916/kaldera"}, + {Title: "Bitwa na Wrzosowiskach", URL: "https://lubimyczytac.pl/ksiazka/3874675/bitwa-na-wrzosowiskach"}, + {Title: "Nieznany ląd", URL: "https://lubimyczytac.pl/ksiazka/303434/nieznany-lad"}, + {Title: "Turniej w Gorlanie", URL: "https://lubimyczytac.pl/ksiazka/266058/turniej-w-gorlanie"}, + {Title: "Góra Skorpiona", URL: "https://lubimyczytac.pl/ksiazka/230915/gora-skorpiona"}, + {Title: "Niewolnicy z Socorro", URL: "https://lubimyczytac.pl/ksiazka/220609/niewolnicy-z-socorro"}, + {Title: "Królewski zwiadowca", URL: "https://lubimyczytac.pl/ksiazka/192775/krolewski-zwiadowca"}, + {Title: "Pościg", URL: "https://lubimyczytac.pl/ksiazka/167327/poscig"}, + {Title: "Najeźdźcy", URL: "https://lubimyczytac.pl/ksiazka/144610/najezdzcy"}, + {Title: "Zaginione historie", URL: "https://lubimyczytac.pl/ksiazka/131440/zaginione-historie"}, + }, + }, + HTML: johnFlanaganHTML, + }, +} diff --git a/main.go b/main.go index 764763a..a1dafe0 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,42 @@ package main import ( "context" + "encoding/xml" "log" "net/http" "os" "os/signal" "time" + + "github.com/go-chi/chi/v5/middleware" + + "github.com/go-chi/chi/v5" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac" +) + +const ( + defaultClientTimeout = 5 * time.Second ) func main() { + r := chi.NewRouter() + r.Use( + middleware.RealIP, + middleware.RequestLogger(&middleware.DefaultLogFormatter{ + NoColor: true, + Logger: log.Default(), + }), + middleware.Recoverer, + middleware.Heartbeat("/health"), + ) + newHandler(lubimyczytac.NewClient(&http.Client{ + Timeout: defaultClientTimeout, + })).register(r) + httpSrv := &http.Server{ Addr: ":9234", - Handler: nil, + Handler: r, ReadTimeout: 2 * time.Second, ReadHeaderTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second, @@ -35,6 +60,90 @@ func main() { ctxShutdown, cancelCtxShutdown := context.WithTimeout(context.Background(), 10*time.Second) defer cancelCtxShutdown() if err := httpSrv.Shutdown(ctxShutdown); err != nil { - log.Fatalln("httpSrv.Shutdown:", err) + log.Println("httpSrv.Shutdown:", err) } } + +type rssItem struct { + XMLName xml.Name `xml:"item"` + Title string `xml:"title"` + Link string `xml:"link"` + GUID string `xml:"guid"` + Description string `xml:"description"` +} + +func rssItemsFromBooks(books []lubimyczytac.Book) []rssItem { + items := make([]rssItem, len(books)) + for i, b := range books { + items[i] = rssItem{ + Title: b.Title, + Link: b.URL, + GUID: b.URL, + Description: "", + } + } + return items +} + +type rssChannel struct { + XMLName xml.Name `xml:"channel"` + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + Language string `xml:"language"` + Items []rssItem `xml:"items"` +} + +func rssChannelFromAuthor(author lubimyczytac.Author) rssChannel { + return rssChannel{ + Title: author.Name, + Description: author.ShortDescription, + Link: author.URL, + Items: rssItemsFromBooks(author.Books), + } +} + +type rssMain struct { + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + Channel rssChannel `xml:"channel"` +} + +func rssMainFromAuthor(author lubimyczytac.Author) rssMain { + return rssMain{ + Version: "2.0", + Channel: rssChannelFromAuthor(author), + } +} + +type handler struct { + client *lubimyczytac.Client +} + +func newHandler(client *lubimyczytac.Client) *handler { + return &handler{client: client} +} + +func (h *handler) register(r chi.Router) { + r.Get("/api/v1/rss/author/{authorID}", h.getRSSAuthor) +} + +func (h *handler) getRSSAuthor(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + author, err := h.client.GetAuthor(ctx, chi.URLParamFromCtx(ctx, "authorID")) + if err == lubimyczytac.ErrAuthorNotFound { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`author not found`)) + return + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`something went wrong while getting author info: ` + err.Error())) + return + } + + w.Header().Set("Content-Type", "text/xml; charset=utf-8") + w.WriteHeader(http.StatusOK) + _ = xml.NewEncoder(w).Encode(rssMainFromAuthor(author)) +}