Skip to content

ryzetech/CiderWS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🍎 CiderWS 🎶

NodeJS wrapper for the Apple Music client Cider

GitHub code size in bytes GitHub issues Discord Server Badge

CiderWS Cover

Current Development Status Current Development Status

About

Cider (cider.sh - an open-source client for Apple Music) has a WebSocket API. Yes, I wouldn't have thought either! But, unlike the REST API, calling it "documentated" would be a blunt lie because documentation... doesn't exist. No worries tho! I had the absolute boredom to do some digging in the JavaScript code for the Web Remote and here we are. I just wrote this as a part of a cool VS Code extension... Oh well...

A thing to note

This will be my first ever npm package. Please don't be mad at me when I made mistakes, didn't work efficiently or overlooked flaws. Just be patient, create an issue or even open a merge request with your fix! I will gladly take every help I can get! :D

Installation

soon tm

Documentation

Methods

connect()

This one is easy. You don't even have to call it because it gets called in the constructor anyways (the call gets ignored if a connection is already established). If you have closed it with the close() function, you may want to reopen it. That's why this method exists.

close()

That closes the websocket connection. You should do that at some point to provide a clean exit.

quit()

Not only closes your connection, but quits the Cider client entirely! Like, actually ends the process! No idea why you would have this but here you go!

forceUpdate()

Forces CiderWS to fetch and update the current song and states. This is not instant tho, so use async getSong() and async getStates() or the event-based system if you want to be absolutely sure!

async getSong()

Returns a Promise, which eventually resolves to a Song object of the current or last played song. Useful if you don't want to use the event-based system.

async getStates()

Returns a Promise, which eventually resolves to a States object of the current or last states. Useful if you don't want to use the event-based system.

async getQueue()

Returns a Promise, which eventually resolves to an Object with the following data:

{
  "items": Song[],
  "isAutoplay": boolean,
  "isRestricted": boolean,
  "position": number,
  "nextPlayableIndex": number
}

moveQueue(from, to)

Moves the song at the given index from to the given position to in the queue.

command(com)

Pass a string (preferrably "play", "pause", "next", "previous") to com to control the player.

seek(time, adjust = false)

This function lets you skip to a time in the current song. time is a timestamp in seconds. If adjust is true, time has to be passed in milliseconds.
Note: No idea why I included the adjust parameter, it's in the API so it has to be useful for something.

setVolume(volume)

This function accepts a number between 0 and 1 as volume. It sets the playback volume of the player.

cycleRepeat()

This function lets you cycle through all three repeat modes. To set the repeat mode directly, check async setRepeat().

async setRepeat(mode)

Sets the repeat mode directly to the number given to mode (0 = no repeat, 1 = song, 2 = playlist).

toggleShuffle()

Toggles the shuffle mode on or off. To set the shuffle mode directly, check setShuffle().

setShuffle(enabled)

Sets the shuffle mode to the boolean given to enabled.

setAutoplay(enabled)

Sets the autoplay mode to the boolean given to enabled.

async getLyrics()

Returns the lyrics of the current song as a string if there are any. You get a nice text block with all lines. Note: This could return an empty string when there are no lyrics.

async getLyricsAdvanced()

Returns the lyrics as an array of objects with the following properties:

Property Type Usage
startTime number Marks the time in seconds where this line is played
endTime number Marks the time in seconds where this line stops
line string The actual lyric line. duh
translation string The translation of the line if chosen in the Cider options

Example:

[
  "..."
  {
    "startTime": 44.3,
    "endTime": 46.1,
    "line": "Never gonna give you up",
    "translation": ""
  },
  {
    "startTime": 46.1,
    "endTime": 48.5,
    "line": "Never gonna let you down",
    "translation": ""
  },
  {
    "startTime": 48.5,
    "endTime": 52.3,
    "line": "Never gonna run around and desert you",
    "translation": ""
  },
  "..."
]

Note: This could also return an empty array when there are no lyrics.

async search(query, type = "song", limit = 10)

Searches for a song, artist, album or playlist and returns the results as an array of Objects or Song objects.

playById(id, kind = "song")

Plays a song / album / artist / playlist / whatever by its ID immediately.

playNextById(id, kind = "song")

Puts a Song next in the queue so it will play after the current song ends. If there is no song playing, it will be "loaded" but not played.

enqueueById(id, kind = "song")

Puts a Song at the end of the queue. There is also an alias: playLaterById(). It just doesn't sound good...

async quickPlay(query, type = "song")

Searches for a song, artist, album or playlist and plays the first result immediately.

async playNext(query, type = "song")

Searches for an element and puts next in the queue so it will play after the current song ends. If there is no song playing, it will be "loaded" but not played.

async enqueue(query, type = "song")

Searches for an element and puts it at the end of the queue. There is also an alias: playLater().

Events

Cider bombards every connected websocket with playback data, which CiderWS filters for you.
The three main events are songUpdate, statesUpdate and playbackUpdate and two related to websocket stuff, ready and close. You get some data with them, too!

So far, so simple.
Listen for events with the structure you would expect:

const { CiderWS } = require("./ciderws.js"); // note: npm rollout soon™
const cider = new CiderWS();

cider.on("songUpdate", (data) => {
  // do shit
});

Additionally, I'm also "forwarding" all messages Cider sends, just in case you want the raw data. The type of the message on the websocket is also the event to listen for, for example generic or playbackStateUpdate (I don't know why you would want to do that tho).

Classes

Song

The Song class packs up some nice information about the current playing title.

Property Type Usage
id string The song ID
title string The song name
artist string The song artist
album string The song album
artwork string The song's album art URL
trackNumber number The song's track number on the album
duration number The song duration in seconds
url string The Apple Music URL for the song

States

This class saves the current options and states for the player when defined by the client.

Property Type Usage
isPlaying boolean Is a title playing?
isShuffling boolean Is shuffling enabled?
repeatMode number Current repeat mode (0 = no repeat, 1 = song, 2 = playlist)
volume number How loud is the player (from 0 to 1)?
autoplay boolean Is autoplay enabled?

PlaybackData

This class shows data relevant for the current playback, e.g. elapsed time, remaining time, the timestamp when the song will end, etc.

Property Type Usage
isPlaying boolean Is a title playing?
startTime number The timestamp at which the song started playing
endTime number The timestamp at which the song will end
remainingTime number The remaining time in milliseconds
elapsedTime number The elapsed time in milliseconds
progress number The progress of the song in decimal form (0-1)

Disclaimer

This project is NOT affiliated with Cider in any way shape or form (yet). The project is open source and free to use. For any legal concerns contact me at legal@ryzetech.live.

Thanks ❤

Thank you @SteffTek for providing me with a base to work with, @ArcticSpaceFox for giving me advice and energy to do this, and @FoxInAScarf for listening to my rants.

About

Control your Cider App via Node.js

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published