-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtumblr.go
355 lines (319 loc) · 6.2 KB
/
tumblr.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
package main
import (
"encoding/json"
"errors"
"fmt"
"github.com/mrjones/oauth"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
var (
tumbHost = "https://api.tumblr.com" //needs to be replaced for testing
tumbV = "/v2/"
tumbFollowing = tumbV + "user/following"
tumbPosts = tumbV + "blog/%s/posts?api_key=%s&offset=%d"
)
func Tumblr(f File, ch chan File) {
tumbUri, _ := url.Parse(tumbHost)
p := f.Url.Path
if len(p) == 0 || p == "/" {
tok, err := OAuth()
if err != nil {
f.SendErr(ch, &err)
return
}
cons := oauth.Consumer{HttpClient: HClient}
for i := 0; ; i += 20 {
resp, err := cons.Get(tumbHost+tumbFollowing,
map[string]string{"offset": strconv.Itoa(i)}, tok)
check(err)
var fr followingResponse
checkResponse(resp, &fr)
for _, b := range fr.Blogs {
bUri, err := url.Parse(b.Url)
check(err)
bUri.Path = bUri.Host
ch <- File{Url: *bUri}
}
if i >= fr.Total_blogs {
break
}
}
} else {
tok, _, err := Keychain(*tumbUri)
if err != nil {
f.SendErr(ch, &err)
return
}
err = tumblrBlog(f, tok, ch)
f.SendErr(ch, &err)
}
return
}
func tumblrBlog(fl File, key string, files chan File) (err error) {
for i := int64(0); ; i += 20 {
// println(fl.Url.String())
blog := strings.Trim(fl.Url.Path, "/")
u := fmt.Sprintf(tumbHost+tumbPosts, blog, key, i)
var r *http.Response
r, err = HClient.Get(u)
if err != nil {
return
}
var br blogResponse
checkResponse(r, &br)
for _, rawPost := range br.Posts {
tumblrPost(fl, rawPost, files)
}
if i >= br.Blog.Posts {
break
}
}
return nil
}
func tumblrPost(f File, raw []byte, ch chan File) {
var bp basePost
err := json.Unmarshal(raw, &bp)
if err != nil {
f.SendErr(ch, &err)
return
}
f.Mtime = time.Unix(bp.Timestamp, 0)
id := strconv.FormatInt(bp.Id, 10)
f.Path += id
var p interface{}
ptype := bp.PostType
if ptype == "photo" {
p = tumblrPhoto(f, raw, ch)
} else {
if ptype == "video" {
f, p = tumblrVideo(f, raw)
} else {
f, p = tumblrText(f, raw, ptype)
}
ch <- f
}
// store metadata in json file
f.Path += ".json"
b, err := json.MarshalIndent(p, "", "\t")
f.Err = err
f.Body = b
if f.Err == nil {
f.SetLeaf()
}
ch <- f.SetLeaf()
}
func tumblrVideo(f File, raw []byte) (File, videoPost) {
p := videoPost{}
err := json.Unmarshal(raw, &p)
if err != nil {
f.Err = err
return f, p
}
pl := p.Player
r := regexp.MustCompile(`src="(.+?)" `)
s := pl[len(pl)-1].Embed_code
rm := r.FindAllStringSubmatch(s, 1)
if len(rm) == 1 && len(rm[0]) == 2 {
vurl := rm[0][1]
u, _ := url.Parse(vurl)
if u.Scheme == "" {
u.Scheme = "http"
}
f.Url = *u
if strings.HasSuffix(f.Url.Host, "tumblr.com") {
f = f.SetLeaf()
}
} else {
f.Err = errors.New("Videopost: " + s)
}
f.Path += "-"
return f, p
}
func tumblrPhoto(f File, raw []byte, ch chan File) photoPost {
p := photoPost{}
f.Err = json.Unmarshal(raw, &p)
for i, photo := range p.Photos {
us := photo.Alt_sizes[0].Url
u, err := url.Parse(us)
nf := f
if len(p.Photos) != 1 {
nf.Path += "-" + strconv.Itoa(i)
}
if err != nil {
nf.Err = err
} else {
nf.Url = *u
}
ch <- nf.SetLeaf()
}
return p
}
func tumblrText(bf File, raw []byte, t string) (File, interface{}) {
var err error
var p interface{}
var ext, content string
switch t {
case "answer", "audio", "chat":
//not implemented
case "link":
lp := linkPost{}
err = json.Unmarshal(raw, &lp)
p = lp
ext = "-link.txt"
content = lp.Url
case "quote":
lp := quotePost{}
err = json.Unmarshal(raw, &lp)
p = lp
ext = "-quote.txt"
content = lp.Text
case "text":
lp := textPost{}
err = json.Unmarshal(raw, &lp)
p = lp
ext = ".txt"
content = lp.Body
default:
bf.Err = errors.New("Unknown Tumblr post type")
}
bf.Body = []byte(content)
bf.Path += ext
if err == nil {
bf = bf.SetLeaf()
} else {
bf.Err = err
}
return bf, p
}
func checkResponse(rc *http.Response, resp interface{}) {
if !(rc.StatusCode < 300 && rc.StatusCode >= 200) {
println(rc.Request.URL.String())
check(errors.New("Request: " + rc.Status))
}
data, err := ioutil.ReadAll(rc.Body)
check(err)
var cr completeResponse
err = json.Unmarshal(data, &cr)
check(err)
err = json.Unmarshal(cr.Response, &resp)
if err != nil {
println("complete response: ", string(cr.Response))
}
check(err)
}
type completeResponse struct {
Meta meta
Response json.RawMessage
}
type meta struct {
Status int64
Msg string
}
type followingResponse struct {
Total_blogs int
Blogs []followingBlog
}
type followingBlog struct {
Name, Url string
Updated int
}
type blogResponse struct {
Blog blog
Posts []json.RawMessage
}
type blog struct {
Title string
Posts int64
Name string
Url string
Updated int64
Description string
Ask bool
Ask_anon bool
}
type basePost struct {
Blog_name string
Id int64
Post_url string
PostType string `json:"type"`
Timestamp int64
Date string
Format string
Reblog_key string
Tags []string
Bookmarklet bool
Mobile bool
Source_url string
Source_title string
Liked bool
State string
Total_Posts int64
}
type textPost struct {
basePost
Title, Body string
}
type photoPost struct {
basePost
Photos []photoObject
Caption string
Width, Height int64
}
type photoObject struct {
Caption string
Alt_sizes []altSize
}
type altSize struct {
Width, Height int64
Url string
}
type quotePost struct {
basePost
Text, Source string
}
type linkPost struct {
basePost
Title, Url, Description string
}
type chatPost struct {
basePost
Title, Body string
Dialogue []dialogue
}
type dialogue struct {
Name, Label, Phrase string
}
type audioPost struct {
basePost
Caption string
Player string
Plays int64
Album_art string
Artist string
Album string
Track_name string
Track_number int64
Year int64
}
type videoPost struct {
basePost
Caption string
Player []player
}
type player struct {
Width int64
Embed_code string
}
type answerPost struct {
basePost
Asking_name string
Asking_url string
Question string
Answer string
}