let spinitron = {submit=()} # Submit a track to the spinitron track system # and return the raw response. # @category Interaction # @flag extra # @param ~api_key API key def spinitron.submit.raw( ~host="https://spinitron.com/api", ~api_key, ~live=false, ~start=null, ~duration=null, ~artist, ~release=null, ~label=null, ~genre=null, ~song, ~composer=null, ~isrc=null ) = params = [("song", song), ("artist", artist)] def fold_optional_string_params(params, param) = let (label, param) = param if null.defined(param) then [(label, null.get(param)), ...params] else params end end params = list.fold( fold_optional_string_params, params, [ ("live", null.map(fun (b) -> b ? "1" : "0" , (live : bool?))), ("start", start), ("duration", null.map(string, (duration : int?))), ("release", release), ("label", label), ("genre", genre), ("composer", composer), ("isrc", isrc) ] ) def encode_param(param) = let (label, param) = param "#{label}=#{url.encode(param)}" end params = string.concat(separator="&", list.map(encode_param, params)) http.post( data=params, headers= [ ("Accept", "application/json"), ("Content-Type", "application/x-www-form-urlencoded"), ( "Authorization", "Bearer #{(api_key : string)}" ) ], "#{host}/spins" ) end # Submit a track to the spinitron track system # and return the parsed response # @category Interaction # @flag extra # @param ~api_key API key def replaces spinitron.submit(%argsof(spinitron.submit.raw)) = resp = spinitron.submit.raw(%argsof(spinitron.submit.raw)) if resp.status_code == 201 then let json.parse (resp : { id: int, playlist_id: int, "start" as spin_start: string, "end" as spin_end: string?, duration: int?, timezone: string?, image: string?, classical: bool?, artist: string, "artist-custom" as artist_custom: string?, composer: string?, release: string?, "release-custom" as release_custom: string?, va: bool?, label: string?, "label-custom" as label_custom: string?, released: int?, medium: string?, genre: string?, song: string, note: string?, request: bool?, local: bool?, new: bool?, work: string?, conductor: string?, performers: string?, ensemble: string?, "catalog-number" as catalog_number: string?, isrc: string?, upc: string?, iswc: string?, "_links" as links: {self: {href: string}?, playlist: {href: string}?}? } ) = resp resp elsif resp.status_code == 422 then let json.parse (errors : [{field: string, message: string}]) = resp errors = list.map( fun (p) -> begin let {field, message} = p "#{field}: #{message}" end, errors ) errors = string.concat( separator= ", ", errors ) error.raise( error.raise( error.http, "Invalid fields: #{errors}" ) ) else let json.parse ({name, message, code, status, type} : {name: string, message: string, code: int, status: int, type: string?} ) = resp type = type ?? "undefined" error.raise( error.raise( error.http, "#{name}: #{message} (code: #{code}, status: #{status}, type: #{type})" ) ) end end # Submit a spin using the given metadata to the spinitron track system # and return the parsed response. `artist` and `song` (or `title`) must # be present either as metadata or as optional argument. # @category Interaction # @flag extra # @param m Metadata to submit. Overrides optional arguments when present. # @param ~mapper Metadata mapper that can be used to map metadata fields to spinitron's expected. \ # Returned metadata are added to the submitted metadata. By default, `title` is \ # mapped to `song` and `album` to `release` if neither of those passed otherwise. # @param ~api_key API key def spinitron.submit.metadata( %argsof(spinitron.submit[!artist,!song]), ~mapper=( fun (m) -> [ ...(m["song"] != "" or m["title"] == "" ? [] : [("song", m["title"])] ), ...( m["release"] != "" or m["album"] == "" ? [] : [("release", m["album"])] ) ] ), ~artist=null, ~song=null, m ) = m = [...m, ...mapper(m)] def conv_opt_arg(convert, label, default) = list.assoc.mem(label, m) ? convert(m[label]) : default end opt_arg = fun (label, default) -> conv_opt_arg(fun (x) -> null(x), label, default) live = conv_opt_arg(bool_of_string, "live", live) start = opt_arg("start", start) duration = conv_opt_arg(int_of_string, "duration", duration) artist = opt_arg("artist", artist) release = opt_arg("release", release) label = opt_arg("label", label) genre = opt_arg("genre", genre) song = opt_arg("song", song) composer = opt_arg("composer", composer) isrc = opt_arg("isrc", isrc) if artist == null or song == null then error.raise( error.invalid, "Both \"artist\" and \"song\" (or \"title\" metadata) must be provided!" ) end artist = null.get(artist) song = null.get(song) res = spinitron.submit(%argsof(spinitron.submit)) print(res) res end # Specialized version of `source.on_metadata` that submits spins using # the source's metadata to the spinitron track system. `artist` and `song` # (or `title`) must be present either as metadata or as optional argument. # @category Interaction # @flag extra # @param m Metadata to submit. Overrides optional arguments when present. # @param ~api_key API key def spinitron.submit.on_metadata( ~id=null, %argsof(spinitron.submit.metadata), s ) = def on_metadata(m) = if m["title"] == "" and m["song"] == "" then log.severe( label=source.id(s), "Field \"song\" or \"title\" missing, skipping metadata spinitron \ submission." ) elsif m["artist"] == "" then log.severe( label=source.id(s), "Field \"artist\" missing, skipping metadata spinitron submission." ) else try ignore(spinitron.submit.metadata(%argsof(spinitron.submit.metadata), m)) log.important( label=source.id(s), "Successfully submitted spin from metadata" ) catch err do log.severe( label=source.id(s), "Error while submitting spin from metadata: #{err}" ) end end end source.on_metadata(id=id, s, on_metadata) end