# frozen_string_literal: true # wowdb.rb : 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 (Ruby 3.0+). Release 58 du site (01/10/2026). # Licence : MIT. Données du jeu © Blizzard Entertainment. # # require_relative "wowdb" # wowdb = WowDb::Client.new(user_agent: "bot-de-ma-guilde/1.0 (https://ma-guilde.example)") # lame = wowdb.retrieve("classic", "items", 5191) # wowdb.iterate("/classic/items", type: "dagger", rogue: true) { |dague| puts dague["name"] } require "json" require "net/http" require "uri" module WowDb VERSION = "1.0.0" BASE_URL = "https://api.wowdb.assemblee-defias.fr/v1" # Erreur de l'API (corps { error: { type, code, message, param, doc_url } }) ou du réseau. class Error < StandardError attr_reader :status, :type, :code, :param, :doc_url, :request_id def initialize(message, status: 0, type: "api_error", code: "network_error", param: nil, doc_url: nil, request_id: nil) super(message) @status = status @type = type @code = code @param = param @doc_url = doc_url @request_id = request_id end end class Client attr_reader :base_url, :user_agent def initialize(user_agent: "wowdb.rb/#{VERSION}", base_url: BASE_URL, max_retries: 2, timeout: 30) @user_agent = user_agent @base_url = base_url.chomp("/") @max_retries = max_retries @timeout = timeout end # Requête GET brute : chemin relatif à /v1 (« /classic/items/5191 ») ; renvoie le JSON (Hash). def request(path, params = {}) propres = params.compact.transform_values { |v| v.is_a?(Array) ? v.join(",") : v.to_s } uri = URI(@base_url + (path.start_with?("/") ? path : "/#{path}")) uri.query = URI.encode_www_form(propres) unless propres.empty? essai = 0 loop do res = begin Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: @timeout, read_timeout: @timeout) do |http| req = Net::HTTP::Get.new(uri) req["User-Agent"] = @user_agent req["Accept"] = "application/json" http.request(req) end rescue StandardError => e raise Error.new("Requête impossible : #{e.message}") end status = res.code.to_i if status == 429 && essai < @max_retries essai += 1 sleep([1, res["Retry-After"].to_i].max) next end corps = begin JSON.parse(res.body.to_s) rescue JSON::ParserError nil end if status >= 400 || !corps.is_a?(Hash) e = corps.is_a?(Hash) ? (corps["error"] || {}) : {} raise Error.new(e["message"] || "Erreur HTTP #{status}", status: status, type: e["type"] || "api_error", code: e["code"] || "http_error", param: e["param"], doc_url: e["doc_url"], request_id: res["X-Request-Id"]) end return corps end end # Une page d'une liste : { "object" => "list", "data" => [...], "has_more" => …, "next_cursor" => … }. def list(path, params = {}) request(path, params) end # Tous les éléments d'une liste, page après page (pages de 100). Sans bloc : un Enumerator. def iterate(path, params = {}, &bloc) return enum_for(:iterate, path, params) unless bloc params = { limit: 100 }.merge(params) loop do page = request(path, params) page["data"].each(&bloc) break unless page["next_cursor"] params = params.merge(cursor: page["next_cursor"]) end end # Fiche d'une ressource : retrieve("classic", "items", 5191). def retrieve(version, resource, id) request("/#{version}/#{resource}/#{id}") end # Recherche plein texte dans toute la base. def search(q, params = {}) request("/search", params.merge(q: q)) end def stats request("/stats") end def versions request("/versions") end end end