diff options
Diffstat (limited to 'misc.go')
| -rw-r--r-- | misc.go | 73 |
1 files changed, 55 insertions, 18 deletions
@@ -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 { |
