krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

8a00f7c55cdbcddcd878c01a79f2db7dc3f080ef

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-07-15T01:22:14Z

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.
 app/api.go | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/app/api.go b/app/api.go
index cd574b8..0398d3c 100755
--- a/app/api.go
+++ b/app/api.go
@@ -3,6 +3,7 @@ package app
 import (
 	"encoding/json"
 	"math/rand"
+	"strconv"
 	"strings"
 
 	"github.com/zerolabsco/devianter"
@@ -58,25 +59,32 @@ func (a API) sendMedia(d *devianter.Deviation) {
 
 // TODO: сделать фильтры
 func (a API) Random() {
-	for attempt := 1; ; {
-		if attempt > 3 {
-			a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
-		}
+	// Bounded retries: the loop used to be unbounded, and the DeviantArt-error
+	// path never incremented attempt, so a single request could spin forever
+	// hammering the API (and get this instance's egress IP banned).
+	const maxAttempts = 3
 
-		s, daErr, err := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a')
+	for attempt := 0; attempt < maxAttempts; attempt++ {
+		// strconv.Itoa, not string(): string(65) is "A", not "65".
+		s, daErr, err := devianter.PerformSearch(strconv.Itoa(rand.Intn(999)), rand.Intn(30), 'a')
 		try(err)
 		if daErr.RAW != nil {
 			continue
 		}
 
-		deviation := &s.Results[rand.Intn(len(s.Results))]
+		// rand.Intn panics on 0, so an empty result set must be skipped.
+		if len(s.Results) == 0 {
+			continue
+		}
 
+		deviation := &s.Results[rand.Intn(len(s.Results))]
 		if deviation.NSFW && !CFG.Nsfw {
-			attempt++
 			continue
 		}
 
 		a.sendMedia(deviation)
 		return
 	}
+
+	a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
 }