diff --git a/go/tumblrandom/tumblrandom.go b/go/tumblrandom/tumblrandom.go index f6ec2ae4e6..3811448d62 100644 --- a/go/tumblrandom/tumblrandom.go +++ b/go/tumblrandom/tumblrandom.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/sha256" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -19,6 +20,7 @@ import ( "os/signal" "path/filepath" "strings" + "sync" "golang.org/x/oauth2" ) @@ -202,6 +204,11 @@ type likesResponse struct { } `json:"response"` } +var ( + refreshBuf map[string][]Post + refreshBufMu sync.Mutex +) + func (a *app) refreshLikes(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() u, ok := user(ctx) @@ -214,39 +221,88 @@ func (a *app) refreshLikes(rw http.ResponseWriter, r *http.Request) { var likes []Post const urlPrefix = "https://api.tumblr.com" - urlSuffix := "/v2/user/likes" - for { - req, err := http.NewRequestWithContext(ctx, "GET", urlPrefix+urlSuffix, nil) - if err != nil { - http.Error(rw, fmt.Sprintf("formulating likes request: %v", err), http.StatusInternalServerError) + refreshSess := r.FormValue("refresh_session") + if refreshSess == "" { + refreshSessBytes := make([]byte, 32) + if _, err := rand.Read(refreshSessBytes); err != nil { + http.Error(rw, fmt.Sprintf("generating refresh session token: %v", err), http.StatusInternalServerError) return } - fmt.Println(req.URL) - resp, err := client.Do(req) - if err != nil { - http.Error(rw, fmt.Sprintf("performing likes request: %v", err), http.StatusInternalServerError) + refreshSess = base64.RawURLEncoding.EncodeToString(refreshSessBytes) + refreshBufMu.Lock() + _, ok := refreshBuf[refreshSess] + refreshBufMu.Unlock() + if ok { + http.Error(rw, fmt.Sprintf("randomness isn't random enough"), http.StatusInternalServerError) return } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - errBody, _ := io.ReadAll(resp.Body) - http.Error(rw, fmt.Sprintf("likes request status %v\n\n%v", resp.StatusCode, string(errBody)), http.StatusInternalServerError) + } else { + refreshBufMu.Lock() + likes2, ok := refreshBuf[refreshSess] + refreshBufMu.Unlock() + if !ok { + http.Error(rw, fmt.Sprintf("refresh session %q is missing", refreshSess), http.StatusBadRequest) return } - - var r likesResponse - if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { - http.Error(rw, fmt.Sprintf("decoding likes body as json: %v", err), http.StatusInternalServerError) - return - } - likes = append(likes, r.Response.LikedPosts...) - - urlSuffix = r.Response.Links.Next.Href - if urlSuffix == "" { - break - } + likes = likes2 } + urlSuffix := r.FormValue("url_suffix") + if urlSuffix == "" { + urlSuffix = "/v2/user/likes" + } + + req, err := http.NewRequestWithContext(ctx, "GET", urlPrefix+urlSuffix, nil) + if err != nil { + http.Error(rw, fmt.Sprintf("formulating likes request: %v", err), http.StatusInternalServerError) + return + } + fmt.Println(req.URL) + resp, err := client.Do(req) + if err != nil { + http.Error(rw, fmt.Sprintf("performing likes request: %v", err), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + errBody, _ := io.ReadAll(resp.Body) + http.Error(rw, fmt.Sprintf("likes request status %v\n\n%v", resp.StatusCode, string(errBody)), http.StatusInternalServerError) + return + } + + var lr likesResponse + if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil { + http.Error(rw, fmt.Sprintf("decoding likes body as json: %v", err), http.StatusInternalServerError) + return + } + likes = append(likes, lr.Response.LikedPosts...) + + urlSuffix = lr.Response.Links.Next.Href + if urlSuffix != "" { + fmt.Fprintf(rw, ` + +
+ + + + + + + +`, urlSuffix, refreshSess) + return + } + refreshBufMu.Lock() + delete(refreshBuf, refreshSess) + refreshBufMu.Unlock() + u.Likes = likes if err := u.save(); err != nil { http.Error(rw, fmt.Sprintf("saving likes: %v", err), http.StatusInternalServerError)