Packages

A Last.fm API client for Elixir.

Current section

Files

Jump to
lastex README.md
Raw

README.md

# Lastex
A [Last.fm API](https://www.last.fm/api) client for Elixir.
## Installation
Add `lastex` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:lastex, "~> 0.1.0"}
]
end
```
## Configuration
Get an API key and secret by [creating a Last.fm API account](https://www.last.fm/api/account/create),
then configure them:
```elixir
config :lastex,
api_key: System.get_env("LASTFM_API_KEY"),
api_secret: System.get_env("LASTFM_API_SECRET")
```
The secret is only needed for authenticated calls.
## Usage
Every function returns `{:ok, result}` or `{:error, %Lastex.Error{}}`. Read
functions take options as a keyword list, which is passed through to the Last.fm
query parameters — so any option the API documents (`autocorrect`, `lang`,
`from`, `to`, …) works even when it isn't named below.
Names are always given artist-first: `Lastex.Track.info("Opeth", "Blackwater Park")`.
### User
```elixir
Lastex.User.info("rj")
Lastex.User.friends("rj", limit: 50)
Lastex.User.recent_tracks("rj", limit: 200)
Lastex.User.loved_tracks("rj", limit: 50)
Lastex.User.top_artists("rj", period: "overall", limit: 10)
Lastex.User.top_albums("rj", period: "6month", limit: 10)
Lastex.User.top_tracks("rj", period: "1month", limit: 10)
Lastex.User.top_tags("rj", limit: 10)
Lastex.User.personal_tags("rj", "progressive metal", "artist")
# Weekly charts
Lastex.User.weekly_chart_list("rj")
Lastex.User.weekly_artist_chart("rj", from: 1_108_296_000, to: 1_108_900_800)
Lastex.User.weekly_album_chart("rj")
Lastex.User.weekly_track_chart("rj")
```
`period` is one of `"overall"`, `"7day"`, `"1month"`, `"3month"`, `"6month"`,
or `"12month"`.
### Artist
```elixir
Lastex.Artist.info("Opeth")
Lastex.Artist.correction("opeth")
Lastex.Artist.similar("Opeth", limit: 10)
Lastex.Artist.tags("Opeth", user: "rj")
Lastex.Artist.top_tags("Opeth")
Lastex.Artist.top_albums("Opeth", limit: 10)
Lastex.Artist.top_tracks("Opeth", limit: 10)
Lastex.Artist.search("Opeth", limit: 5)
```
### Album
```elixir
Lastex.Album.info("Opeth", "Blackwater Park")
Lastex.Album.tags("Opeth", "Blackwater Park", user: "rj")
Lastex.Album.top_tags("Opeth", "Blackwater Park")
Lastex.Album.search("Blackwater Park", limit: 5)
```
### Track
```elixir
Lastex.Track.info("Opeth", "Blackwater Park")
Lastex.Track.correction("opeth", "blackwater park")
Lastex.Track.similar("Opeth", "Blackwater Park", limit: 10)
Lastex.Track.tags("Opeth", "Blackwater Park", user: "rj")
Lastex.Track.top_tags("Opeth", "Blackwater Park")
Lastex.Track.search("Blackwater Park", limit: 5)
```
### Tag
```elixir
Lastex.Tag.info("progressive metal")
Lastex.Tag.similar("progressive metal")
Lastex.Tag.top_artists("progressive metal", limit: 10)
Lastex.Tag.top_albums("progressive metal", limit: 10)
Lastex.Tag.top_tracks("progressive metal", limit: 10)
Lastex.Tag.top_tags()
Lastex.Tag.weekly_chart_list("progressive metal")
```
### Chart, Geo, and Library
```elixir
Lastex.Chart.top_artists(limit: 10)
Lastex.Chart.top_tracks(limit: 10)
Lastex.Chart.top_tags(limit: 10)
Lastex.Geo.top_artists("Sweden", limit: 10)
Lastex.Geo.top_tracks("Sweden", limit: 10)
Lastex.Library.artists("rj", limit: 50)
```
## Authentication
Write operations need a session key, obtained by having the user authorize your
API key. Session keys do not expire, so store them alongside the user.
### Desktop flow
```elixir
# 1. Fetch an unauthorized request token
{:ok, %{"token" => token}} = Lastex.Auth.token()
# 2. Send the user to authorize it in their browser
Lastex.Auth.url(token)
#=> "https://www.last.fm/api/auth/?api_key=YOUR_KEY&token=TOKEN"
# 3. Once they've authorized, exchange the token for a session key
{:ok, %{"session" => %{"key" => session_key, "name" => username}}} =
Lastex.Auth.session(token)
```
### Web flow
Send the user to `https://www.last.fm/api/auth/?api_key=YOUR_KEY`. Last.fm
redirects them to the callback URL registered on your API account with a `token`
query parameter, which you exchange via `Lastex.Auth.session/1` as above. Tokens
are single-use and valid for 60 minutes.
### Mobile flow
For applications that collect credentials in their own UI. Requires the mobile
authentication permission on your API account.
```elixir
{:ok, %{"session" => %{"key" => session_key}}} =
Lastex.Auth.mobile_session("username", "password")
```
### Authenticated calls
Pass the session key as the last argument:
```elixir
Lastex.Track.love("Opeth", "Blackwater Park", session_key)
Lastex.Track.unlove("Opeth", "Blackwater Park", session_key)
Lastex.Artist.add_tags("Opeth", "progressive metal,swedish", session_key)
Lastex.Artist.remove_tag("Opeth", "swedish", session_key)
Lastex.Album.add_tags("Opeth", "Blackwater Park", "progressive metal", session_key)
Lastex.Album.remove_tag("Opeth", "Blackwater Park", "progressive metal", session_key)
Lastex.Track.add_tags("Opeth", "Blackwater Park", "progressive metal", session_key)
Lastex.Track.remove_tag("Opeth", "Blackwater Park", "progressive metal", session_key)
```
Signing is automatic. Lastex builds the `api_sig` by sorting all parameters
alphabetically (excluding `format` and `callback`), concatenating them as
`<name><value>`, appending your API secret, and taking the MD5 hash.
## Pagination
List endpoints return a `Lastex.Page` struct and accept `page` and `limit`:
```elixir
{:ok, page} = Lastex.User.recent_tracks("rj", page: 1, limit: 200)
page.data # the list of results
page.page # 1
page.per_page # 200
page.total # 85_000
page.total_pages # 425
```
Endpoints returning a single entity — `Lastex.User.info/1`, `Lastex.Artist.info/2`,
`Lastex.Album.info/3`, `Lastex.Track.info/3`, `Lastex.Tag.info/2`, the
`weekly_chart_list` functions, and the correction functions — return the decoded
response body directly.
## Error handling
```elixir
case Lastex.User.info("nonexistent") do
{:ok, user} -> user
{:error, %Lastex.Error{code: 6}} -> :not_found
{:error, reason} -> {:error, reason}
end
```
`Lastex.Error` is an exception, so it also works with `raise`. Common codes:
| Code | Meaning |
|---|---|
| 6 | Invalid parameters / resource not found |
| 8 | Operation failed — try again |
| 9 | Invalid session key |
| 10 | Invalid API key |
| 11 | Service offline |
| 13 | Invalid method signature |
| 26 | API key suspended |
| 29 | Rate limit exceeded |
Transport failures pass the underlying `Req`/`Finch` reason through unchanged.
## HTTP client
Lastex uses [Req](https://hexdocs.pm/req). Options are merged into every
request, which is how the test suite stubs the API:
```elixir
config :lastex, req_options: [plug: {Req.Test, Lastex.Client}]
```
Last.fm asks for no more than 5 requests per second per key. Lastex does not
throttle for you — rate limiting is on the roadmap.
## Not yet supported
- Scrobbling (`track.scrobble`, `track.updateNowPlaying`)
- Client-side rate limiting
See the [roadmap](https://gitlab.com/tristanperalta/lastex/-/blob/main/ROADMAP.md).
## License
MIT — see [LICENSE](LICENSE).