AnthoLume/search/search.go
Evan Reichard 841b29c425
All checks were successful
continuous-integration/drone/push Build is passing
improve(search): progress & retries
2024-12-01 17:04:41 -05:00

164 lines
3.6 KiB
Go

package search
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
log "github.com/sirupsen/logrus"
"reichard.io/antholume/metadata"
)
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"
type Cadence string
const (
CADENCE_TOP_YEAR Cadence = "y"
CADENCE_TOP_MONTH Cadence = "m"
)
type Source string
const (
SOURCE_ANNAS_ARCHIVE Source = "Annas Archive"
SOURCE_LIBGEN_FICTION Source = "LibGen Fiction"
SOURCE_LIBGEN_NON_FICTION Source = "LibGen Non-fiction"
)
type SearchItem struct {
ID string
Title string
Author string
Language string
Series string
FileType string
FileSize string
UploadDate string
}
type searchFunc func(query string) (searchResults []SearchItem, err error)
type downloadFunc func(md5 string, source Source) (downloadURL []string, err error)
var searchDefs = map[Source]searchFunc{
SOURCE_ANNAS_ARCHIVE: searchAnnasArchive,
SOURCE_LIBGEN_FICTION: searchLibGenFiction,
SOURCE_LIBGEN_NON_FICTION: searchLibGenNonFiction,
}
var downloadFuncs = []downloadFunc{
getLibGenDownloadURL,
getLibraryDownloadURL,
}
func SearchBook(query string, source Source) ([]SearchItem, error) {
searchFunc, found := searchDefs[source]
if !found {
return nil, fmt.Errorf("invalid source: %s", source)
}
log.Debug("Source: ", source)
return searchFunc(query)
}
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
}
}
return "", nil, errors.New("failed to download book")
}
func getPage(page string) (io.ReadCloser, error) {
log.Debug("URL: ", page)
// Set 10s Timeout
client := http.Client{Timeout: 10 * time.Second}
// Start Request
req, err := http.NewRequest("GET", page, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
// Do Request
resp, err := client.Do(req)
if err != nil {
return nil, err
}
// Return Body
return resp.Body, err
}
func downloadBook(bookURL string, progressFunc func(float32)) (string, error) {
log.Debug("URL: ", bookURL)
// Allow Insecure
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// Start Request
req, err := http.NewRequest("GET", bookURL, nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", userAgent)
// 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
}