Module: Asciidoctor::BeautifyUri::Providers::Spotify
- Defined in:
- lib/asciidoctor/beautify_uri/providers/spotify.rb
Overview
uri::spotify:track/
Constant Summary collapse
- OEMBED_ENDPOINT =
'https://open.spotify.com/oembed'- TOKEN_ENDPOINT =
'https://accounts.spotify.com/api/token'- API_BASE =
'https://api.spotify.com/v1'- HTTP_TIMEOUT =
8- API_RESOURCE_TYPES =
open.spotify.com's own web URLs (and this macro's own path syntax, matching them: uri::spotify:track/
[]) use the singular resource name — "track", "album", "episode" — but the Web API's REST endpoints are plural: /v1/tracks/id, not /v1/track/id. Confirmed against the real API (a live credentialed request against the singular path returns a 404 "Service not found"), not assumed from documentation — this is exactly the kind of thing that stays invisible until the credentialed path is actually exercised, since resolve_via_web_api falls back to oEmbed on any failure. { 'track' => 'tracks', 'album' => 'albums', 'artist' => 'artists', 'episode' => 'episodes', 'show' => 'shows', 'playlist' => 'playlists' }.freeze
Class Method Summary collapse
-
.api_path_for(path) ⇒ Object
See API_RESOURCE_TYPES — the macro's own path syntax and open.spotify.com's web URLs use the singular resource name; the Web API's REST endpoints need it pluralized.
- .display_name ⇒ Object
- .fetch_access_token(client_id, client_secret) ⇒ Object
- .get_json(uri, headers: {}) ⇒ Object
- .icon_block ⇒ Object
- .key ⇒ Object
- .link_text ⇒ Object
-
.pdf_thumbnail_radius_pt ⇒ Object
https://developer.spotify.com/documentation/design: "Artwork corners must be rounded to create optical blending with nearby UI elements.
- .resolve(path, doc) ⇒ Object
- .resolve_via_oembed(canonical_url) ⇒ Object
- .resolve_via_web_api(path, canonical_url, client_id, client_secret) ⇒ Object
- .subtitle_for(json) ⇒ Object
- .thumbnail_image_for(json) ⇒ Object
Class Method Details
.api_path_for(path) ⇒ Object
See API_RESOURCE_TYPES — the macro's own path syntax and open.spotify.com's web URLs use the singular resource name; the Web API's REST endpoints need it pluralized.
114 115 116 117 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 114 def self.api_path_for(path) type, id = path.split('/', 2) %(#{API_RESOURCE_TYPES[type] || "#{type}s"}/#{id}) end |
.display_name ⇒ Object
35 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 35 def self.display_name = 'Spotify' |
.fetch_access_token(client_id, client_secret) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 134 def self.fetch_access_token(client_id, client_secret) uri = URI(TOKEN_ENDPOINT) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.open_timeout = HTTP_TIMEOUT http.read_timeout = HTTP_TIMEOUT request = Net::HTTP::Post.new(uri) request.basic_auth(client_id, client_secret) request.set_form_data('grant_type' => 'client_credentials') response = http.request(request) return nil unless response.is_a?(Net::HTTPSuccess) JSON.parse(response.body)['access_token'] rescue StandardError => e Logging.debug(%(spotify token request failed: #{e.})) nil end |
.get_json(uri, headers: {}) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 152 def self.get_json(uri, headers: {}) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.open_timeout = HTTP_TIMEOUT http.read_timeout = HTTP_TIMEOUT request = Net::HTTP::Get.new(uri) headers.each { |k, v| request[k] = v } response = http.request(request) return nil unless response.is_a?(Net::HTTPSuccess) JSON.parse(response.body) rescue StandardError => e Logging.debug(%(spotify request to #{uri} failed: #{e.})) nil end |
.icon_block ⇒ Object
38 39 40 41 42 43 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 38 def self.icon_block # Spotify's own guidelines require no dedicated contrast badge for # the icon; Spotify Green is approved directly on black or white # backgrounds, which is what this card's frame ever sits on. %(<span class="uri-card-icon">#{CardRenderer.icon 'spotify'}</span>) end |
.key ⇒ Object
34 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 34 def self.key = 'spotify' |
.link_text ⇒ Object
36 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 36 def self.link_text = 'Play on Spotify' |
.pdf_thumbnail_radius_pt ⇒ Object
https://developer.spotify.com/documentation/design: "Artwork corners must be rounded to create optical blending with nearby UI elements. Small & medium devices should use a 4px corner radius, whereas large devices should use a 8px corner radius." This is a brand requirement, not a visual preference — unlike the generic uri_card.thumbnail_border_radius theme key (which a theme is free to set for providers with no such rule), a document/theme author cannot override this value for Spotify specifically. 4px (the small/medium figure) since this card's artwork is always compact — a few lines of body text tall — never a large, standalone piece of artwork.
55 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 55 def self.pdf_thumbnail_radius_pt = 4 |
.resolve(path, doc) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 57 def self.resolve(path, doc) return nil if path.nil? || path.empty? canonical_url = "https://open.spotify.com/#{path}" client_id = doc.attr('spotify-client-id') || ENV['SPOTIFY_CLIENT_ID'] client_secret = doc.attr('spotify-client-secret') || ENV['SPOTIFY_CLIENT_SECRET'] if client_id && client_secret resolve_via_web_api(path, canonical_url, client_id, client_secret) else (canonical_url) end end |
.resolve_via_oembed(canonical_url) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 71 def self.(canonical_url) uri = URI(OEMBED_ENDPOINT) uri.query = URI.encode_www_form(url: canonical_url) json = get_json(uri) return nil unless json && json['title'] { title: json['title'], subtitle: nil, thumbnail_url: json['thumbnail_url'], thumbnail_width: json['thumbnail_width'], thumbnail_height: json['thumbnail_height'], target_url: canonical_url, } end |
.resolve_via_web_api(path, canonical_url, client_id, client_secret) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 87 def self.resolve_via_web_api(path, canonical_url, client_id, client_secret) token = fetch_access_token(client_id, client_secret) unless token Logging.debug('spotify Web API credentials rejected (token request failed) — falling back to unauthenticated oEmbed') return (canonical_url) end json = get_json(URI("#{API_BASE}/#{api_path_for path}"), headers: { 'Authorization' => "Bearer #{token}" }) unless json && json['name'] Logging.debug(%(spotify Web API lookup for "#{path}" failed or returned no name — falling back to unauthenticated oEmbed)) return (canonical_url) end image = thumbnail_image_for(json) { title: json['name'], subtitle: subtitle_for(json), thumbnail_url: image && image['url'], thumbnail_width: image && image['width'], thumbnail_height: image && image['height'], target_url: canonical_url, } end |
.subtitle_for(json) ⇒ Object
119 120 121 122 123 124 125 126 127 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 119 def self.subtitle_for(json) if json['artists'].is_a?(Array) && !json['artists'].empty? json['artists'].map { |a| a['name'] }.compact.join(', ') elsif json.dig('show', 'name') json['show']['name'] elsif json.dig('owner', 'display_name') "by #{json['owner']['display_name']}" end end |
.thumbnail_image_for(json) ⇒ Object
129 130 131 132 |
# File 'lib/asciidoctor/beautify_uri/providers/spotify.rb', line 129 def self.thumbnail_image_for(json) images = json.dig('album', 'images') || json['images'] images&.first end |