├── .godir ├── .gitignore ├── Procfile ├── config_example ├── README.markdown ├── main_test.go └── main.go /.godir: -------------------------------------------------------------------------------- 1 | awwimage -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: awwimage -------------------------------------------------------------------------------- /config_example: -------------------------------------------------------------------------------- 1 | copy_and_paste_tumblr_consumer_key_here -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | This is an API for a random cute image from tumblr. The list is refreshed every hour. 4 | 5 | Demo (refresh to see a new image every time) 6 | ------- 7 | * http://awwimage.herokuapp.com/random/pug/preview 8 | * http://awwimage.herokuapp.com/random/corgi/preview 9 | * http://awwimage.herokuapp.com/random/shiba/preview 10 | * http://awwimage.herokuapp.com/random/cat/preview 11 | * http://awwimage.herokuapp.com/random/giraffe/preview 12 | 13 | Ramdom 14 | ------ 15 | * http://awwimage.herokuapp.com/random/pug 16 | * http://awwimage.herokuapp.com/random/corgi 17 | * http://awwimage.herokuapp.com/random/shiba 18 | * http://awwimage.herokuapp.com/random/cat 19 | * http://awwimage.herokuapp.com/random/giraffe 20 | 21 | Bomb 22 | ---- 23 | * http://awwimage.herokuapp.com/bomb/pug 24 | * http://awwimage.herokuapp.com/bomb/corgi 25 | * http://awwimage.herokuapp.com/bomb/shiba 26 | * http://awwimage.herokuapp.com/bomb/cat 27 | * http://awwimage.herokuapp.com/bomb/giraffe 28 | 29 | Direct link 30 | ----------- 31 | * http://awwimage.herokuapp.com/random/pug/url 32 | * http://awwimage.herokuapp.com/random/corgi/url 33 | * http://awwimage.herokuapp.com/random/shiba/url 34 | * http://awwimage.herokuapp.com/random/cat/url 35 | * http://awwimage.herokuapp.com/random/giraffe/url 36 | 37 | heroku deploy 38 | ------------- 39 | * heroku create / heroku git:remote -a repo_name 40 | * heroku config:set TUMBLR_KEY=copy_and_paste_tumblr_consumer_key_here BUILDPACK_URL=https://github.com/kr/heroku-buildpack-go.git 41 | 42 | appengine 43 | --------- 44 | ~/code/appengine/appcfg.py --oauth2 update . 45 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | var page1 = ` 13 | { 14 | "meta": { 15 | "status": 200, 16 | "msg": "OK" 17 | }, 18 | "response": [ 19 | { 20 | "blog_name": "marangio", 21 | "id": 111415925944, 22 | "post_url": "http://marangio.tumblr.com/post/111415925944/lbungeejumping", 23 | "slug": "lbungeejumping", 24 | "type": "photo", 25 | "date": "2015-02-18 23:44:08 GMT", 26 | "timestamp": 1424303048, 27 | "state": "published", 28 | "format": "html", 29 | "reblog_key": "J558MRGe", 30 | "tags": [ 31 | "bungeejumping", 32 | "jump", 33 | "mouse", 34 | "giraffe", 35 | "illustrator", 36 | "illustration", 37 | "ilustração", 38 | "ilustración", 39 | "abbildung", 40 | "graphic", 41 | "graphicart", 42 | "graphicillustration", 43 | "graphicline", 44 | "graphicdraw", 45 | "graphicdesign", 46 | "art", 47 | "draw", 48 | "line" 49 | ], 50 | "short_url": "http://tmblr.co/ZVKBKm1dmw1ou", 51 | "highlighted": [ 52 | ], 53 | "note_count": 0, 54 | "caption": "
LBUNGEEJUMPING
", 55 | "image_permalink": "http://marangio.tumblr.com/image/111415925944", 56 | "photos": [ 57 | { 58 | "caption": "", 59 | "alt_sizes": [ 60 | { 61 | "width": 1280, 62 | "height": 1280, 63 | "url": "http://41.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_1280.jpg" 64 | }, 65 | { 66 | "width": 500, 67 | "height": 500, 68 | "url": "http://36.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_500.jpg" 69 | }, 70 | { 71 | "width": 400, 72 | "height": 400, 73 | "url": "http://36.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_400.jpg" 74 | }, 75 | { 76 | "width": 250, 77 | "height": 250, 78 | "url": "http://40.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_250.jpg" 79 | }, 80 | { 81 | "width": 100, 82 | "height": 100, 83 | "url": "http://40.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_100.jpg" 84 | }, 85 | { 86 | "width": 75, 87 | "height": 75, 88 | "url": "http://40.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_75sq.jpg" 89 | } 90 | ], 91 | "original_size": { 92 | "width": 1280, 93 | "height": 1280, 94 | "url": "http://41.media.tumblr.com/c8dcfcb11f07801f41db2d2ff46b13ba/tumblr_njzr9kqbUI1u0ep4qo1_1280.jpg" 95 | } 96 | } 97 | ] 98 | }, 99 | { 100 | "blog_name": "piecomic", 101 | "id": 111409833242, 102 | "post_url": "http://piecomic.tumblr.com/post/111409833242", 103 | "slug": "", 104 | "type": "photo", 105 | "date": "2015-02-18 22:30:13 GMT", 106 | "timestamp": 1424298613, 107 | "state": "published", 108 | "format": "html", 109 | "reblog_key": "bcMPdZhi", 110 | "tags": [ 111 | "cartoon", 112 | "lol", 113 | "animals", 114 | "giraffe" 115 | ], 116 | "short_url": "http://tmblr.co/ZU7Znx1dmYoKQ", 117 | "highlighted": [ 118 | ], 119 | "note_count": 165, 120 | "source_url": "http://www.piecomic.com", 121 | "source_title": "piecomic.com", 122 | "caption": "", 123 | "link_url": "http://www.piecomic.com", 124 | "image_permalink": "http://piecomic.tumblr.com/image/111409833242", 125 | "photos": [ 126 | { 127 | "caption": "", 128 | "alt_sizes": [ 129 | { 130 | "width": 500, 131 | "height": 552, 132 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_500.jpg" 133 | }, 134 | { 135 | "width": 400, 136 | "height": 442, 137 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_400.jpg" 138 | }, 139 | { 140 | "width": 250, 141 | "height": 276, 142 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_250.jpg" 143 | }, 144 | { 145 | "width": 100, 146 | "height": 110, 147 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_100.jpg" 148 | }, 149 | { 150 | "width": 75, 151 | "height": 75, 152 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_75sq.jpg" 153 | } 154 | ], 155 | "original_size": { 156 | "width": 500, 157 | "height": 552, 158 | "url": "http://40.media.tumblr.com/dfbaff69a8390c1bc3c048a2c41b7ab0/tumblr_njznudWb0D1qhnegdo1_500.jpg" 159 | } 160 | } 161 | ] 162 | } 163 | ] 164 | } 165 | ` 166 | 167 | type testFetcher struct{} 168 | 169 | func (fetcher testFetcher) Fetch(url string) ([]byte, error) { 170 | return []byte(page1), nil 171 | } 172 | 173 | func reset_image_mapping() { 174 | image_mapping = make(map[string][]string) 175 | } 176 | 177 | type ResponseJson struct { 178 | Url string 179 | } 180 | 181 | func TestPopulateImageMapping(t *testing.T) { 182 | var kind = "pug" 183 | var image_limit_was = image_limit 184 | image_limit = 20 185 | fetcher := testFetcher{} 186 | populate(kind, &fetcher) 187 | if _, ok := image_mapping[kind]; !ok { 188 | t.Error("populate failed") 189 | } 190 | if len(image_mapping[kind]) != 20 { 191 | t.Error("populate count failed") 192 | } 193 | log.Println(image_limit_was) 194 | image_limit = image_limit_was 195 | reset_image_mapping() 196 | } 197 | 198 | func TestRandom(t *testing.T) { 199 | var url = "http://example.com/1.jpg" 200 | image_mapping["pug"] = []string{url} 201 | ts := httptest.NewServer(getHttpHandler()) 202 | defer ts.Close() 203 | res, err := http.Get(ts.URL + "/random/pug") 204 | if err != nil { 205 | t.Error("random failed", err) 206 | } 207 | if res.StatusCode != 200 { 208 | t.Error("random failed", res.StatusCode) 209 | } 210 | body_bytes, err := ioutil.ReadAll(res.Body) 211 | res.Body.Close() 212 | response_json := ResponseJson{} 213 | json.Unmarshal(body_bytes, &response_json) 214 | if response_json.Url != url { 215 | t.Error("random failed", response_json.Url) 216 | } 217 | reset_image_mapping() 218 | } 219 | 220 | func TestRandomPreview(t *testing.T) { 221 | var url = "http://example.com/1.jpg" 222 | image_mapping["pug"] = []string{url} 223 | ts := httptest.NewServer(getHttpHandler()) 224 | defer ts.Close() 225 | res, _ := http.Get(ts.URL + "/random/pug/preview") 226 | body_bytes, _ := ioutil.ReadAll(res.Body) 227 | res.Body.Close() 228 | if string(body_bytes) != "
" {
229 | t.Error("random preview failed", string(body_bytes))
230 | }
231 | reset_image_mapping()
232 | }
233 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "log"
9 | "math/rand"
10 | "net/http"
11 | "os"
12 | "strconv"
13 | "strings"
14 | "time"
15 |
16 | "github.com/bmizerany/pat"
17 | )
18 |
19 | var image_limit = 300
20 | var last_refreshed_at time.Time
21 | var image_mapping = make(map[string][]string)
22 | var api_key string
23 | var kinds = []string{"pug", "corgi", "shiba", "cat", "giraffe"}
24 |
25 | type PhotoProperty struct {
26 | Url string
27 | }
28 |
29 | type Photo struct {
30 | OriginalPhoto PhotoProperty `json:"original_size"`
31 | }
32 |
33 | // TODO Timestamp sometime is string
34 | type Blog struct {
35 | Timestamp int
36 | Photos []Photo
37 | }
38 |
39 | type TaggedApiResponse struct {
40 | Blogs []Blog `json:"response"`
41 | }
42 |
43 | type FetcherInterface interface {
44 | Fetch(url string) ([]byte, error)
45 | }
46 |
47 | type tumblrFetcher struct{}
48 |
49 | func instruction(res http.ResponseWriter, req *http.Request) {
50 | fmt.Fprint(res, get_json_string(endpoints()))
51 | }
52 |
53 | func count(res http.ResponseWriter, req *http.Request) {
54 | kind := req.URL.Query().Get(":kind")
55 | fmt.Fprint(res, get_json_string(&map[string]int{"count": len(image_mapping[kind])}))
56 | }
57 |
58 | func random(res http.ResponseWriter, req *http.Request) {
59 | var err error
60 | refresh_every_hour()
61 | kind := req.URL.Query().Get(":kind")
62 | err = check_kind(kind)
63 | if err != nil {
64 | res.WriteHeader(400)
65 | fmt.Fprint(res, get_json_string(&map[string]string{"error": err.Error()}))
66 | return
67 | }
68 | action := req.URL.Query().Get(":action")
69 | if len(image_mapping[kind]) == 0 {
70 | err = wait_for_populating(kind)
71 | if err != nil {
72 | res.WriteHeader(500)
73 | fmt.Fprint(res, get_json_string(&map[string]string{"error": err.Error()}))
74 | return
75 | }
76 | }
77 | index := rand.Intn(len(image_mapping[kind]))
78 | url := image_mapping[kind][index]
79 | if action == "preview" {
80 | fmt.Fprint(res, "