shiori/internal/cmd/add.go

184 lines
4.4 KiB
Go
Raw Normal View History

2019-05-21 11:31:40 +08:00
package cmd
import (
2019-06-09 15:54:07 +08:00
"bytes"
2019-05-22 00:24:11 +08:00
"fmt"
2019-06-09 15:54:07 +08:00
"io"
"net/http"
2019-05-22 00:24:11 +08:00
nurl "net/url"
fp "path/filepath"
"strings"
"time"
2019-06-09 15:54:07 +08:00
"github.com/go-shiori/shiori/pkg/warc"
2019-05-22 00:24:11 +08:00
"github.com/go-shiori/go-readability"
"github.com/go-shiori/shiori/internal/model"
2019-05-21 11:31:40 +08:00
"github.com/spf13/cobra"
)
func addCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add url",
Short: "Bookmark the specified URL",
Args: cobra.ExactArgs(1),
2019-05-22 00:24:11 +08:00
Run: addHandler,
2019-05-21 11:31:40 +08:00
}
2019-06-09 18:02:16 +08:00
cmd.Flags().StringP("title", "i", "", "Custom title for this bookmark")
cmd.Flags().StringP("excerpt", "e", "", "Custom excerpt for this bookmark")
cmd.Flags().StringSliceP("tags", "t", []string{}, "Comma-separated tags for this bookmark")
cmd.Flags().BoolP("offline", "o", false, "Save bookmark without fetching data from internet")
2019-06-10 16:49:54 +08:00
cmd.Flags().BoolP("no-archival", "a", false, "Save bookmark without creating offline archive")
2019-06-10 01:20:24 +08:00
cmd.Flags().Bool("log-archival", false, "Log the archival process")
2019-05-21 11:31:40 +08:00
return cmd
}
2019-05-22 00:24:11 +08:00
func addHandler(cmd *cobra.Command, args []string) {
// Read flag and arguments
url := args[0]
title, _ := cmd.Flags().GetString("title")
excerpt, _ := cmd.Flags().GetString("excerpt")
tags, _ := cmd.Flags().GetStringSlice("tags")
offline, _ := cmd.Flags().GetBool("offline")
2019-06-10 16:49:54 +08:00
noArchival, _ := cmd.Flags().GetBool("no-archival")
2019-06-10 01:20:24 +08:00
logArchival, _ := cmd.Flags().GetBool("log-archival")
2019-05-22 00:24:11 +08:00
// Clean up URL by removing its fragment and UTM parameters
tmp, err := nurl.Parse(url)
if err != nil || tmp.Scheme == "" || tmp.Hostname() == "" {
cError.Println("URL is not valid")
return
}
tmp.Fragment = ""
clearUTMParams(tmp)
// Create bookmark item
book := model.Bookmark{
URL: tmp.String(),
Title: normalizeSpace(title),
Excerpt: normalizeSpace(excerpt),
}
2019-05-22 17:13:52 +08:00
// Create bookmark ID
book.ID, err = DB.CreateNewID("bookmark")
if err != nil {
cError.Printf("Failed to create ID: %v\n", err)
return
}
2019-05-22 00:24:11 +08:00
// Set bookmark tags
book.Tags = make([]model.Tag, len(tags))
for i, tag := range tags {
book.Tags[i].Name = strings.TrimSpace(tag)
}
// If it's not offline mode, fetch data from internet
2019-05-24 14:25:29 +08:00
var imageURLs []string
2019-05-22 00:24:11 +08:00
if !offline {
func() {
cInfo.Println("Downloading article...")
2019-06-09 15:54:07 +08:00
// Prepare request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
cError.Printf("Failed to download article: %v\n", err)
return
}
// Send request
req.Header.Set("User-Agent", "Shiori/2.0.0 (+https://github.com/go-shiori/shiori)")
resp, err := httpClient.Do(req)
if err != nil {
cError.Printf("Failed to download article: %v\n", err)
return
}
defer resp.Body.Close()
2019-06-09 15:54:07 +08:00
// Save as archive
2019-06-10 16:49:54 +08:00
var readabilityInput io.Reader = resp.Body
if !noArchival {
buffer := bytes.NewBuffer(nil)
archivePath := fp.Join(DataDir, "archive", fmt.Sprintf("%d", book.ID))
archivalRequest := warc.ArchivalRequest{
URL: url,
Reader: io.TeeReader(resp.Body, buffer),
ContentType: resp.Header.Get("Content-Type"),
LogEnabled: logArchival,
}
err = warc.NewArchive(archivalRequest, archivePath)
if err != nil {
cError.Printf("Failed to create archive: %v\n", err)
return
}
readabilityInput = buffer
2019-06-09 15:54:07 +08:00
}
// Parse article
2019-06-10 16:49:54 +08:00
article, err := readability.FromReader(readabilityInput, url)
if err != nil {
cError.Printf("Failed to parse article: %v\n", err)
return
}
book.Author = article.Byline
book.Content = article.TextContent
book.HTML = article.Content
// If title and excerpt doesnt have submitted value, use from article
if book.Title == "" {
book.Title = article.Title
}
if book.Excerpt == "" {
book.Excerpt = article.Excerpt
}
// Get image URL
if article.Image != "" {
2019-05-24 14:25:29 +08:00
imageURLs = append(imageURLs, article.Image)
}
2019-05-24 14:25:29 +08:00
if article.Favicon != "" {
imageURLs = append(imageURLs, article.Favicon)
}
}()
2019-05-22 00:24:11 +08:00
}
// Make sure title is not empty
if book.Title == "" {
book.Title = book.URL
}
// Save bookmark to database
2019-05-22 17:13:52 +08:00
_, err = DB.SaveBookmarks(book)
2019-05-22 00:24:11 +08:00
if err != nil {
2019-05-22 17:13:52 +08:00
cError.Printf("Failed to save bookmark: %v\n", err)
2019-05-22 00:24:11 +08:00
return
}
// Save article image to local disk
2019-05-24 14:25:29 +08:00
imgPath := fp.Join(DataDir, "thumb", fmt.Sprintf("%d", book.ID))
for _, imageURL := range imageURLs {
err = downloadBookImage(imageURL, imgPath, time.Minute)
if err == nil {
break
} else {
2019-05-22 00:24:11 +08:00
cError.Printf("Failed to download image: %v\n", err)
2019-05-24 14:25:29 +08:00
continue
2019-05-22 00:24:11 +08:00
}
}
2019-05-22 17:13:52 +08:00
// Print added bookmark
fmt.Println()
2019-05-22 00:24:11 +08:00
printBookmarks(book)
}