krz/devianter

A DeviantArt guest API library for Go.

clone: git clone https://gitbay.org/krz/devianter.git

v0.3.4: user-group.go · raw

  1package devianter
  2
  3import (
  4	"errors"
  5	"strconv"
  6	"strings"
  7)
  8
  9// GRuser is a profile — a user's or a group's, as DeviantArt models both the
 10// same way. Owner.Group distinguishes them, and determines which of the
 11// ModuleData fields are populated: GroupAbout and GroupAdmins for a group, the
 12// embedded users for a person.
 13//
 14// The Page.Modules slice mirrors the site's own profile layout, so a caller
 15// looking for one piece of information has to search the slice for the module
 16// that carries it rather than reading a field directly.
 17type GRuser struct {
 18	ErrorDescription string
 19	Owner            struct {
 20		Group    bool `json:"isGroup"`
 21		Username string
 22	}
 23	Gruser struct {
 24		ID   int `json:"gruserId"`
 25		Page struct {
 26			Modules []struct {
 27				Name       string
 28				ModuleData struct {
 29					GroupAbout  GroupAbout
 30					GroupAdmins GroupAdmins
 31					users
 32				}
 33			}
 34		}
 35	}
 36	Extra struct {
 37		Tag   string `json:"gruserTagline"`
 38		Stats struct {
 39			Deviations, Watchers, Watching, Pageviews, CommentsMade, Favourites, Friends int
 40			FeedComments                                                                 int `json:"commentsReceivedProfile"`
 41		}
 42	} `json:"pageExtraData"`
 43}
 44
 45// Gallery is a listing of deviations from a profile, returned by
 46// [Group.Gallery] and [Group.Favourites].
 47//
 48// Where the deviations land depends on the call. Results is the flat listing;
 49// folder-scoped requests instead nest them inside the Modules slice, under
 50// Folder for a gallery or Folders for the folder index itself.
 51type Gallery struct {
 52	Gruser struct {
 53		ID   int `json:"gruserId"`
 54		Page struct {
 55			Modules []struct {
 56				Name       string
 57				ModuleData struct {
 58					// Folders is the index of a profile's folders, each with a
 59					// representative thumbnail.
 60					Folders struct {
 61						HasMore bool
 62						Results []struct {
 63							Deviations int `json:"totalItemCount"`
 64							FolderId   int
 65							Size       int
 66							Name       string
 67							Thumb      Deviation
 68						}
 69					}
 70
 71					// Folder is the contents of one folder.
 72					Folder struct {
 73						HasMore    bool
 74						Username   string
 75						Pages      int `json:"totalPageCount"`
 76						Deviations []Deviation
 77					} `json:"folderDeviations"`
 78				}
 79			}
 80		}
 81	}
 82	HasMore bool
 83	Results []Deviation
 84}
 85
 86// Group is the entry point for everything scoped to one profile. Despite the
 87// name it addresses users as well as groups, since DeviantArt treats the two
 88// alike.
 89//
 90// Name is the profile's username and must be set; the methods return an error
 91// otherwise. Construct it directly:
 92//
 93//	g := devianter.Group{Name: "someuser"}
 94//	profile, apiErr, err := g.Get()
 95type Group struct {
 96	Name    string // required
 97	Content Gallery
 98}
 99
100// Get retrieves the profile itself — its about page, statistics, and, for a
101// group, its admins. It works for both users and groups; inspect
102// Owner.Group on the result to tell which was returned.
103func (s Group) Get() (g GRuser, daError Error, err error) {
104	if s.Name == "" {
105		return g, daError, errors.New("missing Name field")
106	}
107	daError = ujson("dauserprofile/init/about?username="+s.Name, &g)
108
109	return
110}
111
112// Favourites retrieves a page of the profile's favourites (its collections), 50
113// at a time, zero-based.
114//
115// Set all to gather every folder's contents into one listing. Otherwise pass a
116// positive folderid to read a single folder, or 0 for the profile's default
117// favourites listing.
118//
119// folderid is optional; omitting it is the same as passing 0. Only the first
120// value is used.
121func (s Group) Favourites(page int, all bool, folderid ...int) (g Group, err Error) {
122	var url strings.Builder
123
124	fid := 0
125	if len(folderid) > 0 {
126		fid = folderid[0]
127	}
128
129	if fid > 0 || all {
130		url.WriteString("dashared/gallection/contents")
131		if all {
132			url.WriteString("?all_folder=true")
133		} else {
134			url.WriteString("?folderid=")
135			url.WriteString(strconv.Itoa(fid))
136		}
137		url.WriteString("&type=collection&")
138	} else {
139		url.WriteString("dauserprofile/init/favourites?deviations_")
140	}
141
142	url.WriteString("limit=50&username=")
143	url.WriteString(s.Name)
144	url.WriteString("&with_subfolders=true&offset=")
145	url.WriteString(strconv.Itoa(page * 50))
146
147	err = ujson(url.String(), &g.Content)
148	return
149}
150
151// Gallery retrieves a page of the profile's gallery, 50 deviations at a time.
152// Pass a positive folderid to read one folder, or 0 for the whole gallery.
153//
154// folderid is optional; omitting it is the same as passing 0. Only the first
155// value is used.
156//
157// Note that page is interpreted differently by the two paths this takes: the
158// whole-gallery listing is zero-based, while a folder listing is one-based.
159func (s Group) Gallery(page int, folderid ...int) (g Group, daError Error, err error) {
160	if s.Name == "" {
161		return g, daError, errors.New("missing Name field")
162	}
163
164	fid := 0
165	if len(folderid) > 0 {
166		fid = folderid[0]
167	}
168
169	var url strings.Builder
170	if fid > 0 {
171		page--
172		url.WriteString("dashared/gallection/contents?username=")
173		url.WriteString(s.Name)
174		url.WriteString("&folderid=")
175		url.WriteString(strconv.Itoa(fid))
176		url.WriteString("&offset=")
177		url.WriteString(strconv.Itoa(page * 50))
178		url.WriteString("&type=gallery&")
179	} else {
180		url.WriteString("dauserprofile/init/gallery?username=")
181		url.WriteString(s.Name)
182		url.WriteString("&page=")
183		url.WriteString(strconv.Itoa(page))
184		url.WriteString("&deviations_")
185	}
186	url.WriteString("limit=50")
187	url.WriteString("&with_subfolders=false")
188
189	daError = ujson(url.String(), &g.Content)
190	return
191}
192
193// GroupAbout is a group's about page: when it was founded and its description.
194type GroupAbout struct {
195	FoundatedAt timeStamp `json:"foundationTs"`
196	Description Text
197}
198
199// GroupAdmins lists a group's staff. TypeId encodes each member's role
200// (founder, co-founder, contributor).
201type GroupAdmins struct {
202	Results []struct {
203		TypeId int
204		User   struct {
205			Username string
206		}
207	}
208}
209
210// About is a person's profile information, all of it self-reported and any of
211// it possibly empty.
212type About struct {
213	Country, Website, WebsiteLabel, Gender string
214	// RegDate is how long the account has existed, in seconds — an age, not a
215	// registration date, despite the name.
216	RegDate     int64 `json:"deviantFor"`
217	Description Text  `json:"textContent"`
218
219	SocialLinks []struct {
220		Value string
221	}
222	Interests []struct {
223		Label, Value string
224	}
225}
226
227// users is the person-specific half of a profile's module data, embedded into
228// [GRuser] so its fields surface inline.
229type users struct {
230	About          About
231	CoverDeviation struct {
232		Deviation Deviation `json:"coverDeviation"`
233	}
234}