// Package wowdb : client officiel de l'API de WoWDB des Défias, version 1.0.0. // // Documentation : https://docs.wowdb.assemblee-defias.fr // Un seul fichier, bibliothèque standard seulement (Go 1.18+). Release 58 du site (01/10/2026). // Licence : MIT. Données du jeu © Blizzard Entertainment. // // client := wowdb.New("bot-de-ma-guilde/1.0 (https://ma-guilde.example)") // lame, err := client.Retrieve(ctx, "classic", "items", 5191) // err = client.Iterate(ctx, "/classic/items", url.Values{"type": {"dagger"}, "rogue": {"true"}}, // func(dague map[string]any) error { fmt.Println(dague["name"]); return nil }) package wowdb import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strconv" "strings" "time" ) // Version du client. const Version = "1.0.0" // BaseURL : adresse de l'API en production. const BaseURL = "https://api.wowdb.assemblee-defias.fr/v1" // Error : erreur de l'API (corps { error: { type, code, message, param, doc_url } }) ou du réseau. type Error struct { Status int `json:"-"` Type string `json:"type"` Code string `json:"code"` Message string `json:"message"` Param *string `json:"param"` DocURL string `json:"doc_url"` RequestID string `json:"-"` } func (e *Error) Error() string { return fmt.Sprintf("wowdb : %d %s : %s", e.Status, e.Code, e.Message) } // Page : une page d'une liste, paginée par curseur. type Page struct { Object string `json:"object"` Data []map[string]any `json:"data"` HasMore bool `json:"has_more"` NextCursor *string `json:"next_cursor"` URL string `json:"url"` TotalCount *int `json:"total_count,omitempty"` } // Client de l'API (lecture seule, sans clé). type Client struct { BaseURL string UserAgent string HTTP *http.Client MaxRetries int } // New : client de production ; userAgent nomme votre application et donne un moyen de vous joindre. func New(userAgent string) *Client { if userAgent == "" { userAgent = "wowdb.go/" + Version } return &Client{BaseURL: BaseURL, UserAgent: userAgent, HTTP: &http.Client{Timeout: 30 * time.Second}, MaxRetries: 2} } // Get : requête GET brute (chemin relatif à /v1), JSON décodé dans out. func (c *Client) Get(ctx context.Context, path string, params url.Values, out any) error { u := strings.TrimRight(c.BaseURL, "/") + "/" + strings.TrimLeft(path, "/") if len(params) > 0 { u += "?" + params.Encode() } for essai := 0; ; essai++ { req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return err } req.Header.Set("User-Agent", c.UserAgent) req.Header.Set("Accept", "application/json") res, err := c.HTTP.Do(req) if err != nil { return &Error{Type: "api_error", Code: "network_error", Message: "Requête impossible : " + err.Error()} } if res.StatusCode == http.StatusTooManyRequests && essai < c.MaxRetries { res.Body.Close() attente, _ := strconv.Atoi(res.Header.Get("Retry-After")) if attente < 1 { attente = 1 } select { case <-time.After(time.Duration(attente) * time.Second): continue case <-ctx.Done(): return ctx.Err() } } err = decoder(res, out) res.Body.Close() return err } } func decoder(res *http.Response, out any) error { if res.StatusCode >= 400 { var corps struct { Error Error `json:"error"` } _ = json.NewDecoder(res.Body).Decode(&corps) e := corps.Error e.Status = res.StatusCode e.RequestID = res.Header.Get("X-Request-Id") if e.Message == "" { e.Message = "Erreur HTTP " + strconv.Itoa(res.StatusCode) } return &e } return json.NewDecoder(res.Body).Decode(out) } // List : une page d'une liste. func (c *Client) List(ctx context.Context, path string, params url.Values) (*Page, error) { var p Page if err := c.Get(ctx, path, params, &p); err != nil { return nil, err } return &p, nil } // Iterate : tous les éléments d'une liste, page après page (pages de 100) ; fn peut arrêter le parcours en renvoyant une erreur. func (c *Client) Iterate(ctx context.Context, path string, params url.Values, fn func(map[string]any) error) error { q := url.Values{} for k, v := range params { q[k] = v } if q.Get("limit") == "" { q.Set("limit", "100") } for { page, err := c.List(ctx, path, q) if err != nil { return err } for _, element := range page.Data { if err := fn(element); err != nil { return err } } if page.NextCursor == nil { return nil } q.Set("cursor", *page.NextCursor) } } // Retrieve : fiche d'une ressource, par exemple Retrieve(ctx, "classic", "items", 5191). func (c *Client) Retrieve(ctx context.Context, version, resource string, id int) (map[string]any, error) { var o map[string]any err := c.Get(ctx, "/"+version+"/"+resource+"/"+strconv.Itoa(id), nil, &o) return o, err } // Search : recherche plein texte dans toute la base. func (c *Client) Search(ctx context.Context, q string, params url.Values) (*Page, error) { p := url.Values{} for k, v := range params { p[k] = v } p.Set("q", q) return c.List(ctx, "/search", p) } // Stats : statistiques de la base. func (c *Client) Stats(ctx context.Context) (map[string]any, error) { var o map[string]any err := c.Get(ctx, "/stats", nil, &o) return o, err }