-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
150 lines (139 loc) · 3.47 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
import (
"context"
"flag"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"net/http"
"net/url"
"os"
"path"
"runtime"
"strings"
_ "github.com/bake/mri"
"github.com/cheggaaa/pb/v3"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
)
// client describes the interface that each supported site has to implement.
type client interface {
// Match returns true if the given URL can be processed by this client.
Match(url *url.URL) bool
// Files return a slice of URLs of images.
Files(url *url.URL) ([]string, error)
// Authenticate can optionally modify a request and add necessary headers.
Authenticate(req *http.Request)
}
type clients []client
func (cs clients) filter(url *url.URL) client {
for _, c := range cs {
if !c.Match(url) {
continue
}
return c
}
return nil
}
var version = "development"
func main() {
out := flag.String("out", ".", "Download directory")
format := flag.String("format", "jpg", "Encode images as GIF, JPG or PNG")
worker := flag.Int("worker", runtime.NumCPU(), "Concurrent downloads")
progress := flag.Bool("progress", false, "Show a progressbar")
flag.Usage = func() {
flag.CommandLine.SetOutput(os.Stderr)
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s ", os.Args[0])
fmt.Fprintf(flag.CommandLine.Output(), "[-format=%s] [-out=%s] [-worker=%d] [-progress] url\n", *format, *out, *worker)
fmt.Fprintf(flag.CommandLine.Output(), "Flags:\n")
flag.PrintDefaults()
fmt.Fprintf(flag.CommandLine.Output(), "Version: %s\n", version)
}
flag.Parse()
if flag.NArg() < 1 {
flag.Usage()
os.Exit(1)
}
url, err := url.Parse(flag.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "could not parse URL: %v\n", err)
os.Exit(1)
}
cs := clients{
newDongmanClient(),
newKuaikanClient(),
newMangaDexClient(),
newTapasClient(),
}
c := cs.filter(url)
if c == nil {
fmt.Fprintf(os.Stderr, "the URL is not supported\n")
os.Exit(1)
}
files, err := c.Files(url)
if err != nil {
fmt.Fprintf(os.Stderr, "could not get files: %v\n", err)
os.Exit(1)
}
sem := semaphore.NewWeighted(int64(*worker))
ctx := context.Background()
var bar *pb.ProgressBar
if *progress {
bar = pb.StartNew(len(files))
defer bar.Finish()
}
defer sem.Acquire(ctx, int64(*worker))
for i, file := range files {
i, file := i, file
sem.Acquire(ctx, 1)
go func() {
defer sem.Release(1)
if bar != nil {
defer bar.Increment()
}
dst := path.Join(*out, fmt.Sprintf("%04d.%s", i, *format))
err := download(c, dst, file, *format)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}()
}
}
func download(c client, dst, src, format string) error {
req, err := http.NewRequest(http.MethodGet, src, nil)
if err != nil {
return errors.Wrap(err, "could not generate new request")
}
c.Authenticate(req)
res, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "cound not download image")
}
defer res.Body.Close()
img, _, err := image.Decode(res.Body)
if err != nil {
return errors.Wrap(err, "could not decode image")
}
w, err := os.Create(dst)
if err != nil {
return errors.Wrap(err, "could not create file")
}
defer w.Close()
return encode(w, img, format)
}
func encode(w io.Writer, img image.Image, format string) error {
switch strings.ToLower(format) {
case "gif":
return gif.Encode(w, img, nil)
case "jpg", "jpeg":
return jpeg.Encode(w, img, nil)
case "png":
return png.Encode(w, img)
default:
return errors.Errorf("unexpected format %q", format)
}
}