Documentation
¶
Overview ¶
Package toniecloud is a Go client for the TonieCloud REST API (https://api.tonie.cloud/v2). It is a faithful, feature-complete port of the Python library github.com/alexhartm/tonie_api, extended with a real token-caching auth layer, structured errors, single-tonie reads and chapter-level editing.
It is NOT associated with Boxine / tonies.de in any way.
Index ¶
- Constants
- Variables
- func IsNotFound(err error) bool
- func IsUnauthorized(err error) bool
- func NormalizeUser(u string) string
- type APIError
- type Authenticator
- type Chapter
- type Client
- func (c *Client) AddChapter(ctx context.Context, t CreativeTonie, fileID, title string) error
- func (c *Client) ChapterDownloadURL(ctx context.Context, t CreativeTonie, ch Chapter) (string, error)
- func (c *Client) CreateFileUpload(ctx context.Context) (FileUploadRequest, error)
- func (c *Client) CreativeTonie(ctx context.Context, householdID, tonieID string) (CreativeTonie, error)
- func (c *Client) CreativeTonies(ctx context.Context) ([]CreativeTonie, error)
- func (c *Client) CreativeToniesByHousehold(ctx context.Context, householdID string) ([]CreativeTonie, error)
- func (c *Client) DownloadChapter(ctx context.Context, t CreativeTonie, ch Chapter, destPath string) error
- func (c *Client) GetConfig(ctx context.Context) (Config, error)
- func (c *Client) Households(ctx context.Context) ([]Household, error)
- func (c *Client) Me(ctx context.Context) (User, error)
- func (c *Client) Patch(ctx context.Context, t CreativeTonie, fields map[string]any) (CreativeTonie, error)
- func (c *Client) Raw(ctx context.Context, method, path string, body any) (json.RawMessage, error)
- func (c *Client) RenameTonie(ctx context.Context, t CreativeTonie, name string) (CreativeTonie, error)
- func (c *Client) SetChapters(ctx context.Context, t CreativeTonie, chapters []Chapter) (CreativeTonie, error)
- func (c *Client) UploadFile(ctx context.Context, t CreativeTonie, path, title string) (UploadResult, error)
- func (c *Client) UploadReader(ctx context.Context, t CreativeTonie, r io.Reader, title, contentType string) (UploadResult, error)
- func (c *Client) WaitForTranscoding(ctx context.Context, householdID, tonieID string, ...) (CreativeTonie, error)
- type Config
- type CreativeTonie
- type FileUploadRequest
- type Household
- type Request
- type TokenCache
- type TokenEntry
- type UploadResult
- type User
Constants ¶
const ( DefaultTokenURL = "https://login.tonies.com/auth/realms/tonies/protocol/openid-connect/token" DefaultClientID = "my-tonies" )
Default OpenID Connect endpoint and client used by the official my tonies app.
const DefaultAPIURL = "https://api.tonie.cloud/v2"
DefaultAPIURL is the TonieCloud REST base URL.
Variables ¶
ErrChapterDownloadUnavailable is returned when a chapter's audio cannot be downloaded (no available endpoint, or a content-token chapter).
var ErrNotAuthenticated = errors.New("not authenticated: set TONIE_USERNAME and TONIE_PASSWORD (or run `tonys auth login`)")
ErrNotAuthenticated is returned when no credentials and no usable cached token are available.
var UserAgent = "tonys-cli"
UserAgent is sent with every request; overridable by the CLI.
Functions ¶
func IsNotFound ¶
IsNotFound reports whether err is an APIError with a 404 status.
func IsUnauthorized ¶
IsUnauthorized reports whether err is an APIError with a 401/403 status, which usually means the token expired or the credentials are wrong.
func NormalizeUser ¶
NormalizeUser returns the cache-key form of a username (lowercased, trimmed). Exposed so the CLI computes identical keys.
Types ¶
type APIError ¶
type APIError struct {
Method string
URL string
Status int
// Detail is the server's human-readable message when one could be parsed.
Detail string
// Body is the raw response body (truncated) for debugging.
Body string
}
APIError represents a non-2xx HTTP response from TonieCloud (or S3). Unlike the upstream Python library, which silently swallows failures and returns an empty dict, we surface the status, endpoint and server-provided detail so callers (and agents) can react.
type Authenticator ¶
type Authenticator struct {
TokenURL string
ClientID string
Username string
Password string
HTTP *http.Client
Cache *TokenCache // optional; nil disables persistence
// contains filtered or unexported fields
}
Authenticator turns a username/password into a bearer token, transparently caching it and refreshing via the OpenID refresh_token grant when possible so that repeated CLI invocations (e.g. from a bot) do not hammer the SSO server.
func (*Authenticator) Login ¶
func (a *Authenticator) Login(ctx context.Context) (TokenEntry, error)
Login forces a fresh password grant (ignoring any cached access token) and persists the result. Used by `tonys auth login`.
type Chapter ¶
type Chapter struct {
ID string `json:"id"`
Title string `json:"title"`
// File is the file identifier. For new chapters this is the UUID returned
// by POST /file. For existing chapters it is an opaque blob; for content
// shipped by tonies it starts with "ContentToken:".
File string `json:"file"`
Seconds float64 `json:"seconds"`
Transcoding bool `json:"transcoding"`
}
Chapter is a single audio track on a creative tonie.
func (Chapter) IsContentToken ¶
IsContentToken reports whether the chapter is tonies-published content rather than a user upload. Such chapters cannot be re-uploaded, only reordered or removed.
type Client ¶
type Client struct {
APIURL string
HTTP *http.Client
Auth *Authenticator
}
Client talks to the TonieCloud REST API. Construct it with New.
func New ¶
func New(apiURL string, auth *Authenticator, httpClient *http.Client) *Client
New builds a Client. A nil httpClient uses http.DefaultClient.
func (*Client) AddChapter ¶
AddChapter appends an already-uploaded file as a new chapter (POST /households/{hid}/creativetonies/{id}/chapters).
func (*Client) ChapterDownloadURL ¶
func (c *Client) ChapterDownloadURL(ctx context.Context, t CreativeTonie, ch Chapter) (string, error)
ChapterDownloadURL resolves a temporary URL to a chapter's audio, if the backend exposes one. The endpoint used here is discovered empirically; if the account/plan does not expose it, ErrChapterDownloadUnavailable is returned.
func (*Client) CreateFileUpload ¶
func (c *Client) CreateFileUpload(ctx context.Context) (FileUploadRequest, error)
CreateFileUpload requests a presigned S3 upload slot (POST /file).
func (*Client) CreativeTonie ¶
func (c *Client) CreativeTonie(ctx context.Context, householdID, tonieID string) (CreativeTonie, error)
CreativeTonie fetches a single creative tonie (GET /households/{hid}/creativetonies/{id}).
func (*Client) CreativeTonies ¶
func (c *Client) CreativeTonies(ctx context.Context) ([]CreativeTonie, error)
CreativeTonies lists every creative tonie across all of the user's households.
func (*Client) CreativeToniesByHousehold ¶
func (c *Client) CreativeToniesByHousehold(ctx context.Context, householdID string) ([]CreativeTonie, error)
CreativeToniesByHousehold lists all creative tonies in one household.
func (*Client) DownloadChapter ¶
func (c *Client) DownloadChapter(ctx context.Context, t CreativeTonie, ch Chapter, destPath string) error
DownloadChapter writes a chapter's audio to destPath.
func (*Client) Households ¶
Households returns all households of the logged-in user (GET /households).
func (*Client) Patch ¶
func (c *Client) Patch(ctx context.Context, t CreativeTonie, fields map[string]any) (CreativeTonie, error)
Patch applies an arbitrary set of fields to a tonie via PATCH.
func (*Client) Raw ¶
Raw performs an arbitrary authenticated API call and returns the raw JSON response. It is the escape hatch that makes every endpoint reachable, even ones without a dedicated method. method is GET/POST/PATCH/etc.; path is relative to the API base (leading slash optional); body is optional JSON.
func (*Client) RenameTonie ¶
func (c *Client) RenameTonie(ctx context.Context, t CreativeTonie, name string) (CreativeTonie, error)
RenameTonie changes a tonie's display name via PATCH.
func (*Client) SetChapters ¶
func (c *Client) SetChapters(ctx context.Context, t CreativeTonie, chapters []Chapter) (CreativeTonie, error)
SetChapters replaces the chapter list (and order) of a tonie via PATCH. This powers sorting, removing and renaming chapters and clearing the tonie.
func (*Client) UploadFile ¶
func (c *Client) UploadFile(ctx context.Context, t CreativeTonie, path, title string) (UploadResult, error)
UploadFile uploads a local audio file and appends it as a new chapter on the tonie. If title is empty the file's base name (without extension) is used. This is the three-step flow: POST /file → presigned S3 upload → POST chapters.
func (*Client) UploadReader ¶
func (c *Client) UploadReader(ctx context.Context, t CreativeTonie, r io.Reader, title, contentType string) (UploadResult, error)
UploadReader is like UploadFile but reads content from r, so callers can pipe data (e.g. from stdin). contentType may be empty.
func (*Client) WaitForTranscoding ¶
func (c *Client) WaitForTranscoding(ctx context.Context, householdID, tonieID string, interval, timeout time.Duration) (CreativeTonie, error)
WaitForTranscoding polls the tonie until transcoding finishes, a transcoding error is reported, or the timeout/context expires, returning the final tonie state. A zero interval defaults to 2s; a non-positive timeout means "no overall deadline" (still cancellable via ctx). This prevents an unattended `--wait` from spinning forever when the backend stalls or fails a transcode.
type Config ¶
type Config struct {
Locales []string `json:"locales"`
UnicodeLocales []string `json:"unicodeLocales"`
MaxChapters int `json:"maxChapters"`
MaxSeconds int `json:"maxSeconds"`
MaxBytes int64 `json:"maxBytes"`
Accepts []string `json:"accepts"`
StageWarning bool `json:"stageWarning"`
PaypalClientID string `json:"paypalClientId"`
SSOEnabled bool `json:"ssoEnabled"`
}
Config is the backend configuration and upload limits (GET /config).
type CreativeTonie ¶
type CreativeTonie struct {
ID string `json:"id"`
HouseholdID string `json:"householdId"`
Name string `json:"name"`
ImageURL string `json:"imageUrl"`
SecondsRemaining float64 `json:"secondsRemaining"`
SecondsPresent float64 `json:"secondsPresent"`
ChaptersRemaining int `json:"chaptersRemaining"`
ChaptersPresent int `json:"chaptersPresent"`
Transcoding bool `json:"transcoding"`
Live bool `json:"live"`
Private bool `json:"private"`
LastUpdate *time.Time `json:"lastUpdate"`
TranscodingErrors []any `json:"transcodingErrors,omitempty"`
Chapters []Chapter `json:"chapters"`
}
CreativeTonie is a re-recordable tonie figurine (GET /households/{id}/creativetonies).
type FileUploadRequest ¶
FileUploadRequest is returned by POST /file and contains a presigned S3 form plus the fileId to reference once the upload completes.
type Household ¶
type Household struct {
ID string `json:"id"`
Name string `json:"name"`
OwnerName string `json:"ownerName"`
Access string `json:"access"`
CanLeave bool `json:"canLeave"`
Image string `json:"image,omitempty"`
// ForeignCreativeTonieContent indicates whether content from other
// households may be played on this household's tonies.
ForeignCreativeTonieContent bool `json:"foreignCreativeTonieContent,omitempty"`
}
Household groups creative tonies and is the unit of access control (GET /households).
type TokenCache ¶
type TokenCache struct {
Path string
// contains filtered or unexported fields
}
TokenCache is a tiny JSON file ({"entries": {key: entry}}) holding bearer tokens, written with 0600 permissions. It is keyed by the normalized (lowercased) username so multiple accounts can coexist.
func NewTokenCache ¶
func NewTokenCache(path string) *TokenCache
NewTokenCache returns a cache backed by path. A blank path yields an in-memory cache that is never persisted.
func (*TokenCache) Delete ¶
func (c *TokenCache) Delete(key string)
Delete removes an entry (all entries if key is empty) and persists.
func (*TokenCache) Entries ¶
func (c *TokenCache) Entries() map[string]TokenEntry
Entries returns a copy of all cached entries.
func (*TokenCache) Get ¶
func (c *TokenCache) Get(key string) (TokenEntry, bool)
Get returns the entry for key, if present.
func (*TokenCache) Put ¶
func (c *TokenCache) Put(key string, e TokenEntry)
Put stores an entry and persists the cache.
func (*TokenCache) SoleKey ¶
func (c *TokenCache) SoleKey() (string, bool)
SoleKey returns the only key in the cache, if exactly one exists. This lets the CLI reuse a cached token when no username is supplied.
type TokenEntry ¶
type TokenEntry struct {
Username string `json:"username"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
RefreshExpiresAt time.Time `json:"refresh_expires_at,omitempty"`
}
TokenEntry is one account's cached credentials.
type UploadResult ¶
UploadResult describes a completed upload + chapter creation.
type User ¶
type User struct {
UUID string `json:"uuid"`
Email string `json:"email"`
// Extra holds every other field returned by /me verbatim.
Extra map[string]any `json:"-"`
}
User is the account behind a set of credentials (GET /me).
The TonieCloud API returns many more fields than the upstream Python library models; we keep the documented essentials as typed fields and preserve everything else (firstName, locale, country, region, ...) in Extra so no data is lost and agents can read arbitrary attributes.
func (User) MarshalJSON ¶
MarshalJSON re-flattens Extra back alongside the typed fields.
func (*User) UnmarshalJSON ¶
UnmarshalJSON keeps the typed uuid/email fields while preserving every other attribute the API returns in Extra.