2023-10-07 01:25:56 +00:00
|
|
|
package search
|
|
|
|
|
|
|
|
import (
|
2024-01-22 22:43:47 +00:00
|
|
|
"crypto/tls"
|
2024-12-01 22:02:53 +00:00
|
|
|
"errors"
|
2023-11-25 23:38:18 +00:00
|
|
|
"fmt"
|
2023-10-07 01:25:56 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-12-01 22:02:53 +00:00
|
|
|
"reichard.io/antholume/metadata"
|
2023-10-07 01:25:56 +00:00
|
|
|
)
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
const userAgent string = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
2024-01-22 22:43:47 +00:00
|
|
|
|
2023-10-07 01:25:56 +00:00
|
|
|
type Cadence string
|
|
|
|
|
|
|
|
const (
|
2023-11-25 23:38:18 +00:00
|
|
|
CADENCE_TOP_YEAR Cadence = "y"
|
|
|
|
CADENCE_TOP_MONTH Cadence = "m"
|
2023-10-07 01:25:56 +00:00
|
|
|
)
|
|
|
|
|
2023-11-25 23:38:18 +00:00
|
|
|
type Source string
|
|
|
|
|
|
|
|
const (
|
|
|
|
SOURCE_ANNAS_ARCHIVE Source = "Annas Archive"
|
|
|
|
SOURCE_LIBGEN_FICTION Source = "LibGen Fiction"
|
|
|
|
SOURCE_LIBGEN_NON_FICTION Source = "LibGen Non-fiction"
|
|
|
|
)
|
|
|
|
|
2023-10-07 01:25:56 +00:00
|
|
|
type SearchItem struct {
|
|
|
|
ID string
|
|
|
|
Title string
|
|
|
|
Author string
|
|
|
|
Language string
|
|
|
|
Series string
|
|
|
|
FileType string
|
|
|
|
FileSize string
|
|
|
|
UploadDate string
|
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
type searchFunc func(query string) (searchResults []SearchItem, err error)
|
|
|
|
type downloadFunc func(md5 string, source Source) (downloadURL []string, err error)
|
2023-10-07 01:25:56 +00:00
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
var searchDefs = map[Source]searchFunc{
|
|
|
|
SOURCE_ANNAS_ARCHIVE: searchAnnasArchive,
|
|
|
|
SOURCE_LIBGEN_FICTION: searchLibGenFiction,
|
|
|
|
SOURCE_LIBGEN_NON_FICTION: searchLibGenNonFiction,
|
2023-10-07 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
var downloadFuncs = []downloadFunc{
|
|
|
|
getLibGenDownloadURL,
|
|
|
|
getLibraryDownloadURL,
|
2023-10-07 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
func SearchBook(query string, source Source) ([]SearchItem, error) {
|
|
|
|
searchFunc, found := searchDefs[source]
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("invalid source: %s", source)
|
2023-10-07 01:25:56 +00:00
|
|
|
}
|
2024-12-01 22:02:53 +00:00
|
|
|
log.Debug("Source: ", source)
|
|
|
|
return searchFunc(query)
|
2023-10-07 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
func SaveBook(md5 string, source Source, progressFunc func(float32)) (string, *metadata.MetadataInfo, error) {
|
|
|
|
for _, f := range downloadFuncs {
|
|
|
|
downloadURLs, err := f(md5, source)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to acquire download urls")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, bookURL := range downloadURLs {
|
|
|
|
// Download File
|
|
|
|
log.Info("Downloading Book: ", bookURL)
|
|
|
|
fileName, err := downloadBook(bookURL, progressFunc)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Book URL API Failure: ", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Metadata
|
|
|
|
metadata, err := metadata.GetMetadata(fileName)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Book Metadata Failure: ", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileName, metadata, nil
|
|
|
|
}
|
2023-11-25 23:38:18 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
return "", nil, errors.New("failed to download book")
|
2023-11-25 23:38:18 +00:00
|
|
|
}
|
|
|
|
|
2023-10-26 10:20:56 +00:00
|
|
|
func getPage(page string) (io.ReadCloser, error) {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Debug("URL: ", page)
|
2023-11-25 23:38:18 +00:00
|
|
|
|
2023-10-26 10:20:56 +00:00
|
|
|
// Set 10s Timeout
|
2024-08-14 02:32:16 +00:00
|
|
|
client := http.Client{Timeout: 10 * time.Second}
|
|
|
|
|
|
|
|
// Start Request
|
|
|
|
req, err := http.NewRequest("GET", page, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-10-26 10:20:56 +00:00
|
|
|
}
|
2024-08-14 02:32:16 +00:00
|
|
|
req.Header.Set("User-Agent", userAgent)
|
|
|
|
|
|
|
|
// Do Request
|
|
|
|
resp, err := client.Do(req)
|
2023-10-26 10:20:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return Body
|
|
|
|
return resp.Body, err
|
2023-10-07 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
func downloadBook(bookURL string, progressFunc func(float32)) (string, error) {
|
2024-08-14 02:32:16 +00:00
|
|
|
log.Debug("URL: ", bookURL)
|
|
|
|
|
2024-01-22 22:43:47 +00:00
|
|
|
// Allow Insecure
|
2024-08-14 02:32:16 +00:00
|
|
|
client := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
},
|
|
|
|
}
|
2024-01-22 22:43:47 +00:00
|
|
|
|
|
|
|
// Start Request
|
|
|
|
req, err := http.NewRequest("GET", bookURL, nil)
|
|
|
|
if err != nil {
|
2024-12-01 22:02:53 +00:00
|
|
|
return "", err
|
2024-01-22 22:43:47 +00:00
|
|
|
}
|
|
|
|
req.Header.Set("User-Agent", userAgent)
|
|
|
|
|
2024-12-01 22:02:53 +00:00
|
|
|
// Perform API Request
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create File
|
|
|
|
tempFile, err := os.CreateTemp("", "book")
|
|
|
|
if err != nil {
|
|
|
|
log.Error("File Create Error: ", err)
|
|
|
|
return "", fmt.Errorf("failed to create temp file: %w", err)
|
|
|
|
}
|
|
|
|
defer tempFile.Close()
|
|
|
|
|
|
|
|
// Copy File to Disk
|
|
|
|
log.Info("Saving Book")
|
|
|
|
counter := &writeCounter{Total: resp.ContentLength, ProgressFunction: progressFunc}
|
|
|
|
_, err = io.Copy(tempFile, io.TeeReader(resp.Body, counter))
|
|
|
|
if err != nil {
|
|
|
|
os.Remove(tempFile.Name())
|
|
|
|
log.Error("File Copy Error: ", err)
|
|
|
|
return "", fmt.Errorf("failed to copy response to temp file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tempFile.Name(), nil
|
2024-01-22 22:43:47 +00:00
|
|
|
}
|