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)
|
||||||
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package books
|
package books
|
||||||
|
|
||||||
type Book struct {
|
type Book struct {
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title"`
|
||||||
Author string `yaml:"author"`
|
Slug string `yaml:"slug"`
|
||||||
Link string `yaml:"link"`
|
Author string `yaml:"author"`
|
||||||
Year int `yaml:"year"`
|
Link string `yaml:"link"`
|
||||||
Rate float64 `yaml:"rate"`
|
Year int `yaml:"year"`
|
||||||
Progress string `yaml:"progress"`
|
ID string `yaml:"id"`
|
||||||
Image string `yaml:"image"`
|
ISBN string `yaml:"isbn"`
|
||||||
Poster string `yaml:"poster-image"`
|
Rate float64 `yaml:"rate"`
|
||||||
Background string `yaml:"background-image"`
|
Pages int `yaml:"pages"`
|
||||||
Date string `yaml:"date"`
|
Progress string `yaml:"progress"`
|
||||||
Tags []string
|
Image string `yaml:"image"`
|
||||||
|
Date string `yaml:"date"`
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Books []Book
|
type Books []Book
|
||||||
|
12
main.go
12
main.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hugo-medialog/internal/books"
|
||||||
"hugo-medialog/internal/movies"
|
"hugo-medialog/internal/movies"
|
||||||
"hugo-medialog/internal/series"
|
"hugo-medialog/internal/series"
|
||||||
"hugo-medialog/utils"
|
"hugo-medialog/utils"
|
||||||
@ -33,4 +34,15 @@ func main() {
|
|||||||
fmt.Printf("Error processing series: %v\n", err)
|
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 }}"
|
author: "{{ .Author }}"
|
||||||
year: {{ .Year }}
|
year: {{ .Year }}
|
||||||
rate: {{ .Rate }}
|
rate: {{ .Rate }}
|
||||||
|
pages: {{ .Pages }}
|
||||||
progress: {{ .Progress }}
|
progress: {{ .Progress }}
|
||||||
image: {{ .Image }}
|
image: {{ .Image }}
|
||||||
poster-image: {{ .PosterImage }}
|
|
||||||
background-image: {{ .BackgroundImage }}
|
|
||||||
date: {{ .Date }}
|
date: {{ .Date }}
|
||||||
draft: false
|
draft: false
|
||||||
tags: {{ .Tags }}
|
tags: {{ .Tags }}
|
||||||
|
Loading…
Reference in New Issue
Block a user