From b80180e77eebad8476aa66c8a5433764da672c62 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 15 Jul 2026 17:26:22 -0500 Subject: docs: document the exported API, and fix five bugs found writing it (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- deviantion.go | 70 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 26 deletions(-) (limited to 'deviantion.go') diff --git a/deviantion.go b/deviantion.go index 12c7a14..5b6df39 100644 --- a/deviantion.go +++ b/deviantion.go @@ -1,13 +1,14 @@ package devianter import ( - "encoding/json" "strconv" "strings" "time" ) -// хрень для парсинга времени публикации +// timeStamp is a time.Time that parses DeviantArt's publication timestamps, +// which are ISO 8601 with no colon in the zone offset and so are rejected by +// encoding/json's default time handling. type timeStamp struct { time.Time } @@ -20,7 +21,14 @@ func (t *timeStamp) UnmarshalJSON(b []byte) (err error) { return } -// самая главная структура для поста +// Deviation is a single artwork and its metadata: the central type of this +// package. Most endpoints return these, either alone or in slices. +// +// How much of it is populated depends on the endpoint. Search results and +// gallery listings return a shallow Deviation — enough for a thumbnail and a +// title — while [GetDeviation] fills in Extended, with the tags, original file +// details, and description. A zero-valued field usually means the endpoint did +// not send it rather than that the artwork lacks it. type Deviation struct { Title, Url, License string PublishedTime timeStamp @@ -55,17 +63,25 @@ type Deviation struct { TextContent Text } -// её выпердыши +// Media locates a deviation's image files. It is not a usable URL on its own: +// the pieces have to be assembled, and the result signed with a token. Pass it +// to [UrlFromMedia] rather than building the URL by hand. type Media struct { BaseUri string Name string `json:"prettyName"` Token []string - Types []struct { + // Types are the renditions available (thumbnails, preview, "fullview"), each + // with its own dimensions. + Types []struct { T string H, W int } } +// Text is a block of user-written text — a description, a comment, a group's +// about page. Markup holds either HTML or a JSON-encoded Draft.js document, +// distinguished by Type; the functions that return a Text generally extract the +// plain text into a neighbouring field, which is easier to use. type Text struct { Excerpt string Html struct { @@ -73,7 +89,13 @@ type Text struct { } } -// структура поста +// Post is a deviation together with its comment metadata, as returned by +// [GetDeviation]. IMG and Description are conveniences that GetDeviation derives +// from the Deviation, so callers need not assemble a URL or decode Draft.js +// markup themselves. +// +// Comments holds only a total and a cursor. To retrieve the comments, pass them +// to [GetComments] with type 1. type Post struct { Deviation Deviation Comments struct { @@ -90,7 +112,14 @@ type Post struct { IMG, Description string } -// преобразование урла в правильный +// UrlFromMedia assembles a usable, token-signed image URL from a [Media], along +// with the filename DeviantArt would serve it under. It selects the "fullview" +// rendition and returns empty strings if the media has none. +// +// An optional thumb argument scales the request down towards that many pixels +// per side, for fetching a smaller copy than the original. GIFs and very large +// images (beyond roughly 33 megapixels) are returned at their original URL +// without resizing, as DeviantArt's resizer refuses them. func UrlFromMedia(m Media, thumb ...int) (urlParsed, wellFormattedFilename string) { var url strings.Builder @@ -131,7 +160,13 @@ func UrlFromMedia(m Media, thumb ...int) (urlParsed, wellFormattedFilename strin return } -// для работы функции нужно ID поста и имя пользователя. +// GetDeviation retrieves a single deviation by its numeric ID and its author's +// username. Both are required: the endpoint will not resolve an ID alone. They +// appear in a deviation's page URL, which ends in a slug of the form +// title-by-author-123456789. +// +// The returned Post has its IMG and Description already derived, and its +// Deviation is fully populated, including Extended. func GetDeviation(id string, user string) (st Post, err Error) { err = ujson( "dadeviation/init?deviationid="+id+"&username="+user+"&type=art&include_session=false&expand=deviation.related&preload=true", @@ -140,24 +175,7 @@ func GetDeviation(id string, user string) (st Post, err Error) { st.IMG, _ = UrlFromMedia(st.Deviation.Media) - // базовая обработка описания - txt := st.Deviation.TextContent.Html.Markup - if len(txt) > 1 && txt[1] == '{' { - var description struct { - Blocks []struct { - Text string - } - } - - if err := json.Unmarshal([]byte(txt), &description); err != nil { - // Handle error appropriately - try(err) // or log/return the error - } - for _, a := range description.Blocks { - txt = a.Text - } - } - st.Description = txt + st.Description = flattenComment(st.Deviation.TextContent.Html.Markup) return } -- cgit v1.2.3