link2epub/main.go
main.goRaw
1package main
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "log"
8 "net/http"
9 "os"
10 "os/exec"
11 "regexp"
12 "strconv"
13 "strings"
14
15 readability "github.com/go-shiori/go-readability"
16)
17
18const tmpDir = "tmp"
19
20func main() {
21 // var typeFlag string
22 linkFlag := flag.String("l", "", "Link to download")
23 typeFlag := flag.String("type", "mobi", "Type of epub. Available: mobi (default), epub")
24
25 flag.Parse()
26
27 if *typeFlag != "mobi" && *typeFlag != "epub" {
28 log.Fatal("not valid type")
29 }
30 err := os.Mkdir(tmpDir, os.ModePerm)
31 if err != nil {
32 log.Fatalf("error creating tmp dir %s: %v\n", tmpDir, err)
33 }
34
35 // get link
36 fmt.Println("\n> getting the link")
37 resp, err := http.Get(*linkFlag)
38 if err != nil {
39 log.Fatalf("failed to download %s: %v\n", *linkFlag, err)
40 }
41 defer resp.Body.Close()
42
43 // convert the html to simple html with go-readability
44 article, err := readability.FromReader(resp.Body, *linkFlag)
45 if err != nil {
46 log.Fatalf("failed to parse %s: %v\n", *linkFlag, err)
47 }
48
49 fmt.Printf(" URL : %s\n", *linkFlag)
50 fmt.Printf(" Title : %s\n", article.Title)
51 fmt.Printf(" Author : %s\n", article.Byline)
52 fmt.Printf(" Length : %d\n", article.Length)
53 fmt.Printf(" Excerpt : %s\n", article.Excerpt)
54 fmt.Printf(" SiteName: %s\n", article.SiteName)
55 fmt.Printf(" Image : %s\n", article.Image)
56 fmt.Printf(" Favicon : %s\n", article.Favicon)
57
58 // get images
59 fmt.Println("\n>getting the images")
60 imgRegex := regexp.MustCompile(`(<img )([^>]*)(src=")([^"]*)"`)
61 imgs := imgRegex.FindAllSubmatch([]byte(article.Content), -1)
62 for i, img := range imgs {
63 fmt.Println(" img", i, string(img[4]))
64 filename, err := downloadImg(string(img[4]), strconv.Itoa(i))
65 if err != nil {
66 log.Fatalf("error in downloadImg %s: %v\n", img[4], err)
67 }
68
69 // replace in the article.Content the current img by new filename
70 article.Content = strings.Replace(article.Content, string(img[4]), filename, -1)
71 }
72
73 // store html file
74 filename := article.Title + " - " + article.Byline
75 out, err := os.Create(tmpDir + "/" + filename + ".html")
76 if err != nil {
77 log.Fatalf("failed creating index.xhtml: %v\n", err)
78 }
79 defer out.Close()
80
81 _, err = out.Write([]byte(article.Content))
82 if err != nil {
83 log.Fatalf("failed writting index.html: %v\n", err)
84 }
85 out.Sync()
86
87 // call calibre to convert the html to epub/mobi
88 fmt.Println("\n>converting to", *typeFlag)
89 cmd := exec.Command("ebook-convert", tmpDir+"/"+filename+".html", filename+"."+*typeFlag)
90 if err := cmd.Run(); err != nil {
91 log.Fatalf("failed converting the html to %s: %v\n", *typeFlag, err)
92 }
93
94 // delete tmp dir
95 cmd = exec.Command("rm", "-rf", tmpDir)
96 if err := cmd.Run(); err != nil {
97 log.Fatalf("failed removing the tmp dir %s: %v\n", tmpDir, err)
98 }
99}
100
101func downloadImg(url string, path string) (string, error) {
102 url = strings.Replace(url, "/max/60/", "/max/1000/", -1) // for "medium.com" api
103 resp, err := http.Get(url)
104 if err != nil {
105 return "", err
106 }
107 defer resp.Body.Close()
108 body, err := ioutil.ReadAll(resp.Body)
109 if err != nil {
110 return "", err
111 }
112
113 contentType := http.DetectContentType(body)
114 filename := path + "." + strings.Replace(contentType, "image/", "", -1)
115
116 out, err := os.Create(tmpDir + "/" + filename)
117 if err != nil {
118 return "", err
119 }
120 defer out.Close()
121
122 _, err = out.Write(body)
123 if err != nil {
124 return "", err
125 }
126 out.Sync()
127
128 return filename, nil
129}