Commit 8a00f7c55c

8a00f7c55cdbcddcd878c01a79f2db7dc3f080ef

parent: 0fdc45a668

Verified · cmc

cmc <hello@cleberg.net> · 2026-07-15 01:22 UTC

fix(api): harden random-art retry loop

Three bugs in Random():

- The retry loop was unbounded. Only the NSFW path incremented attempt,
  so a run of DeviantArt errors span forever, hammering the API and
  risking an egress-IP ban.
- string(rand.Intn(999)) converts a rune, not a number: string(65) is
  "A", not "65". Searches were querying garbage. Use strconv.Itoa.
- rand.Intn panics on 0, so an empty result set crashed the handler.
  Skip empty results.

The exhausted-retries error now fires when the loop ends rather than
falling through to index an empty slice.

Layout: unified · split

app/api.go +15 −7
@@ -3,6 +3,7 @@ package app
3import ( 3import (
4 "encoding/json" 4 "encoding/json"
5 "math/rand" 5 "math/rand"
6 "strconv"
6 "strings" 7 "strings"
7 8
8 "github.com/zerolabsco/devianter" 9 "github.com/zerolabsco/devianter"
@@ -58,25 +59,32 @@ func (a API) sendMedia(d *devianter.Deviation) {
58 59
59// TODO: сделать фильтры 60// TODO: сделать фильтры
60func (a API) Random() { 61func (a API) Random() {
61 for attempt := 1; ; { 62 // Bounded retries: the loop used to be unbounded, and the DeviantArt-error
62 if attempt > 3 { 63 // path never incremented attempt, so a single request could spin forever
63 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500) 64 // hammering the API (and get this instance's egress IP banned).
64 } 65 const maxAttempts = 3
65 66
66 s, daErr, err := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') 67 for attempt := 0; attempt < maxAttempts; attempt++ {
68 // strconv.Itoa, not string(): string(65) is "A", not "65".
69 s, daErr, err := devianter.PerformSearch(strconv.Itoa(rand.Intn(999)), rand.Intn(30), 'a')
67 try(err) 70 try(err)
68 if daErr.RAW != nil { 71 if daErr.RAW != nil {
69 continue 72 continue
70 } 73 }
71 74
72 deviation := &s.Results[rand.Intn(len(s.Results))] 75 // rand.Intn panics on 0, so an empty result set must be skipped.
76 if len(s.Results) == 0 {
77 continue
78 }
73 79
80 deviation := &s.Results[rand.Intn(len(s.Results))]
74 if deviation.NSFW && !CFG.Nsfw { 81 if deviation.NSFW && !CFG.Nsfw {
75 attempt++
76 continue 82 continue
77 } 83 }
78 84
79 a.sendMedia(deviation) 85 a.sendMedia(deviation)
80 return 86 return
81 } 87 }
88
89 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
82} 90}