initial commit
This commit is contained in:
223
api.go
Normal file
223
api.go
Normal file
@ -0,0 +1,223 @@
|
||||
package nuapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.tordarus.net/tordarus/adverr/v2"
|
||||
"git.tordarus.net/tordarus/slices"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
UserAgent string
|
||||
Cookie string
|
||||
}
|
||||
|
||||
func NewApi(cookie string) *Api {
|
||||
return &Api{
|
||||
Cookie: cookie,
|
||||
UserAgent: "Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0",
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Api) GetReadingList(ctx context.Context, listIndex int) (*ReadingList, error) {
|
||||
doc, err := api.GetWithCookie(ctx, fmt.Sprintf("https://www.novelupdates.com/reading-list/?list=%d", listIndex))
|
||||
if err != nil {
|
||||
return nil, ErrApiRequestFailed.Wrap(err)
|
||||
}
|
||||
|
||||
listID := ReadingListID(doc.Find("#cssmenu ul li.active a").Text())
|
||||
|
||||
selection := doc.Find("table tbody tr")
|
||||
entries := make([]ReadingListEntry, 0, selection.Length())
|
||||
selection.Each(func(i int, s *goquery.Selection) {
|
||||
link := s.Find("td:nth-child(2) a:first-child")
|
||||
href, ok := link.Attr("href")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
novelID := NovelID(path.Base(href))
|
||||
novel := NovelEntry{
|
||||
NovelID: novelID,
|
||||
Name: link.Text(),
|
||||
}
|
||||
|
||||
currentChapterLink := s.Find("td:nth-child(3) a")
|
||||
currentChapter := ReadingListChapterEntry{
|
||||
NovelID: novelID,
|
||||
ID: ChapterID(currentChapterLink.Text()),
|
||||
Link: currentChapterLink.AttrOr("href", ""),
|
||||
}
|
||||
|
||||
latestChapterLink := s.Find("td:nth-child(3) a")
|
||||
latestChapter := ReadingListChapterEntry{
|
||||
NovelID: novelID,
|
||||
ID: ChapterID(latestChapterLink.Text()),
|
||||
Link: latestChapterLink.AttrOr("href", ""),
|
||||
}
|
||||
|
||||
entries = append(entries, ReadingListEntry{
|
||||
Novel: novel,
|
||||
CurrentChapter: currentChapter,
|
||||
LatestChapter: latestChapter,
|
||||
})
|
||||
})
|
||||
|
||||
return &ReadingList{
|
||||
ID: listID,
|
||||
Entries: entries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *Api) GetNovelByID(novelID NovelID) (*Novel, error) {
|
||||
doc, err := api.Get(fmt.Sprintf("https://www.novelupdates.com/series/%s/", novelID))
|
||||
if err != nil {
|
||||
return nil, ErrApiRequestFailed.Wrap(err)
|
||||
}
|
||||
|
||||
title := doc.Find(".seriestitlenu").Text()
|
||||
description := strings.TrimSpace(doc.Find("#editdescription").Text())
|
||||
cover := doc.Find(".wpb_wrapper img").AttrOr("src", "")
|
||||
|
||||
associatedNamesHtml, err := doc.Find("#editassociated").Html()
|
||||
if err != nil {
|
||||
return nil, ErrApiElementNotFound.Wrap(err, "#editassociated")
|
||||
}
|
||||
associatedNames := strings.Split(strings.TrimSpace(associatedNamesHtml), "<br/>")
|
||||
|
||||
novelType := NovelType(doc.Find("#showtype a.genre.type").Text())
|
||||
originalLanguage := Language(strings.ToLower(strings.Trim(doc.Find("#showtype a.genre.type + span").Text(), "()")))
|
||||
|
||||
genreElems := doc.Find("#seriesgenre a.genre")
|
||||
genres := make([]GenreID, 0, genreElems.Length())
|
||||
genreElems.Each(func(i int, s *goquery.Selection) {
|
||||
href, ok := s.Attr("href")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
genres = append(genres, GenreID(path.Base(href)))
|
||||
})
|
||||
|
||||
tagElems := doc.Find("#showtags a.genre")
|
||||
tags := make([]TagID, 0, genreElems.Length())
|
||||
tagElems.Each(func(i int, s *goquery.Selection) {
|
||||
href, ok := s.Attr("href")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tags = append(tags, TagID(path.Base(href)))
|
||||
})
|
||||
|
||||
return &Novel{
|
||||
ID: novelID,
|
||||
Name: title,
|
||||
AssociatedNames: associatedNames,
|
||||
Description: description,
|
||||
Cover: cover,
|
||||
Type: novelType,
|
||||
OriginalLanguage: originalLanguage,
|
||||
Genres: genres,
|
||||
Tags: tags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *Api) GetChapterEntriesByNovelID(novelID NovelID) *Cursor[NovelChapterEntry] {
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
out := make(chan *NovelChapterEntry, 15)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
doc, err := api.Get(fmt.Sprintf("https://www.novelupdates.com/series/%s/?pg=%d", novelID, 1))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
pageCount, err := api.getPageCount(doc)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
for pageIndex := pageCount; pageIndex > 0; pageIndex-- {
|
||||
if ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
entries, err := api.getChapterEntriesByPageIndex(novelID, pageIndex)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entry := entry
|
||||
out <- &entry
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &Cursor[NovelChapterEntry]{
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
Chan: out,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Api) getPageCount(doc *goquery.Document) (int, error) {
|
||||
pagination := doc.Find(".digg_pagination")
|
||||
if pagination.Length() <= 0 {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
pageCount, err := strconv.ParseInt(pagination.Find("a:nth-last-child(2)").Text(), 10, 64)
|
||||
if err != nil {
|
||||
adverr.Println(ErrApiElementNotFound.Wrap(err, ".digg_pagination a:nth-child(5)"))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(pageCount), nil
|
||||
}
|
||||
|
||||
func (api *Api) getChapterEntriesByPageIndex(novelID NovelID, pageIndex int) ([]NovelChapterEntry, error) {
|
||||
doc, err := api.Get(fmt.Sprintf("https://www.novelupdates.com/series/%s/?pg=%d", novelID, pageIndex))
|
||||
if err != nil {
|
||||
return nil, ErrApiRequestFailed.Wrap(err)
|
||||
}
|
||||
|
||||
entryElems := doc.Find("#myTable tbody tr")
|
||||
entries := make([]NovelChapterEntry, 0, entryElems.Length())
|
||||
entryElems.Each(func(i int, s *goquery.Selection) {
|
||||
td3 := s.Find("td:nth-child(3) a")
|
||||
|
||||
fmt.Printf("%#v\n", adverr.Must(s.Find("td:nth-child(3)").Html()))
|
||||
|
||||
chapterID := strings.TrimSpace(td3.Text())
|
||||
groupID := path.Base(s.Find("td:nth-child(2) a").AttrOr("href", ""))
|
||||
link := "https:" + td3.AttrOr("href", "")
|
||||
|
||||
date, err := time.Parse("01/02/2006", strings.TrimSpace(s.Find("td:first-child").Text()))
|
||||
if err != nil {
|
||||
adverr.Println(ErrApiElementNotFound.Wrap(err, "td:first-child"))
|
||||
return
|
||||
}
|
||||
|
||||
entries = append(entries, NovelChapterEntry{
|
||||
NovelID: novelID,
|
||||
ID: chapterID,
|
||||
Link: link,
|
||||
Date: date,
|
||||
Group: groupID,
|
||||
})
|
||||
})
|
||||
|
||||
return slices.Reverse(entries), nil
|
||||
}
|
Reference in New Issue
Block a user