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