diff options
| author | Christian Cleberg <[email protected]> | 2026-07-15 17:26:22 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-15 17:26:22 -0500 |
| commit | b80180e77eebad8476aa66c8a5433764da672c62 (patch) | |
| tree | 3078b122345c45a4bd8a5477ae21ad63fa56f13d /user-group.go | |
| parent | b70d3dd588c8f9d6c9ed0ff111af06b76fa1f6ba (diff) | |
| download | devianter-b80180e77eebad8476aa66c8a5433764da672c62.tar.gz devianter-b80180e77eebad8476aa66c8a5433764da672c62.tar.bz2 devianter-b80180e77eebad8476aa66c8a5433764da672c62.zip | |
docs: document the exported API, and fix five bugs found writing it (#2)v0.3.2
* docs: translate Russian comments and document exported API
The package carried 35 Russian comments and no doc comments on its
exported identifiers, so pkg.go.dev rendered a bare list of signatures.
Translate the Russian to English and give every exported identifier a
doc comment in godoc form. Add doc.go with a package overview covering
the guest-session requirement, the Error-as-struct convention, and
CloudFront blocking.
The comments record what the signatures cannot: that UpdateCSRF must run
first and that its token expires, that Error is a struct and so is never
nil, that Thread is one comment rather than a thread, and that
GetComments' page parameter costs one request per page.
Comments only; the struct field realignment is gofmt's, from doc comments
splitting alignment groups.
Refs #1
* add CODEOWNERS
* fix: stop the library killing its caller, and flatten text correctly
Writing doc comments for the exported API surfaced behavior too alarming
to document and leave alone. A library should never terminate the process
that imports it.
Crashes:
- GetComments panicked on a comment with an empty body: the shape check
read m[0] and m[len(m)-1] with no length check.
- Group.Favourites and Group.Gallery indexed their variadic folderid
without checking len, so omitting it — which the signature invites —
panicked. Omitting it now means 0.
- AEmedia and PerformSearch called log.Fatalln on a bad argument rune,
terminating the caller. Both now return an error, which their
signatures already allowed for.
Draft.js flattening, in the same code both callers share:
- The block loop assigned rather than accumulated, so every block but the
last was dropped and a multi-paragraph body came back as its closing
line alone. Blocks are block-level elements, so join them with newlines.
- GetDeviation guarded on txt[1] == '{', the second character of a body
that opens with {"blocks". It never fired, so descriptions were handed
back as raw Draft.js JSON. It now shares flattenComment with
GetComments rather than keeping its own copy.
Both flattening fixes change output for existing callers.
Refs #1
Diffstat (limited to 'user-group.go')
| -rw-r--r-- | user-group.go | 83 |
1 files changed, 72 insertions, 11 deletions
diff --git a/user-group.go b/user-group.go index dba8a91..d7c9358 100644 --- a/user-group.go +++ b/user-group.go @@ -6,7 +6,14 @@ import ( "strings" ) -// структура группы или пользователя +// GRuser is a profile — a user's or a group's, as DeviantArt models both the +// same way. Owner.Group distinguishes them, and determines which of the +// ModuleData fields are populated: GroupAbout and GroupAdmins for a group, the +// embedded users for a person. +// +// The Page.Modules slice mirrors the site's own profile layout, so a caller +// looking for one piece of information has to search the slice for the module +// that carries it rather than reading a field directly. type GRuser struct { ErrorDescription string Owner struct { @@ -35,6 +42,12 @@ type GRuser struct { } `json:"pageExtraData"` } +// Gallery is a listing of deviations from a profile, returned by +// [Group.Gallery] and [Group.Favourites]. +// +// Where the deviations land depends on the call. Results is the flat listing; +// folder-scoped requests instead nest them inside the Modules slice, under +// Folder for a gallery or Folders for the folder index itself. type Gallery struct { Gruser struct { ID int `json:"gruserId"` @@ -42,7 +55,8 @@ type Gallery struct { Modules []struct { Name string ModuleData struct { - // группы + // Folders is the index of a profile's folders, each with a + // representative thumbnail. Folders struct { HasMore bool Results []struct { @@ -54,7 +68,7 @@ type Gallery struct { } } - // галерея + // Folder is the contents of one folder. Folder struct { HasMore bool Username string @@ -69,12 +83,23 @@ type Gallery struct { Results []Deviation } +// Group is the entry point for everything scoped to one profile. Despite the +// name it addresses users as well as groups, since DeviantArt treats the two +// alike. +// +// Name is the profile's username and must be set; the methods return an error +// otherwise. Construct it directly: +// +// g := devianter.Group{Name: "someuser"} +// profile, apiErr, err := g.Get() type Group struct { - Name string // обязательно заполнить + Name string // required Content Gallery } -// подходит как группа, так и пользователь +// Get retrieves the profile itself — its about page, statistics, and, for a +// group, its admins. It works for both users and groups; inspect +// Owner.Group on the result to tell which was returned. func (s Group) Get() (g GRuser, daError Error, err error) { if s.Name == "" { return g, daError, errors.New("missing Name field") @@ -84,10 +109,24 @@ func (s Group) Get() (g GRuser, daError Error, err error) { return } +// Favourites retrieves a page of the profile's favourites (its collections), 50 +// at a time, zero-based. +// +// Set all to gather every folder's contents into one listing. Otherwise pass a +// positive folderid to read a single folder, or 0 for the profile's default +// favourites listing. +// +// folderid is optional; omitting it is the same as passing 0. Only the first +// value is used. func (s Group) Favourites(page int, all bool, folderid ...int) (g Group, err Error) { var url strings.Builder - if fid := folderid[0]; fid > 0 || all { + fid := 0 + if len(folderid) > 0 { + fid = folderid[0] + } + + if fid > 0 || all { url.WriteString("dashared/gallection/contents") if all { url.WriteString("?all_folder=true") @@ -109,19 +148,31 @@ func (s Group) Favourites(page int, all bool, folderid ...int) (g Group, err Err return } -// гарелея пользователя или группы +// Gallery retrieves a page of the profile's gallery, 50 deviations at a time. +// Pass a positive folderid to read one folder, or 0 for the whole gallery. +// +// folderid is optional; omitting it is the same as passing 0. Only the first +// value is used. +// +// Note that page is interpreted differently by the two paths this takes: the +// whole-gallery listing is zero-based, while a folder listing is one-based. func (s Group) Gallery(page int, folderid ...int) (g Group, daError Error, err error) { if s.Name == "" { return g, daError, errors.New("missing Name field") } + fid := 0 + if len(folderid) > 0 { + fid = folderid[0] + } + var url strings.Builder - if folderid[0] > 0 { + if fid > 0 { page-- url.WriteString("dashared/gallection/contents?username=") url.WriteString(s.Name) url.WriteString("&folderid=") - url.WriteString(strconv.Itoa(folderid[0])) + url.WriteString(strconv.Itoa(fid)) url.WriteString("&offset=") url.WriteString(strconv.Itoa(page * 50)) url.WriteString("&type=gallery&") @@ -139,10 +190,14 @@ func (s Group) Gallery(page int, folderid ...int) (g Group, daError Error, err e return } +// GroupAbout is a group's about page: when it was founded and its description. type GroupAbout struct { FoundatedAt timeStamp `json:"foundationTs"` Description Text } + +// GroupAdmins lists a group's staff. TypeId encodes each member's role +// (founder, co-founder, contributor). type GroupAdmins struct { Results []struct { TypeId int @@ -152,10 +207,14 @@ type GroupAdmins struct { } } +// About is a person's profile information, all of it self-reported and any of +// it possibly empty. type About struct { Country, Website, WebsiteLabel, Gender string - RegDate int64 `json:"deviantFor"` - Description Text `json:"textContent"` + // RegDate is how long the account has existed, in seconds — an age, not a + // registration date, despite the name. + RegDate int64 `json:"deviantFor"` + Description Text `json:"textContent"` SocialLinks []struct { Value string @@ -165,6 +224,8 @@ type About struct { } } +// users is the person-specific half of a profile's module data, embedded into +// [GRuser] so its fields surface inline. type users struct { About About CoverDeviation struct { |
