app/api.go
106 lines · 2775 bytes · executable
1package app
2
3import (
4 "encoding/json"
5 "math/rand"
6 "strings"
7
8 "github.com/krazywarez/devianter"
9)
10
11// API serves the JSON endpoints under /api, backed by the request its main
12// field points at.
13type API struct {
14 main *skunkyart
15}
16
17type info struct {
18 Version string `json:"version"`
19 Settings settingsParams `json:"settings"`
20}
21
22// Info responds with this instance's version and its proxy/NSFW/hide-ai settings.
23func (a API) Info() {
24 json, err := json.Marshal(info{
25 Version: a.main.Version,
26 Settings: settingsParams{
27 Nsfw: CFG.Nsfw,
28 Proxy: CFG.Proxy,
29 HideAI: CFG.HideAI,
30 Theme: CFG.Theme,
31 },
32 })
33 try(err)
34 _, _ = a.main.Writer.Write(json)
35}
36
37// Error responds with a JSON error body and the given HTTP status.
38func (a API) Error(description string, status int) {
39 a.main.Writer.Header().Del("Cache-Control")
40 a.main.Writer.WriteHeader(status)
41 var response strings.Builder
42 response.WriteString(`{"error":"`)
43 response.WriteString(description)
44 response.WriteString(`"}`)
45 wr(a.main.Writer, response.String())
46}
47
48func (a API) sendMedia(d *devianter.Deviation) {
49 mediaURL, name := devianter.UrlFromMedia(d.Media)
50 a.main.SetFilename(name)
51 if len(mediaURL) == 0 {
52 return
53 }
54
55 if !CFG.Proxy {
56 a.main.Writer.Header().Add("Location", mediaURL)
57 a.main.Writer.WriteHeader(302)
58 return
59 }
60
61 subdomain, path, token, ok := wixmpMedia(mediaURL)
62 if !ok {
63 a.Error("bad media url", 502)
64 return
65 }
66 a.main.Writer.Header().Del("Content-Type")
67 a.main.downloadAndSendMedia(subdomain, path, token)
68}
69
70// fetchDailyDeviations is devianter.GetDailyDeviations behind a variable so
71// tests can script it.
72var fetchDailyDeviations = devianter.GetDailyDeviations
73
74// Random responds with a random artwork's media, picked from the current daily
75// deviations. That page is one upstream call the API cache answers for its
76// TTL, where the previous random searches were up to three uncacheable calls
77// per hit and a cheap way for a bot to burn the instance's upstream budget.
78func (a API) Random() {
79 dd, daErr := fetchDailyDeviations(0)
80 if daErr.RAW != nil {
81 a.Error("deviantart returned an error", 502)
82 return
83 }
84
85 var pool []*devianter.Deviation
86 for i := range dd.Deviations {
87 if d := &dd.Deviations[i]; VisibleDeviation(d) {
88 pool = append(pool, d)
89 }
90 }
91 for s := range dd.Strips {
92 for i := range dd.Strips[s].Deviations {
93 if d := &dd.Strips[s].Deviations[i]; VisibleDeviation(d) {
94 pool = append(pool, d)
95 }
96 }
97 }
98 if len(pool) == 0 {
99 a.Error("no daily deviation this instance can show", 404)
100 return
101 }
102
103 // math/rand is deliberate: this picks a random artwork to show, which is not
104 // a security decision and does not need a cryptographic source.
105 a.sendMedia(pool[rand.Intn(len(pool))]) //nolint:gosec // G404
106}