diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 73133a6..324117b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -34,4 +34,4 @@ archives: format_overrides: - goos: windows format: zip - name_template: '{{.ProjectName}}_{{.Os}}-{{.Arch}}' \ No newline at end of file + name_template: '{{.ProjectName}}_{{.Os}}-{{.Arch}}' diff --git a/cmd/search.go b/cmd/search.go index 06debae..7214bec 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -2,11 +2,11 @@ package cmd import ( "fmt" - "log" "strings" "github.com/fatih/color" - "github.com/laureanray/clibgen/pkg/api" + "github.com/laureanray/clibgen/internal/libgen" + "github.com/laureanray/clibgen/internal/mirror" "github.com/manifoldco/promptui" "github.com/spf13/cobra" ) @@ -48,46 +48,45 @@ var ( return } - var libgenType = api.LibgenNew + var m mirror.Mirror - if selectedSite == "old" { - libgenType = api.LibgenOld + if selectedSite == "legacy" { + m = mirror.NewLegacyMirror(libgen.IS) } else if selectedSite == "new" { - libgenType = api.LibgenNew - } - - books, siteUsed, err := api.SearchBookByTitle(args[0], numberOfResults, libgenType) - if err != nil { - log.Fatalln(err) - } + m = mirror.NewCurrentMirror(libgen.LC) + } else{ + // TODO: Improve this. + fmt.Print("Not an option"); + return + } - if err != nil { - log.Fatal(err) - return - } + + books, _ := m.SearchByTitle(args[0]) var titles []string - + for _, book := range books { parsedTitle := truncateText(book.Title, 42) parsedAuthor := truncateText(book.Author, 24) parsedExt := getExtension(fmt.Sprintf("%-4s", book.Extension)) titles = append(titles, fmt.Sprintf("%s %-6s | %-45s %s", parsedExt, book.FileSize, parsedTitle, parsedAuthor)) } - + prompt := promptui.Select{ Label: "Select Title", Items: titles, } - + resultInt, _, err := prompt.Run() - + if err != nil { fmt.Printf("Prompt failed %v\n", err) return } - api.DownloadSelection(books[resultInt], siteUsed) + println(resultInt) + + m.DownloadSelection(books[resultInt]) }, } ) @@ -95,10 +94,10 @@ var ( func init() { searchCmd. PersistentFlags(). - StringVarP(&selectedSite, "site", "s", "old", `select which site to use + StringVarP(&selectedSite, "site", "s", "legacy", `select which site to use options: - "old" -> libgen.is - "new" -> liggen.li + "legacy" + "new" `) searchCmd. diff --git a/pkg/api/book.go b/internal/book/book.go similarity index 92% rename from pkg/api/book.go rename to internal/book/book.go index d50b6b7..b09eb57 100644 --- a/pkg/api/book.go +++ b/internal/book/book.go @@ -1,4 +1,4 @@ -package api +package book type Book struct { ID string diff --git a/internal/console/console.go b/internal/console/console.go new file mode 100644 index 0000000..516942b --- /dev/null +++ b/internal/console/console.go @@ -0,0 +1,34 @@ +package console + +import ( + "fmt" + + "github.com/fatih/color" +) + +// TODO: Refactor this to just print directly instead +// of doing fmt.Println(console.Hihlight()) and so on... +func Higlight(format string, a ...any) string { + magenta := color.New(color.FgHiWhite).Add(color.BgBlack).SprintFunc() + return magenta(fmt.Sprintf(format, a...)) +} + +func Error(format string, a ...any) string { + red := color.New(color.FgRed).SprintFunc() + return red(fmt.Sprintf(format, a...)) +} + +func Info(format string, a ...any) string { + yellow := color.New(color.FgYellow).SprintFunc() + return yellow(fmt.Sprintf(format, a...)) +} + +func Success(format string, a ...any) string { + green := color.New(color.FgHiGreen).SprintFunc() + return green(fmt.Sprintf(format, a...)) +} + +func Normal(format string, a ...any) string { + white := color.New(color.FgWhite).SprintFunc() + return white(fmt.Sprintf(format, a...)) +} diff --git a/internal/document_parser/current_document.go b/internal/document_parser/current_document.go new file mode 100644 index 0000000..e8b5990 --- /dev/null +++ b/internal/document_parser/current_document.go @@ -0,0 +1,130 @@ +package documentparser + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/laureanray/clibgen/internal/book" +) + +type CurrentDocumentParser struct { + doc *goquery.Document +} + +func NewCurrentDocumentParser(document *goquery.Document) *CurrentDocumentParser { + return &CurrentDocumentParser{doc: document} +} + +func NewCurrentDocumentParserFromReader(r io.Reader) *CurrentDocumentParser { + document, _ := goquery.NewDocumentFromReader(r) + return &CurrentDocumentParser{doc: document} +} + +func findTitle(selection *goquery.Selection) string { + var title string + selection.Find("a").EachWithBreak(func(v int, s *goquery.Selection) bool { + if s.Text() != "" && len(s.Text()) > 1 { + title = s.Text() + // Break out of the loop + return false + } + return true + }) + + return title +} + +func (cdp *CurrentDocumentParser) GetBookDataFromDocument() []book.Book { + var books []book.Book + cdp.doc.Find("#tablelibgen > tbody > tr").Each(func(resultsRow int, bookRow *goquery.Selection) { + var id, author, title, publisher, extension, year, fileSize string + var mirrors []string + if resultsRow != 0 { + bookRow.Find("td").Each(func(column int, columnSelection *goquery.Selection) { + switch column { + case 0: + title = findTitle(columnSelection) + case 1: + author = columnSelection.Text() + case 2: + publisher = columnSelection.Text() + case 3: + year = columnSelection.Text() + case 6: + fileSize = columnSelection.Text() + case 7: + extension = columnSelection.Text() + case 8: + columnSelection.Find("a").Each(func(linkCol int, link *goquery.Selection) { + href, hrefExists := link.Attr("href") + if hrefExists { + mirrors = append(mirrors, href) + } + }) + } + }) + books = append(books, book.Book{ + ID: id, + Author: author, + Year: year, + Title: title, + Publisher: publisher, + Extension: extension, + Mirrors: mirrors, + FileSize: fileSize, + }) + } + }) + return books +} + +func (cdp *CurrentDocumentParser) getDownloadLinkFromDocument() (string, bool) { + return cdp.doc.Find("#main a").First().Attr("href") +} + +func (cdp *CurrentDocumentParser) getBookTitleFromSelection(selection *goquery.Selection) string { + var title string + selection.Find("a").Each(func(v int, s *goquery.Selection) { + _, exists := s.Attr("title") + if exists { + title = s.Text() + } + }) + selection.Find("a > font").Each(func(v int, s *goquery.Selection) { + a := s.Text() + title = strings.ReplaceAll(title, a, "") + }) + return title +} + +func (cdp *CurrentDocumentParser) GetDirectDownloadLink(selectedBook book.Book) string { + fmt.Println("Obtaining direct download link") + + // TODO Implement retry? + link := selectedBook.Mirrors[0] + + resp, err := http.Get(link) + defer func(Body io.ReadCloser) { + err := Body.Close() + + if err != nil { + fmt.Println("Error closing body:", err) + } + }(resp.Body) + + if err != nil { + fmt.Println("Error getting response:", err) + } + + directDownloadLink, exists := + NewCurrentDocumentParserFromReader(resp.Body).getDownloadLinkFromDocument() + + if exists { + return directDownloadLink + } + + return "" +} diff --git a/internal/document_parser/document_parser.go b/internal/document_parser/document_parser.go new file mode 100644 index 0000000..321f273 --- /dev/null +++ b/internal/document_parser/document_parser.go @@ -0,0 +1,15 @@ +package documentparser + +import ( + "github.com/laureanray/clibgen/internal/book" +) + +// type Page struct { +// doc *goquery.Document +// } + +type DocumentParser interface { + GetBookDataFromDocument() []book.Book + GetDownloadLinkFromDocument() (string, bool) +} + diff --git a/internal/document_parser/legacy_document.go b/internal/document_parser/legacy_document.go new file mode 100644 index 0000000..6a4e37d --- /dev/null +++ b/internal/document_parser/legacy_document.go @@ -0,0 +1,149 @@ +package documentparser + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/laureanray/clibgen/internal/book" +) + +type LegacyDocumentParser struct { + doc *goquery.Document +} + +func NewLegacyDocumentParser(document *goquery.Document) *LegacyDocumentParser { + return &LegacyDocumentParser{doc: document} +} + +func NewLegacyDocumentParserFromReader(r io.Reader) *LegacyDocumentParser { + document, _ := goquery.NewDocumentFromReader(r) + return &LegacyDocumentParser{doc: document} +} + +func (ldp *LegacyDocumentParser) GetBookDataFromDocument() []book.Book { + var books []book.Book + ldp.doc.Find(".c > tbody > tr").Each(func(resultsRow int, bookRow *goquery.Selection) { + var id, author, title, publisher, extension, year, fileSize string + var mirrors []string + if resultsRow != 0 { + bookRow.Find("td").Each(func(column int, columnSelection *goquery.Selection) { + switch column { + case 0: + id = columnSelection.Text() + case 1: + author = columnSelection.Text() + case 2: + title = getBookTitleFromSelection(columnSelection) + case 3: + publisher = columnSelection.Text() + case 4: + year = columnSelection.Text() + case 7: + fileSize = columnSelection.Text() + case 8: + extension = columnSelection.Text() + case 9, 10, 11: + href, hrefExists := columnSelection.Find("a").Attr("href") + if hrefExists { + mirrors = append(mirrors, href) + } + } + }) + books = append(books, book.Book{ + ID: id, + Author: author, + Year: year, + Title: title, + Publisher: publisher, + Extension: extension, + Mirrors: mirrors, + FileSize: fileSize, + }) + } + }) + + return books +} + + +func (ldp *LegacyDocumentParser) getDownloadLinkFromDocument() (string, bool){ + return ldp.doc.Find("#download > ul > li > a").First().Attr("href") +} + + +func getBookTitleFromSelection(selection *goquery.Selection) string { + var title string + selection.Find("a").Each(func(v int, s *goquery.Selection) { + _, exists := s.Attr("title") + if exists { + title = s.Text() + } + }) + selection.Find("a > font").Each(func(v int, s *goquery.Selection) { + a := s.Text() + title = strings.ReplaceAll(title, a, "") + }) + return title +} + + +func GetDirectDownloadLinkFromLegacy(link string) string { + fmt.Println("Obtaining direct download link") + resp, err := http.Get(link) + defer func(Body io.ReadCloser) { + err := Body.Close() + + if err != nil { + fmt.Println("Error closing body:", err) + } + }(resp.Body) + + if err != nil { + fmt.Println("Error getting response:", err) + } + + page := NewLegacyDocumentParserFromReader(resp.Body) + // TODO: I think this can be improved + directDownloadLink, exists := page.getDownloadLinkFromDocument() + + fmt.Println("Direct download link:", directDownloadLink) + + if exists { + return directDownloadLink + } + + return "" +} + +func GetDirectDownloadLinkFromCurrent(link string) string { + fmt.Println("Obtaining direct download link") + resp, err := http.Get(link) + defer func(Body io.ReadCloser) { + err := Body.Close() + + if err != nil { + fmt.Println("Error closing body:", err) + } + }(resp.Body) + + if err != nil { + fmt.Println("Error getting response:", err) + } + + page := NewCurrentDocumentParserFromReader(resp.Body) + // TODO: I think this can be improved + directDownloadLink, exists := page.getDownloadLinkFromDocument() + + fmt.Println("Direct download link:", directDownloadLink) + + if exists { + return directDownloadLink + } + + return "" +} + + diff --git a/internal/downloader/downloader.go b/internal/downloader/downloader.go new file mode 100644 index 0000000..dab4bcd --- /dev/null +++ b/internal/downloader/downloader.go @@ -0,0 +1,59 @@ +package downloader + +import ( + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/kennygrant/sanitize" + "github.com/laureanray/clibgen/internal/book" + "github.com/laureanray/clibgen/internal/console" + "github.com/schollz/progressbar/v3" +) + +type Downloader struct { + selectedBook book.Book + directLink string +} + +func NewDownloader(selectedBook book.Book, directLink string) *Downloader { + return &Downloader{ + selectedBook: selectedBook, + directLink: directLink, + } +} + +func (d *Downloader) Download() error { + fmt.Println(console.Info("Initializing download ")) + + // TODO: implement retry + req, _ := http.NewRequest("GET", d.directLink, nil) + resp, error := http.DefaultClient.Do(req) + + if error != nil { + fmt.Println(console.Error("Error downloading file: %s", error.Error())) + } + + defer resp.Body.Close() + filename := sanitize.Path(strings.Trim(d.selectedBook.Title, " ") + "." + d.selectedBook.Extension) + + f, _ := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0666) + defer f.Close() + + bar := progressbar.DefaultBytes( + resp.ContentLength, + "Downloading", + ) + + bytes, err := io.Copy(io.MultiWriter(f, bar), resp.Body) + + if bytes == 0 || err != nil { + fmt.Println(bytes, err) + } else { + fmt.Println(console.Success("File successfully downloaded: %s", f.Name())) + } + + return err +} diff --git a/internal/libgen/libgen.go b/internal/libgen/libgen.go new file mode 100644 index 0000000..50fe451 --- /dev/null +++ b/internal/libgen/libgen.go @@ -0,0 +1,28 @@ +package libgen + +type Domain string +const ( + RS = "rs" + IS = "is" + ST = "st" + + LI = "li" + LC = "lc" + GS = "gs" + TOP = "top" + CLICK = "click" +) + +type Filter string +const ( + TITLE = "title" + AUTHOR = "author" + SERIES = "series" + PUBLISHER = "publisher" + YEAR = "year" + ISBN = "isbn" + MD5 = "md5" + TAGS = "tags" + EXTENSION = "extension" +) + diff --git a/internal/mirror/current_mirror.go b/internal/mirror/current_mirror.go new file mode 100644 index 0000000..3c54b9c --- /dev/null +++ b/internal/mirror/current_mirror.go @@ -0,0 +1,102 @@ +package mirror + +import ( + "fmt" + "io" + "net/http" + "net/url" + + "github.com/PuerkitoBio/goquery" + "github.com/laureanray/clibgen/internal/book" + "github.com/laureanray/clibgen/internal/console" + "github.com/laureanray/clibgen/internal/document_parser" + "github.com/laureanray/clibgen/internal/downloader" + "github.com/laureanray/clibgen/internal/libgen" +) + +type CurrentMirror struct { + domain libgen.Domain + filter libgen.Filter + config Configuration +} + +func NewCurrentMirror(domain libgen.Domain) *CurrentMirror { + return &CurrentMirror{ + domain: domain, + // TODO: Make this configurable + filter: libgen.TITLE, + config: Configuration{ + numberOfResults: 5, + }, + } +} + +func (m *CurrentMirror) SearchByTitle(query string) ([]book.Book, error) { + fmt.Println("Searching for: ", console.Higlight(query)) + var document *goquery.Document + + document, err := m.searchSite(query) + + if err != nil { + fmt.Println(console.Error("Error searching for book: %s", query)) + // TODO: Implement retrying + // fmt.Println(infoColor("Retrying with other site")) + // document, e = searchLibgen(query, siteToUse) // If this also fails then we have a problem + } + fmt.Println(console.Success("Search complete, parsing the document...")) + + page := documentparser.NewCurrentDocumentParser(document) + bookResults := page.GetBookDataFromDocument() + + // if len(bookResults) >= limit { + // bookResults = bookResults[:limit] + // } + + return bookResults, err +} + + +// Search the libgen site returns the document +// of the search results page +func (m *CurrentMirror) searchSite(query string) (*goquery.Document, error) { + + baseUrl := fmt.Sprintf("https://libgen.%s/index.php", m.domain) + + queryString := fmt.Sprintf( + "%s?req=%s", + baseUrl, + url.QueryEscape(query), + ) + + reqString:= queryString + "&columns%5B%5D=t&columns%5B%5D=a&columns%5B%5D=s&columns%5B%5D=y&columns%5B%5D=p&columns%5B%5D=i&objects%5B%5D=f&objects%5B%5D=e&objects%5B%5D=s&objects%5B%5D=a&objects%5B%5D=p&objects%5B%5D=w&topics%5B%5D=l&topics%5B%5D=f&topics%5B%5D=r&res=25&filesuns=all" + + fmt.Println(reqString) + + resp, e := http.Get(reqString) + + if e != nil { + return nil, e + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + e = err + } + }(resp.Body) + + document, e := goquery.NewDocumentFromReader(resp.Body) + + if e != nil { + fmt.Println(e) + return nil, e + } + + return document, e +} + +func (m *CurrentMirror) DownloadSelection(selectedBook book.Book) { + fmt.Println(console.Info("Downloading book...")) + directLink := documentparser.GetDirectDownloadLinkFromCurrent(selectedBook.Mirrors[0]) + downloader.NewDownloader(selectedBook, directLink).Download() +} diff --git a/internal/mirror/legacy_mirror.go b/internal/mirror/legacy_mirror.go new file mode 100644 index 0000000..852bd4f --- /dev/null +++ b/internal/mirror/legacy_mirror.go @@ -0,0 +1,100 @@ +package mirror + +import ( + "fmt" + "io" + "net/http" + "net/url" + + "github.com/PuerkitoBio/goquery" + "github.com/laureanray/clibgen/internal/book" + "github.com/laureanray/clibgen/internal/console" + "github.com/laureanray/clibgen/internal/document_parser" + "github.com/laureanray/clibgen/internal/downloader" + "github.com/laureanray/clibgen/internal/libgen" +) + +type LegacyMirror struct { + domain libgen.Domain + filter libgen.Filter + config Configuration +} + +func NewLegacyMirror(domain libgen.Domain) *LegacyMirror { + return &LegacyMirror{ + domain: domain, + // TODO: Make this configurable + filter: libgen.TITLE, + config: Configuration{ + numberOfResults: 5, + }, + } +} + +func (m *LegacyMirror) SearchByTitle(query string) ([]book.Book, error) { + fmt.Println("Searching for: ", console.Higlight(query)) + var document *goquery.Document + + document, err := m.searchSite(query) + + if err != nil { + fmt.Println(console.Error("Error searching for book: %s", query)) + // TODO: Implement retrying + // fmt.Println(infoColor("Retrying with other site")) + // document, e = searchLibgen(query, siteToUse) // If this also fails then we have a problem + } + fmt.Println(console.Success("Search complete, parsing the document...")) + + bookResults := + documentparser.NewLegacyDocumentParser(document).GetBookDataFromDocument() + + if len(bookResults) >= m.config.numberOfResults { + bookResults = bookResults[:m.config.numberOfResults] + } + + return bookResults, err +} + +// Search the libgen site returns the document +// of the search results page +func (m *LegacyMirror) searchSite(query string) (*goquery.Document, error) { + + baseUrl := fmt.Sprintf("https://libgen.%s/search.php", m.domain) + + queryString := fmt.Sprintf( + "%s?req=%s&res=25&view=simple&phrase=1&column=%s", + baseUrl, + url.QueryEscape(query), + m.filter, + ) + + fmt.Println(console.Info(queryString)) + + resp, e := http.Get(queryString) + + if e != nil { + return nil, e + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + e = err + } + }(resp.Body) + + document, e := goquery.NewDocumentFromReader(resp.Body) + + if e != nil { + fmt.Println(e) + return nil, e + } + + return document, e +} + +func (m *LegacyMirror) DownloadSelection(selectedBook book.Book) { + fmt.Println(console.Info("Downloading book...")) + directLink := documentparser.GetDirectDownloadLinkFromLegacy(selectedBook.Mirrors[0]) + downloader.NewDownloader(selectedBook, directLink).Download() +} diff --git a/internal/mirror/mirror.go b/internal/mirror/mirror.go new file mode 100644 index 0000000..138d7d5 --- /dev/null +++ b/internal/mirror/mirror.go @@ -0,0 +1,23 @@ +package mirror + +import ( + "github.com/laureanray/clibgen/internal/book" + "github.com/laureanray/clibgen/internal/libgen" +) + +type Mirror interface { + SearchByTitle(query string) ([]book.Book, error) + // SearchByAuthor(author string) []book.Book + // SearchByISBN(isbn string) []book.Book + // 1GetDownloadLink(book book.Book) string + DownloadSelection(book book.Book) +} + +// TODO: Make this persistent +type Configuration struct { + numberOfResults int +} + +type NewMirror struct { + domain libgen.Domain +} diff --git a/pkg/api/libgen.go b/pkg/api/libgen.go deleted file mode 100644 index 9d1bbaa..0000000 --- a/pkg/api/libgen.go +++ /dev/null @@ -1,319 +0,0 @@ -package api - -import ( - "fmt" - "io" - "net/http" - "net/url" - "os" - "strings" - - "github.com/PuerkitoBio/goquery" - "github.com/fatih/color" - "github.com/kennygrant/sanitize" - "github.com/schollz/progressbar/v3" -) - -/* - -Currently there are three libgen domains: - - - libgen.is -> primary domain (old interface) - - libgen.li -> secondary (fallback) newer interface - -These domains might change in the future -*/ - -type Site int32 - -const ( - LibgenOld Site = 0 - LibgenNew Site = 1 -) - -// Note: Applicable only for libgen.is! (We might need to implemented something like this for libgen.li) -// Get book title from the selection, in most cases the title is hidden through nested anchor tags. -// In order to produce a clean output extra texts are also removed. -func getBookTitleFromSelection(selection *goquery.Selection) string { - var title string - selection.Find("a").Each(func(v int, s *goquery.Selection) { - _, exists := s.Attr("title") - if exists { - title = s.Text() - } - }) - selection.Find("a > font").Each(func(v int, s *goquery.Selection) { - a := s.Text() - title = strings.ReplaceAll(title, a, "") - }) - return title -} - -func getBookDataFromDocumentOld(document *goquery.Document) []Book { - var books []Book - document.Find(".c > tbody > tr").Each(func(resultsRow int, bookRow *goquery.Selection) { - var id, author, title, publisher, extension, year, fileSize string - var mirrors []string - if resultsRow != 0 { - bookRow.Find("td").Each(func(column int, columnSelection *goquery.Selection) { - switch column { - case 0: - id = columnSelection.Text() - case 1: - author = columnSelection.Text() - case 2: - title = getBookTitleFromSelection(columnSelection) - case 3: - publisher = columnSelection.Text() - case 4: - year = columnSelection.Text() - case 7: - fileSize = columnSelection.Text() - case 8: - extension = columnSelection.Text() - case 9, 10, 11: - href, hrefExists := columnSelection.Find("a").Attr("href") - if hrefExists { - mirrors = append(mirrors, href) - } - } - }) - books = append(books, Book{ - ID: id, - Author: author, - Year: year, - Title: title, - Publisher: publisher, - Extension: extension, - Mirrors: mirrors, - FileSize: fileSize, - }) - } - }) - return books -} - -func getBookDataFromDocumentNew(document *goquery.Document) []Book { - var books []Book - document.Find("#tablelibgen > tbody > tr").Each(func(resultsRow int, bookRow *goquery.Selection) { - var id, author, title, publisher, extension, year, fileSize string - var mirrors []string - if resultsRow != 0 { - bookRow.Find("td").Each(func(column int, columnSelection *goquery.Selection) { - switch column { - case 0: - title = columnSelection.Find("a").First().Text() - case 1: - author = columnSelection.Text() - case 2: - publisher = columnSelection.Text() - case 3: - year = columnSelection.Text() - case 6: - fileSize = columnSelection.Text() - case 7: - extension = columnSelection.Text() - case 8: - columnSelection.Find("a").Each(func(linkCol int, link *goquery.Selection) { - href, hrefExists := link.Attr("href") - if hrefExists { - mirrors = append(mirrors, href) - } - }) - } - }) - books = append(books, Book{ - ID: id, - Author: author, - Year: year, - Title: title, - Publisher: publisher, - Extension: extension, - Mirrors: mirrors, - FileSize: fileSize, - }) - } - }) - return books -} - -// Parse HTML and get the data from the table by parsing and iterating through them. -func getBookDataFromDocument(document *goquery.Document, libgenSite Site) []Book { - switch libgenSite { - case LibgenOld: - return getBookDataFromDocumentOld(document) - case LibgenNew: - return getBookDataFromDocumentNew(document) - } - return []Book{} -} - -func getLinkFromDocumentOld(document *goquery.Document) (string, bool) { - return document.Find("#download > ul > li > a").First().Attr("href") -} - -func getLinkFromDocumentNew(document *goquery.Document) (string, bool) { - return document.Find("#main a").First().Attr("href") -} - -func getDirectDownloadLink(link string, libgenType Site) string { - fmt.Println("Obtaining direct download link") - - resp, err := http.Get(link) - - defer func(Body io.ReadCloser) { - err := Body.Close() - - if err != nil { - fmt.Println("Error closing body:", err) - } - }(resp.Body) - - if err != nil { - fmt.Println("Error getting response:", err) - } - - document, err := goquery.NewDocumentFromReader(resp.Body) - - if err != nil { - fmt.Println("Error creating document:", err) - } - - var directDownloadLink string - var relativeLink string - var exists bool - - switch libgenType { - case LibgenOld: - directDownloadLink, exists = getLinkFromDocumentOld(document) - case LibgenNew: - u, _ := url.Parse(link) - // TODO: Add proper err handling - var host = u.Host - var protocol = u.Scheme - relativeLink, exists = getLinkFromDocumentNew(document) - - directDownloadLink = fmt.Sprintf("%s://%s/%s", protocol, host, relativeLink) - } - - if exists { - fmt.Println(successColor("Direct download link found")) - return directDownloadLink - } - - fmt.Println(errorColor("Direct download link not found")) - return "" -} - -func highlight(s string) string { - magenta := color.New(color.FgHiWhite).Add(color.BgBlack).SprintFunc() - return magenta(s) -} - -func errorColor(s string) string { - red := color.New(color.FgRed).SprintFunc() - return red(s) -} - -func infoColor(s string) string { - yellow := color.New(color.FgYellow).SprintFunc() - return yellow(s) -} - -func successColor(s string) string { - green := color.New(color.FgHiGreen).SprintFunc() - return green(s) -} - -func searchLibgen(query string, libgenSite Site) (document *goquery.Document, e error) { - var baseUrl string - - switch libgenSite { - case LibgenOld: - baseUrl = "https://libgen.is/search.php" - case LibgenNew: - baseUrl = "https://libgen.li/index.php" - } - - queryString := fmt.Sprintf("%s?req=%s&res=25&view=simple&phrase=1&column=def", baseUrl, url.QueryEscape(query)) - resp, e := http.Get(queryString) - - if e != nil { - return nil, e - } - - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - e = err - } - }(resp.Body) - - document, e = goquery.NewDocumentFromReader(resp.Body) - - if e != nil { - fmt.Println(e) - return nil, e - } - - return document, e -} - -func SearchBookByTitle(query string, limit int, libgenSite Site) (bookResults []Book, siteToUse Site, e error) { - fmt.Println("Searching for:", highlight(query)) - var document *goquery.Document - document, e = searchLibgen(query, siteToUse) - - if e != nil { - fmt.Println(errorColor("Error searching for book: " + query)) - failedSite := libgenSite - if failedSite == LibgenOld { - siteToUse = LibgenNew - } else { - siteToUse = LibgenOld - } - - fmt.Println(infoColor("Retrying with other site")) - document, e = searchLibgen(query, siteToUse) // If this also fails then we have a problem - } - fmt.Println(successColor("Search complete, parsing the document...")) - - bookResults = getBookDataFromDocument(document, siteToUse) - - if len(bookResults) >= limit { - bookResults = bookResults[:limit] - } - - return bookResults, siteToUse, e -} - -// DownloadSelection Downloads the file to current working directory -func DownloadSelection(selectedBook Book, libgenType Site) { - link := getDirectDownloadLink(selectedBook.Mirrors[0], libgenType) - fmt.Println(infoColor("Initializing download ")) - req, _ := http.NewRequest("GET", link, nil) - resp, error := http.DefaultClient.Do(req) - - if error != nil { - fmt.Println(errorColor("Error downloading file: " + error.Error())) - } - - defer resp.Body.Close() - filename := sanitize.Path(strings.Trim(selectedBook.Title, " ") + "." + selectedBook.Extension) - - f, _ := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0666) - defer f.Close() - - bar := progressbar.DefaultBytes( - resp.ContentLength, - "Downloading", - ) - - bytes, err := io.Copy(io.MultiWriter(f, bar), resp.Body) - - if bytes == 0 || err != nil { - fmt.Println(bytes, err) - } else { - fmt.Println(successColor("File successfully downloaded:"), f.Name()) - } -}