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 = "link2epubtmpdir"
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 titleFlag := flag.String("title", "", "Title is automatically getted from article, if want to change it, use this flag")
25
26 flag.Parse()
27
28 if *typeFlag != "mobi" && *typeFlag != "epub" {
29 log.Fatal("not valid type")
30 }
31 err := os.Mkdir(tmpDir, os.ModePerm)
32 if err != nil {
33 log.Fatalf("error creating tmp dir %s: %v\n", tmpDir, err)
34 }
35
36 // get link
37 fmt.Println("\n> getting the link")
38 resp, err := http.Get(*linkFlag)
39 if err != nil {
40 log.Fatalf("failed to download %s: %v\n", *linkFlag, err)
41 }
42 defer resp.Body.Close()
43
44 // convert the html to simple html with go-readability
45 article, err := readability.FromReader(resp.Body, *linkFlag)
46 if err != nil {
47 log.Fatalf("failed to parse %s: %v\n", *linkFlag, err)
48 }
49
50 fmt.Printf(" URL : %s\n", *linkFlag)
51 fmt.Printf(" Title : %s\n", article.Title)
52 fmt.Printf(" Author : %s\n", article.Byline)
53 fmt.Printf(" Length : %d\n", article.Length)
54 fmt.Printf(" Excerpt : %s\n", article.Excerpt)
55 fmt.Printf(" SiteName: %s\n", article.SiteName)
56 fmt.Printf(" Image : %s\n", article.Image)
57 fmt.Printf(" Favicon : %s\n", article.Favicon)
58
59 // get images
60 fmt.Println("\n>getting the images")
61 imgRegex := regexp.MustCompile(`(<img )([^>]*)(src=")([^"]*)"`)
62 imgs := imgRegex.FindAllSubmatch([]byte(article.Content), -1)
63 for i, img := range imgs {
64 fmt.Println(" img", i, string(img[4]))
65 filename, err := downloadImg(string(img[4]), strconv.Itoa(i))
66 if err != nil {
67 log.Fatalf("error in downloadImg %s: %v\n", img[4], err)
68 }
69
70 // replace in the article.Content the current img by new filename
71 article.Content = strings.Replace(article.Content, string(img[4]), filename, -1)
72 }
73
74 if *titleFlag != "" {
75 article.Title = *titleFlag
76 }
77
78 // add title to content
79 article.Content = `
80 <h1>` + article.Title + `</h1>
81 <h2 style="text-align:right;">` + article.Byline + `</h2>
82 <br>
83 ` + article.Content
84
85 // store html file
86 filename := article.Title + " - " + article.Byline
87 out, err := os.Create(tmpDir + "/" + filename + ".html")
88 if err != nil {
89 log.Fatalf("failed creating index.xhtml: %v\n", err)
90 }
91 defer out.Close()
92
93 _, err = out.Write([]byte(article.Content))
94 if err != nil {
95 log.Fatalf("failed writting index.html: %v\n", err)
96 }
97 out.Sync()
98
99 // call calibre to convert the html to epub/mobi
100 fmt.Println("\n>converting to", *typeFlag)
101 cmd := exec.Command("ebook-convert", tmpDir+"/"+filename+".html", filename+"."+*typeFlag)
102 if err := cmd.Run(); err != nil {
103 log.Fatalf("failed converting the html to %s: %v\n", *typeFlag, err)
104 }
105
106 // delete tmp dir
107 cmd = exec.Command("rm", "-rf", tmpDir)
108 if err := cmd.Run(); err != nil {
109 log.Fatalf("failed removing the tmp dir %s: %v\n", tmpDir, err)
110 }
111}
112
113func downloadImg(url string, path string) (string, error) {
114 url = strings.Replace(url, "/max/60/", "/max/1000/", -1) // for "medium.com" api
115 resp, err := http.Get(url)
116 if err != nil {
117 return "", err
118 }
119 defer resp.Body.Close()
120 body, err := ioutil.ReadAll(resp.Body)
121 if err != nil {
122 return "", err
123 }
124
125 contentType := http.DetectContentType(body)
126 filename := path + "." + strings.Replace(contentType, "image/", "", -1)
127
128 out, err := os.Create(tmpDir + "/" + filename)
129 if err != nil {
130 return "", err
131 }
132 defer out.Close()
133
134 _, err = out.Write(body)
135 if err != nil {
136 return "", err
137 }
138 out.Sync()
139
140 return filename, nil
141}