summaryrefslogtreecommitdiff
path: root/misc.go
diff options
context:
space:
mode:
Diffstat (limited to 'misc.go')
-rw-r--r--misc.go73
1 files changed, 55 insertions, 18 deletions
diff --git a/misc.go b/misc.go
index 6b34050..9778615 100644
--- a/misc.go
+++ b/misc.go
@@ -2,7 +2,6 @@ package devianter
import (
"errors"
- "log"
"math"
"net/url"
"strconv"
@@ -10,20 +9,28 @@ import (
)
/* AVATARS AND EMOJIS */
+// AEmedia fetches a user's avatar or a site emoji by name. t selects which:
+// 'a' for an avatar, 'e' for an emoji.
+//
+// It returns the image data itself, not a URL. DeviantArt does not say which
+// format a given name is stored in, so this tries .jpg, .png, and .gif in turn
+// and returns the first that exists — up to three requests per call, and three
+// for a name that does not exist.
+//
+// Passing any other t returns an error without making a request.
func AEmedia(name string, t rune) (string, error) {
if len(name) < 2 {
return "", errors.New("name must be specified")
}
- // список всех возможных расширений
var extensions = [3]string{
".jpg",
".png",
".gif",
}
- // надо
name = strings.ToLower(name)
- // построение ссылок. билдер потому что он быстрее обычного сложения строк.
+ // Avatars and emoji are sharded into directories by the leading characters of
+ // the name; avatars additionally normalise dashes to underscores first.
var b strings.Builder
switch t {
case 'a':
@@ -38,11 +45,11 @@ func AEmedia(name string, t rune) (string, error) {
b.WriteString(name[:1])
b.WriteString("/")
default:
- log.Fatalln("Invalid type.\n- 'a' -- avatar;\n- 'e' -- emoji.")
+ return "", errors.New("invalid type: want 'a' (avatar) or 'e' (emoji)")
}
b.WriteString(name)
- // проверка ссылки на доступность
+ // Probe each extension; the first 200 is the real format.
for x := 0; x < len(extensions); x++ {
req := request(b.String() + extensions[x])
if req.Status == 200 {
@@ -54,6 +61,9 @@ func AEmedia(name string, t rune) (string, error) {
}
/* DAILY DEVIATIONS */
+// DailyDeviations is the staff-curated front page selection. The picks are
+// grouped into Strips, each a titled row as the site presents it; Deviations is
+// the ungrouped listing.
type DailyDeviations struct {
HasMore bool
Strips []struct {
@@ -64,30 +74,54 @@ type DailyDeviations struct {
Deviations []Deviation
}
+// GetDailyDeviations retrieves a page of the daily deviation selection. Pages
+// are zero-based; check the returned HasMore before asking for the next.
func GetDailyDeviations(page int) (dd DailyDeviations, err Error) {
err = ujson("dabrowse/networkbar/rfy/deviations?page="+strconv.Itoa(page), &dd)
return
}
/* SEARCH */
+// Search is a page of search results. Read the matches from Results, which
+// [PerformSearch] populates whichever field the endpoint used.
+//
+// Total is DeviantArt's own estimate and is approximate. Pages is derived from
+// it and capped at 417, the depth a guest session can reach before the API stops
+// paginating.
type Search struct {
- Total int `json:"estTotal"`
- Pages int // only for 'a' and 'g' scope.
- HasMore bool
- Results []Deviation `json:"deviations"`
+ Total int `json:"estTotal"`
+ Pages int // only for 'a' and 'g' scope.
+ HasMore bool
+ Results []Deviation `json:"deviations"`
+ // ResultsGalleryTemp receives the results of gallery and collection searches,
+ // which return them under a different key. PerformSearch copies it into
+ // Results; callers should not need this field.
ResultsGalleryTemp []Deviation `json:"results"`
}
+// PerformSearch searches DeviantArt. scope selects what is being searched:
+//
+// 'a' — everything, by title and description
+// 't' — by tag
+// 'g' — within one user's or group's gallery
+// 'f' — within one user's or group's collections (favourites)
+//
+// Scopes 'g' and 'f' search a particular account, so they require the username
+// as the final argument and return an error without it. The other two ignore it.
+//
+// Pages are zero-based. A guest session cannot page beyond roughly 417 pages
+// deep regardless of how many results Total claims.
+//
+// Passing any other scope returns an error without making a request.
func PerformSearch(query string, page int, scope rune, user ...string) (ss Search, daError Error, err error) {
var buildurl strings.Builder
- // о5 построение ссылок.
switch scope {
- case 'a': // поиск артов по названию
+ case 'a':
buildurl.WriteString("dabrowse/search/all?q=")
- case 't': // поиск артов по тегам
+ case 't':
buildurl.WriteString("dabrowse/networkbar/tag/deviations?tag=")
- case 'g', 'f': // поиск артов пользователя или группы
+ case 'g', 'f':
if user == nil {
err = errors.New("missing username (last argument)")
return
@@ -103,13 +137,15 @@ func PerformSearch(query string, page int, scope rune, user ...string) (ss Searc
}
buildurl.WriteString("&order=most-recent&init=true&limit=50&q=")
default:
- log.Fatalln("Invalid type.\n- 'a' -- all;\n- 't' -- tag;\n- 'g' - gallery\n- 'f' - folders.")
+ err = errors.New("invalid scope: want 'a' (all), 't' (tag), 'g' (gallery) or 'f' (favourites)")
+ return
}
buildurl.WriteString(url.QueryEscape(query))
- if scope != 'g' { // если область поиска не равна поиску по группам, то активируется этот код
+ // Gallery search paginates by item offset rather than page number.
+ if scope != 'g' {
buildurl.WriteString("&page=")
- } else { // иначе вместо страницы будет оффсет и страница умножится на 50
+ } else {
buildurl.WriteString("&offset=")
page = 50 * page
}
@@ -121,7 +157,8 @@ func PerformSearch(query string, page int, scope rune, user ...string) (ss Searc
ss.Results = ss.ResultsGalleryTemp
}
- // расчёт, сколько всего страниц по запросу. без токена, 417 страниц - максимум
+ // Derive the page count from the result estimate, clamped to the 417 pages a
+ // guest session can actually reach.
totalfloat := int(math.Round(float64(ss.Total / 25)))
for x := 0; x < totalfloat; x++ {
if x <= 417 {