Added books
This commit is contained in:
parent
afb40f9a2d
commit
6a6217ae31
188
internal/books/api_googlebooks.go
Normal file
188
internal/books/api_googlebooks.go
Normal file
@ -0,0 +1,188 @@
|
||||
package books
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hugo-medialog/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const googleBooksAPIBaseURL = "https://www.googleapis.com/books/v1/volumes?q=intitle:%s&key=%s"
|
||||
|
||||
// Function to search for a book by title
|
||||
func SearchBookByTitle(title string, book *Book) error {
|
||||
apiKey := os.Getenv("GOOGLE_BOOKS_API_KEY")
|
||||
url := fmt.Sprintf(googleBooksAPIBaseURL, url.QueryEscape(title), apiKey)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := result["items"].([]interface{})
|
||||
if len(items) == 0 {
|
||||
return fmt.Errorf("Book %s not found", title)
|
||||
}
|
||||
|
||||
// Get the ISBN from any of the results
|
||||
isbn, err := extractISBNFromResults(items)
|
||||
if err != nil {
|
||||
fmt.Printf("ISBN not found\n", isbn)
|
||||
} else {
|
||||
book.ISBN = isbn
|
||||
}
|
||||
|
||||
bookInfo := items[0].(map[string]interface{})["volumeInfo"].(map[string]interface{})
|
||||
|
||||
book.ID = items[0].(map[string]interface{})["id"].(string)
|
||||
book.Title = bookInfo["title"].(string)
|
||||
book.Author = bookInfo["authors"].([]interface{})[0].(string)
|
||||
book.Link = bookInfo["infoLink"].(string)
|
||||
if pageCount, ok := bookInfo["pageCount"].(float64); ok {
|
||||
book.Pages = int(pageCount)
|
||||
}
|
||||
if publishedDate, ok := bookInfo["publishedDate"].(string); ok {
|
||||
if len(publishedDate) >= 4 {
|
||||
book.Year, _ = strconv.Atoi(publishedDate[:4])
|
||||
}
|
||||
}
|
||||
|
||||
book.Image = getBookCoverByTitle(title)
|
||||
if err != nil {
|
||||
fmt.Printf("Image not found: %s\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractISBNFromResults(items []interface{}) (string, error) {
|
||||
// Loop over all the results and returns first ISBN found (ISBN_13 or
|
||||
// ISBN_10)
|
||||
for _, item := range items {
|
||||
bookInfo := item.(map[string]interface{})["volumeInfo"].(map[string]interface{})
|
||||
industryIdentifiers, ok := bookInfo["industryIdentifiers"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var isbn string
|
||||
for _, identifier := range industryIdentifiers {
|
||||
id := identifier.(map[string]interface{})
|
||||
// Primero intentamos con ISBN_13
|
||||
if id["type"] == "ISBN_13" {
|
||||
isbn = id["identifier"].(string)
|
||||
return isbn, nil
|
||||
} else if id["type"] == "ISBN_10" && isbn == "" {
|
||||
// Si no hay ISBN_13, intentamos con ISBN_10
|
||||
isbn = id["identifier"].(string)
|
||||
return isbn, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no ISBN found in any result")
|
||||
}
|
||||
|
||||
func getBookCoverByISBN(isbn string) string {
|
||||
openLibraryURL := fmt.Sprintf("https://covers.openlibrary.org/b/isbn/%s-L.jpg", isbn)
|
||||
if checkImageExists(openLibraryURL) {
|
||||
return openLibraryURL
|
||||
}
|
||||
|
||||
amazonURL := fmt.Sprintf("https://images-na.ssl-images-amazon.com/images/P/%s.jpg", isbn)
|
||||
if checkImageExists(amazonURL) {
|
||||
return amazonURL
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getBookCoverByTitle(title string) string {
|
||||
apiKey := os.Getenv("GOOGLE_CUSTOM_SEARCH_ENGINE_API_KEY")
|
||||
cx := os.Getenv("GOOGLE_CX")
|
||||
|
||||
searchURL := fmt.Sprintf("https://www.googleapis.com/customsearch/v1?q=%s+book+cover&key=%s&searchType=image&num=1&cx=%s", url.QueryEscape(title), apiKey, cx)
|
||||
|
||||
resp, err := http.Get(searchURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching cover from Google Images API: %s\n", err)
|
||||
return "default-cover.jpg"
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Error fetching cover, status code: %d\n", resp.StatusCode)
|
||||
return "default-cover.jpg"
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
fmt.Printf("Error decoding JSON response: %s\n", err)
|
||||
return "default-cover.jpg"
|
||||
}
|
||||
|
||||
if items, ok := result["items"].([]interface{}); ok && len(items) > 0 {
|
||||
if link, ok := items[0].(map[string]interface{})["link"].(string); ok {
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
||||
return "default-cover.jpg"
|
||||
}
|
||||
|
||||
func checkImageExists(imageURL string) bool {
|
||||
resp, err := http.Head(imageURL)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DownloadImage(url, slug string) error {
|
||||
imageDir := filepath.Join(os.Getenv("MARKDOWN_OUTPUT_BOOKS_DIR"), os.Getenv("IMAGES_OUTPUT_DIR"))
|
||||
if err := utils.CreateDirIfNotExists(imageDir); err != nil {
|
||||
return err
|
||||
}
|
||||
filename := fmt.Sprintf("%s.jpg", slug)
|
||||
filePath := filepath.Join(imageDir, filename)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error downloading image: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("non-200 response while downloading image: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating image file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading image data: %w", err)
|
||||
}
|
||||
|
||||
_, err = file.Write(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing image data: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" - Image saved successfully at: %s\n", filePath)
|
||||
return nil
|
||||
}
|
@ -1 +1,86 @@
|
||||
package books
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hugo-medialog/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func LoadBooks() ([]Book, error) {
|
||||
booksFile := os.Getenv("OBSIDIAN_BOOKS_FILE")
|
||||
fileData, err := os.ReadFile(booksFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var books []Book
|
||||
err = yaml.Unmarshal(fileData, &books)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return books, nil
|
||||
}
|
||||
|
||||
func ProcessBooks(books []Book) error {
|
||||
utils.Sep()
|
||||
for _, book := range books {
|
||||
fmt.Printf("Title: %s, Author: %s, ID: %s\n", book.Title, book.Author, book.ID)
|
||||
|
||||
// If we dont have ID, search movie by Title and get the ID
|
||||
if book.ID == "" {
|
||||
err := SearchBookByTitle(book.Title, &book)
|
||||
if err != nil {
|
||||
fmt.Printf("Error searching book by title %s: %s\n", book.Title, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
book.Slug = utils.Sluggify(book.Title)
|
||||
|
||||
// Now we need to get the image
|
||||
imageURL := getBookCoverByTitle(book.Title)
|
||||
if imageURL != "" {
|
||||
err := DownloadImage(imageURL, book.Slug)
|
||||
if err != nil {
|
||||
fmt.Printf("Error downloading %s: %s\n", imageURL, err)
|
||||
}
|
||||
}
|
||||
book.Image = fmt.Sprintf("%s.jpg", book.Slug)
|
||||
|
||||
err := generateBookMarkdown(book)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Sep()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateBookMarkdown(book Book) error {
|
||||
templatePath := filepath.Join(os.Getenv("TEMPLATES_DIR"), "book.md.tpl")
|
||||
outputDir := os.Getenv("MARKDOWN_OUTPUT_BOOKS_DIR")
|
||||
if err := utils.CreateDirIfNotExists(outputDir); err != nil {
|
||||
return err
|
||||
}
|
||||
outputPath := filepath.Join(outputDir, fmt.Sprintf("%s.md", book.Slug))
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Title": book.Title,
|
||||
"Author": book.Author,
|
||||
"Link": book.Link,
|
||||
"Subtitle": book.Year,
|
||||
"Year": book.Year,
|
||||
"Rate": book.Rate,
|
||||
"Pages": book.Pages,
|
||||
"Progress": book.Progress,
|
||||
"Image": book.Image,
|
||||
"Date": book.Date,
|
||||
"Tags": "reading",
|
||||
}
|
||||
|
||||
return utils.GenerateMarkdown(templatePath, outputPath, data)
|
||||
}
|
||||
|
@ -2,14 +2,16 @@ package books
|
||||
|
||||
type Book struct {
|
||||
Title string `yaml:"title"`
|
||||
Slug string `yaml:"slug"`
|
||||
Author string `yaml:"author"`
|
||||
Link string `yaml:"link"`
|
||||
Year int `yaml:"year"`
|
||||
ID string `yaml:"id"`
|
||||
ISBN string `yaml:"isbn"`
|
||||
Rate float64 `yaml:"rate"`
|
||||
Pages int `yaml:"pages"`
|
||||
Progress string `yaml:"progress"`
|
||||
Image string `yaml:"image"`
|
||||
Poster string `yaml:"poster-image"`
|
||||
Background string `yaml:"background-image"`
|
||||
Date string `yaml:"date"`
|
||||
Tags []string
|
||||
}
|
||||
|
12
main.go
12
main.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hugo-medialog/internal/books"
|
||||
"hugo-medialog/internal/movies"
|
||||
"hugo-medialog/internal/series"
|
||||
"hugo-medialog/utils"
|
||||
@ -33,4 +34,15 @@ func main() {
|
||||
fmt.Printf("Error processing series: %v\n", err)
|
||||
}
|
||||
|
||||
// Books
|
||||
booksList, err := books.LoadBooks()
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading books file: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = books.ProcessBooks(booksList)
|
||||
if err != nil {
|
||||
fmt.Printf("Error processing books: %v\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,10 +5,9 @@ subtitle: "{{ .Subtitle }}"
|
||||
author: "{{ .Author }}"
|
||||
year: {{ .Year }}
|
||||
rate: {{ .Rate }}
|
||||
pages: {{ .Pages }}
|
||||
progress: {{ .Progress }}
|
||||
image: {{ .Image }}
|
||||
poster-image: {{ .PosterImage }}
|
||||
background-image: {{ .BackgroundImage }}
|
||||
date: {{ .Date }}
|
||||
draft: false
|
||||
tags: {{ .Tags }}
|
||||
|
Loading…
Reference in New Issue
Block a user