rocksky 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.artist.*` endpoints.
4
+ class Artist < Base
5
+ # Fetch an artist by AT-URI.
6
+ def get_artist(uri:)
7
+ query("app.rocksky.artist.getArtist", uri: uri)
8
+ end
9
+
10
+ # List artists. `names` may be an Array of strings; it will be joined CSV-style.
11
+ def get_artists(limit: nil, offset: nil, names: nil, genre: nil)
12
+ query("app.rocksky.artist.getArtists",
13
+ limit: limit, offset: offset, names: names, genre: genre)
14
+ end
15
+
16
+ # Albums by an artist.
17
+ def get_artist_albums(uri:)
18
+ query("app.rocksky.artist.getArtistAlbums", uri: uri)
19
+ end
20
+
21
+ # Tracks by an artist.
22
+ def get_artist_tracks(uri:, limit: nil, offset: nil)
23
+ query("app.rocksky.artist.getArtistTracks",
24
+ uri: uri, limit: limit, offset: offset)
25
+ end
26
+
27
+ # All-time listeners for an artist.
28
+ def get_artist_listeners(uri:, limit: nil, offset: nil)
29
+ query("app.rocksky.artist.getArtistListeners",
30
+ uri: uri, limit: limit, offset: offset)
31
+ end
32
+
33
+ # Recent listeners for an artist.
34
+ def get_artist_recent_listeners(uri:, limit: nil, offset: nil)
35
+ query("app.rocksky.artist.getArtistRecentListeners",
36
+ uri: uri, limit: limit, offset: offset)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ module Rocksky
2
+ module Resources
3
+ # Shared plumbing for resource classes. Each subclass calls {#query} or
4
+ # {#procedure} with the lexicon NSID; the {Rocksky::HTTP} instance does the
5
+ # actual transport work.
6
+ class Base
7
+ attr_reader :http
8
+
9
+ def initialize(http)
10
+ @http = http
11
+ end
12
+
13
+ protected
14
+
15
+ def query(nsid, **params)
16
+ @http.query(nsid, params)
17
+ end
18
+
19
+ def procedure(nsid, params: {}, body: nil)
20
+ @http.procedure(nsid, params, body)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.charts.*` endpoints.
4
+ class Charts < Base
5
+ # Scrobble chart data.
6
+ def get_scrobbles_chart(did: nil, artist_uri: nil, album_uri: nil,
7
+ song_uri: nil, genre: nil, from: nil, to: nil)
8
+ query("app.rocksky.charts.getScrobblesChart",
9
+ did: did,
10
+ artisturi: artist_uri,
11
+ albumuri: album_uri,
12
+ songuri: song_uri,
13
+ genre: genre,
14
+ from: from,
15
+ to: to)
16
+ end
17
+
18
+ # Top artists.
19
+ def get_top_artists(limit: nil, offset: nil, start_date: nil, end_date: nil)
20
+ query("app.rocksky.charts.getTopArtists",
21
+ limit: limit, offset: offset,
22
+ startDate: start_date, endDate: end_date)
23
+ end
24
+
25
+ # Top tracks.
26
+ def get_top_tracks(limit: nil, offset: nil, start_date: nil, end_date: nil)
27
+ query("app.rocksky.charts.getTopTracks",
28
+ limit: limit, offset: offset,
29
+ startDate: start_date, endDate: end_date)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.feed.*` endpoints.
4
+ class Feed < Base
5
+ # Free-text search across the catalogue.
6
+ # `q` accepts a positional or `query:` keyword; both map to the lexicon's
7
+ # `query` parameter.
8
+ def search(q = nil, query: nil)
9
+ term = q || query
10
+ raise ArgumentError, "search needs a query string" if term.nil? || term.empty?
11
+
12
+ @http.query("app.rocksky.feed.search", query: term)
13
+ end
14
+
15
+ # List feed generators.
16
+ def get_feed_generators(size: nil)
17
+ query("app.rocksky.feed.getFeedGenerators", size: size)
18
+ end
19
+
20
+ # Fetch a feed generator by URI.
21
+ def get_feed_generator(feed:)
22
+ query("app.rocksky.feed.getFeedGenerator", feed: feed)
23
+ end
24
+
25
+ # Fetch feed contents.
26
+ def get_feed(feed:, limit: nil, cursor: nil)
27
+ query("app.rocksky.feed.getFeed", feed: feed, limit: limit, cursor: cursor)
28
+ end
29
+
30
+ # Stories (recent highlights).
31
+ def get_stories(size: nil)
32
+ query("app.rocksky.feed.getStories", size: size)
33
+ end
34
+
35
+ # Track recommendations for an actor.
36
+ def get_recommendations(did:, limit: nil)
37
+ query("app.rocksky.feed.getRecommendations", did: did, limit: limit)
38
+ end
39
+
40
+ # Artist recommendations for an actor.
41
+ def get_artist_recommendations(did:, limit: nil)
42
+ query("app.rocksky.feed.getArtistRecommendations", did: did, limit: limit)
43
+ end
44
+
45
+ # Album recommendations for an actor.
46
+ def get_album_recommendations(did:, limit: nil)
47
+ query("app.rocksky.feed.getAlbumRecommendations", did: did, limit: limit)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,34 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.graph.*` endpoints.
4
+ class Graph < Base
5
+ # Follow an account. `account` is a DID or handle.
6
+ def follow_account(account:)
7
+ procedure("app.rocksky.graph.followAccount", params: { account: account })
8
+ end
9
+
10
+ # Unfollow an account.
11
+ def unfollow_account(account:)
12
+ procedure("app.rocksky.graph.unfollowAccount", params: { account: account })
13
+ end
14
+
15
+ # Followers of an actor. `dids` may be a list; it will be CSV-encoded.
16
+ def get_followers(actor:, limit: nil, dids: nil, cursor: nil)
17
+ query("app.rocksky.graph.getFollowers",
18
+ actor: actor, limit: limit, dids: dids, cursor: cursor)
19
+ end
20
+
21
+ # Accounts an actor follows.
22
+ def get_follows(actor:, limit: nil, dids: nil, cursor: nil)
23
+ query("app.rocksky.graph.getFollows",
24
+ actor: actor, limit: limit, dids: dids, cursor: cursor)
25
+ end
26
+
27
+ # Followers of an actor that the viewer also knows.
28
+ def get_known_followers(actor:, limit: nil, cursor: nil)
29
+ query("app.rocksky.graph.getKnownFollowers",
30
+ actor: actor, limit: limit, cursor: cursor)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.like.*` endpoints. All require an authenticated client.
4
+ class Like < Base
5
+ # Like a song.
6
+ def like_song(uri:)
7
+ procedure("app.rocksky.like.likeSong", body: { uri: uri })
8
+ end
9
+
10
+ # Remove a like on a song.
11
+ def dislike_song(uri:)
12
+ procedure("app.rocksky.like.dislikeSong", body: { uri: uri })
13
+ end
14
+
15
+ # Like a shout.
16
+ def like_shout(uri:)
17
+ procedure("app.rocksky.like.likeShout", body: { uri: uri })
18
+ end
19
+
20
+ # Remove a like on a shout.
21
+ def dislike_shout(uri:)
22
+ procedure("app.rocksky.like.dislikeShout", body: { uri: uri })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.mirror.*` endpoints. Require an authenticated client.
4
+ class Mirror < Base
5
+ # List configured mirror sources for the authenticated user.
6
+ def get_mirror_sources
7
+ query("app.rocksky.mirror.getMirrorSources")
8
+ end
9
+
10
+ # Configure a mirror source (e.g. Last.fm, ListenBrainz).
11
+ def put_mirror_source(provider:, enabled: nil, external_username: nil, api_key: nil)
12
+ body = {
13
+ provider: provider,
14
+ enabled: enabled,
15
+ externalUsername: external_username,
16
+ apiKey: api_key
17
+ }.compact
18
+ procedure("app.rocksky.mirror.putMirrorSource", body: body)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,73 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.player.*` endpoints — remote control of a Rocksky player.
4
+ class Player < Base
5
+ # Currently playing track.
6
+ def get_currently_playing(player_id: nil, actor: nil)
7
+ query("app.rocksky.player.getCurrentlyPlaying",
8
+ playerId: player_id, actor: actor)
9
+ end
10
+
11
+ # Playback queue.
12
+ def get_playback_queue(player_id: nil)
13
+ query("app.rocksky.player.getPlaybackQueue", playerId: player_id)
14
+ end
15
+
16
+ # Resume playback.
17
+ def play(player_id: nil)
18
+ procedure("app.rocksky.player.play", params: { playerId: player_id })
19
+ end
20
+
21
+ # Pause playback.
22
+ def pause(player_id: nil)
23
+ procedure("app.rocksky.player.pause", params: { playerId: player_id })
24
+ end
25
+
26
+ # Skip to next track.
27
+ def next(player_id: nil)
28
+ procedure("app.rocksky.player.next", params: { playerId: player_id })
29
+ end
30
+
31
+ # Go to previous track.
32
+ def previous(player_id: nil)
33
+ procedure("app.rocksky.player.previous", params: { playerId: player_id })
34
+ end
35
+
36
+ # Seek to position in milliseconds.
37
+ def seek(position:, player_id: nil)
38
+ procedure("app.rocksky.player.seek",
39
+ params: { playerId: player_id, position: position })
40
+ end
41
+
42
+ # Play a single file.
43
+ def play_file(file_id:, player_id: nil)
44
+ procedure("app.rocksky.player.playFile",
45
+ params: { playerId: player_id, fileId: file_id })
46
+ end
47
+
48
+ # Play a directory.
49
+ def play_directory(directory_id:, player_id: nil, shuffle: nil,
50
+ recurse: nil, position: nil)
51
+ procedure("app.rocksky.player.playDirectory",
52
+ params: {
53
+ playerId: player_id,
54
+ directoryId: directory_id,
55
+ shuffle: shuffle,
56
+ recurse: recurse,
57
+ position: position
58
+ })
59
+ end
60
+
61
+ # Append items to the queue. `items` is an Array of file identifiers.
62
+ def add_items_to_queue(items:, player_id: nil, position: nil, shuffle: nil)
63
+ procedure("app.rocksky.player.addItemsToQueue",
64
+ params: {
65
+ playerId: player_id,
66
+ items: items,
67
+ position: position,
68
+ shuffle: shuffle
69
+ })
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,45 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.playlist.*` endpoints.
4
+ class Playlist < Base
5
+ # Fetch a playlist.
6
+ def get_playlist(uri:)
7
+ query("app.rocksky.playlist.getPlaylist", uri: uri)
8
+ end
9
+
10
+ # List playlists.
11
+ def get_playlists(limit: nil, offset: nil)
12
+ query("app.rocksky.playlist.getPlaylists", limit: limit, offset: offset)
13
+ end
14
+
15
+ # Create a playlist.
16
+ def create_playlist(name:, description: nil)
17
+ procedure("app.rocksky.playlist.createPlaylist",
18
+ params: { name: name, description: description })
19
+ end
20
+
21
+ # Remove a playlist.
22
+ def remove_playlist(uri:)
23
+ procedure("app.rocksky.playlist.removePlaylist", params: { uri: uri })
24
+ end
25
+
26
+ # Start a playlist.
27
+ def start_playlist(uri:, shuffle: nil, position: nil)
28
+ procedure("app.rocksky.playlist.startPlaylist",
29
+ params: { uri: uri, shuffle: shuffle, position: position })
30
+ end
31
+
32
+ # Insert files into a playlist.
33
+ def insert_files(uri:, files:, position: nil)
34
+ procedure("app.rocksky.playlist.insertFiles",
35
+ params: { uri: uri, files: files, position: position })
36
+ end
37
+
38
+ # Insert a directory into a playlist.
39
+ def insert_directory(uri:, directory:, position: nil)
40
+ procedure("app.rocksky.playlist.insertDirectory",
41
+ params: { uri: uri, directory: directory, position: position })
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.scrobble.*` endpoints.
4
+ class Scrobble < Base
5
+ # Create a new scrobble. Requires an authenticated client.
6
+ #
7
+ # Required: `title`, `artist`. Everything else is optional metadata that
8
+ # gets forwarded as-is to the lexicon's createScrobble body. Extra fields
9
+ # may be passed via `**extra`.
10
+ #
11
+ # Example:
12
+ #
13
+ # client.scrobble.create_scrobble(
14
+ # title: "In Bloom",
15
+ # artist: "Nirvana",
16
+ # album: "Nevermind",
17
+ # timestamp: Time.now.to_i
18
+ # )
19
+ def create_scrobble(title:, artist:, **extra)
20
+ body = { title: title, artist: artist }.merge(extra).compact
21
+ procedure("app.rocksky.scrobble.createScrobble", body: body)
22
+ end
23
+
24
+ # Fetch a scrobble by URI.
25
+ def get_scrobble(uri:)
26
+ query("app.rocksky.scrobble.getScrobble", uri: uri)
27
+ end
28
+
29
+ # List scrobbles.
30
+ def get_scrobbles(did: nil, following: nil, limit: nil, offset: nil)
31
+ query("app.rocksky.scrobble.getScrobbles",
32
+ did: did, following: following, limit: limit, offset: offset)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.shout.*` endpoints.
4
+ class Shout < Base
5
+ # Create a shout.
6
+ def create_shout(message:, **extra)
7
+ body = { message: message }.merge(extra).compact
8
+ procedure("app.rocksky.shout.createShout", body: body)
9
+ end
10
+
11
+ # Reply to a shout.
12
+ def reply_shout(shout_id:, message:)
13
+ procedure("app.rocksky.shout.replyShout",
14
+ body: { shoutId: shout_id, message: message })
15
+ end
16
+
17
+ # Remove a shout.
18
+ def remove_shout(id:)
19
+ procedure("app.rocksky.shout.removeShout", params: { id: id })
20
+ end
21
+
22
+ # Report a shout.
23
+ def report_shout(shout_id:, reason:)
24
+ procedure("app.rocksky.shout.reportShout",
25
+ body: { shoutId: shout_id, reason: reason })
26
+ end
27
+
28
+ # Shouts on a profile.
29
+ def get_profile_shouts(did:, limit: nil, offset: nil)
30
+ query("app.rocksky.shout.getProfileShouts",
31
+ did: did, limit: limit, offset: offset)
32
+ end
33
+
34
+ # Shouts on an album.
35
+ def get_album_shouts(uri:, limit: nil, offset: nil)
36
+ query("app.rocksky.shout.getAlbumShouts",
37
+ uri: uri, limit: limit, offset: offset)
38
+ end
39
+
40
+ # Shouts on an artist.
41
+ def get_artist_shouts(uri:, limit: nil, offset: nil)
42
+ query("app.rocksky.shout.getArtistShouts",
43
+ uri: uri, limit: limit, offset: offset)
44
+ end
45
+
46
+ # Shouts on a track.
47
+ def get_track_shouts(uri:)
48
+ query("app.rocksky.shout.getTrackShouts", uri: uri)
49
+ end
50
+
51
+ # Replies to a shout.
52
+ def get_shout_replies(uri:, limit: nil, offset: nil)
53
+ query("app.rocksky.shout.getShoutReplies",
54
+ uri: uri, limit: limit, offset: offset)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.song.*` endpoints.
4
+ class Song < Base
5
+ # Fetch a song by URI, MusicBrainz id, ISRC, or Spotify id.
6
+ def get_song(uri: nil, mbid: nil, isrc: nil, spotify_id: nil)
7
+ query("app.rocksky.song.getSong",
8
+ uri: uri, mbid: mbid, isrc: isrc, spotifyId: spotify_id)
9
+ end
10
+
11
+ # List songs.
12
+ def get_songs(limit: nil, offset: nil, genre: nil,
13
+ mbid: nil, isrc: nil, spotify_id: nil)
14
+ query("app.rocksky.song.getSongs",
15
+ limit: limit, offset: offset, genre: genre,
16
+ mbid: mbid, isrc: isrc, spotifyId: spotify_id)
17
+ end
18
+
19
+ # Recent listeners for a song.
20
+ def get_song_recent_listeners(uri:, limit: nil, offset: nil)
21
+ query("app.rocksky.song.getSongRecentListeners",
22
+ uri: uri, limit: limit, offset: offset)
23
+ end
24
+
25
+ # Find an existing song by metadata.
26
+ def match_song(title:, artist:, mb_id: nil, isrc: nil)
27
+ query("app.rocksky.song.matchSong",
28
+ title: title, artist: artist, mbId: mb_id, isrc: isrc)
29
+ end
30
+
31
+ # Create a song.
32
+ def create_song(title:, artist:, **extra)
33
+ body = { title: title, artist: artist }.merge(extra).compact
34
+ procedure("app.rocksky.song.createSong", body: body)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.spotify.*` endpoints — Spotify remote control for the
4
+ # authenticated user.
5
+ class Spotify < Base
6
+ # Currently playing on Spotify.
7
+ def get_currently_playing(actor: nil)
8
+ query("app.rocksky.spotify.getCurrentlyPlaying", actor: actor)
9
+ end
10
+
11
+ # Resume Spotify playback.
12
+ def play
13
+ procedure("app.rocksky.spotify.play")
14
+ end
15
+
16
+ # Pause Spotify playback.
17
+ def pause
18
+ procedure("app.rocksky.spotify.pause")
19
+ end
20
+
21
+ # Skip to next track.
22
+ def next
23
+ procedure("app.rocksky.spotify.next")
24
+ end
25
+
26
+ # Go to previous track.
27
+ def previous
28
+ procedure("app.rocksky.spotify.previous")
29
+ end
30
+
31
+ # Seek to position in milliseconds.
32
+ def seek(position:)
33
+ procedure("app.rocksky.spotify.seek", params: { position: position })
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module Rocksky
2
+ module Resources
3
+ # `app.rocksky.stats.*` endpoints.
4
+ class Stats < Base
5
+ # Per-user stats.
6
+ def get_stats(did:)
7
+ query("app.rocksky.stats.getStats", did: did)
8
+ end
9
+
10
+ # Year-in-review ("Wrapped") data.
11
+ def get_wrapped(did:, year: nil)
12
+ query("app.rocksky.stats.getWrapped", did: did, year: year)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Rocksky
2
+ VERSION = "0.1.0".freeze
3
+ end
data/lib/rocksky.rb ADDED
@@ -0,0 +1,32 @@
1
+ require "rocksky/version"
2
+ require "rocksky/error"
3
+ require "rocksky/http"
4
+ require "rocksky/resources/base"
5
+ require "rocksky/resources/actor"
6
+ require "rocksky/resources/album"
7
+ require "rocksky/resources/apikey"
8
+ require "rocksky/resources/artist"
9
+ require "rocksky/resources/charts"
10
+ require "rocksky/resources/feed"
11
+ require "rocksky/resources/graph"
12
+ require "rocksky/resources/like"
13
+ require "rocksky/resources/mirror"
14
+ require "rocksky/resources/player"
15
+ require "rocksky/resources/playlist"
16
+ require "rocksky/resources/scrobble"
17
+ require "rocksky/resources/shout"
18
+ require "rocksky/resources/song"
19
+ require "rocksky/resources/spotify"
20
+ require "rocksky/resources/stats"
21
+ require "rocksky/client"
22
+
23
+ module Rocksky
24
+ # Build a new Rocksky client.
25
+ #
26
+ # client = Rocksky.new(token: ENV["ROCKSKY_TOKEN"])
27
+ #
28
+ # See {Rocksky::Client#initialize} for all options.
29
+ def self.new(**opts)
30
+ Client.new(**opts)
31
+ end
32
+ end
data/rocksky.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ require_relative "lib/rocksky/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rocksky"
5
+ spec.version = Rocksky::VERSION
6
+ spec.authors = ["Rocksky"]
7
+ spec.email = ["hi@rocksky.app"]
8
+
9
+ spec.summary = "Ruby client for the Rocksky XRPC API."
10
+ spec.description = "Idiomatic Ruby SDK for Rocksky (rocksky.app) — scrobbles, " \
11
+ "shouts, charts, playlists, and more."
12
+ spec.homepage = "https://github.com/tsirysndr/rocksky"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata = {
18
+ "homepage_uri" => spec.homepage,
19
+ "source_code_uri" => "https://github.com/tsirysndr/rocksky/tree/main/sdk/ruby",
20
+ "bug_tracker_uri" => "https://github.com/tsirysndr/rocksky/issues",
21
+ "documentation_uri" => "https://docs.rocksky.app",
22
+ "rubygems_mfa_required" => "true"
23
+ }
24
+
25
+ spec.files = Dir[
26
+ "lib/**/*.rb",
27
+ "exe/*",
28
+ "*.gemspec",
29
+ "LICENSE",
30
+ "README.md",
31
+ "CHANGELOG.md"
32
+ ]
33
+ spec.require_paths = ["lib"]
34
+ spec.bindir = "exe"
35
+ spec.executables = Dir["exe/*"].map { |f| File.basename(f) }
36
+
37
+ spec.add_development_dependency "irb", "~> 1.11"
38
+ spec.add_development_dependency "minitest", "~> 5.20"
39
+ spec.add_development_dependency "rake", "~> 13.0"
40
+ spec.add_development_dependency "webmock", "~> 3.19"
41
+ end