mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
include some missing methods
This commit is contained in:
parent
b7d057958b
commit
b2c8ad9f95
@ -3,13 +3,41 @@ import axios from "axios"
|
|||||||
const TIDAL_CLIENT_ID = process.env.TIDAL_CLIENT_ID
|
const TIDAL_CLIENT_ID = process.env.TIDAL_CLIENT_ID
|
||||||
const TIDAL_CLIENT_SECRET = process.env.TIDAL_CLIENT_SECRET
|
const TIDAL_CLIENT_SECRET = process.env.TIDAL_CLIENT_SECRET
|
||||||
|
|
||||||
|
function tranformTrackData(data = {}) {
|
||||||
|
// TODO: Support Track remixes & versions
|
||||||
|
data._id = data.id
|
||||||
|
|
||||||
|
const coverUID = data.album.cover.replace(/-/g, "/")
|
||||||
|
|
||||||
|
data.cover = `https://resources.tidal.com/images/${coverUID}/1080x1080.jpg`
|
||||||
|
|
||||||
|
data.artist = data.artists.map(artist => artist.name).join(", ")
|
||||||
|
|
||||||
|
data.metadata = {
|
||||||
|
title: data.title,
|
||||||
|
artists: data.artists.map(artist => artist.name).join(", "),
|
||||||
|
artist: data.artists.map(artist => artist.name).join(", "),
|
||||||
|
album: data.album.title,
|
||||||
|
duration: data.duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
data.service = "tidal"
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
export default class TidalAPI {
|
export default class TidalAPI {
|
||||||
|
static API_V1 = "https://api.tidal.com/v1"
|
||||||
|
static API_V2 = "https://api.tidal.com/v2"
|
||||||
|
|
||||||
|
static API_USERS = "https://api.tidal.com/v1/users"
|
||||||
|
|
||||||
static async checkAuthStatus(device_code) {
|
static async checkAuthStatus(device_code) {
|
||||||
const data = {
|
const data = {
|
||||||
"client_id": TIDAL_CLIENT_ID,
|
client_id: TIDAL_CLIENT_ID,
|
||||||
"device_code": device_code,
|
device_code: device_code,
|
||||||
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||||
"scope": "r_usr+w_usr+w_sub"
|
scope: "r_usr+w_usr+w_sub",
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
@ -18,9 +46,9 @@ export default class TidalAPI {
|
|||||||
params: data,
|
params: data,
|
||||||
auth: {
|
auth: {
|
||||||
username: TIDAL_CLIENT_ID,
|
username: TIDAL_CLIENT_ID,
|
||||||
password: TIDAL_CLIENT_SECRET
|
password: TIDAL_CLIENT_SECRET,
|
||||||
}
|
},
|
||||||
}).catch((err) => {
|
}).catch(err => {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -34,47 +62,38 @@ export default class TidalAPI {
|
|||||||
static async getAuthUrl() {
|
static async getAuthUrl() {
|
||||||
let data = {
|
let data = {
|
||||||
client_id: TIDAL_CLIENT_ID,
|
client_id: TIDAL_CLIENT_ID,
|
||||||
scope: "r_usr+w_usr+w_sub"
|
scope: "r_usr+w_usr+w_sub",
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "https://auth.tidal.com/v1/oauth2/device_authorization",
|
url: "https://auth.tidal.com/v1/oauth2/device_authorization",
|
||||||
params: data
|
params: data,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: "https://" + response.data.verificationUri + "/" + response.data.userCode,
|
url: "https://" + response.data.verificationUri + "/" + response.data.userCode,
|
||||||
device_code: response.data.deviceCode,
|
device_code: response.data.deviceCode,
|
||||||
expires_in: response.data.expiresIn
|
expires_in: response.data.expiresIn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserData({
|
static async getUserData({ access_token, user_id, country }) {
|
||||||
access_token,
|
|
||||||
user_id,
|
|
||||||
country
|
|
||||||
}) {
|
|
||||||
const url = `https://api.tidal.com/v1/users/${user_id}?countryCode=${country}`
|
const url = `https://api.tidal.com/v1/users/${user_id}?countryCode=${country}`
|
||||||
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url,
|
url,
|
||||||
headers: {
|
headers: {
|
||||||
"Origin": "http://listen.tidal.com",
|
Origin: "http://listen.tidal.com",
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getTrackPlaybackUrl({
|
static async getTrackPlaybackUrl({ track_id, quality, access_token, country }) {
|
||||||
track_id,
|
|
||||||
quality,
|
|
||||||
access_token,
|
|
||||||
country,
|
|
||||||
}) {
|
|
||||||
let params = {
|
let params = {
|
||||||
countryCode: country ?? "US",
|
countryCode: country ?? "US",
|
||||||
audioquality: quality ?? "LOSSLESS",
|
audioquality: quality ?? "LOSSLESS",
|
||||||
@ -88,8 +107,8 @@ export default class TidalAPI {
|
|||||||
params: params,
|
params: params,
|
||||||
headers: {
|
headers: {
|
||||||
Origin: "http://listen.tidal.com",
|
Origin: "http://listen.tidal.com",
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let decodedManifest = JSON.parse(global.b64Decode(response.data.manifest))
|
let decodedManifest = JSON.parse(global.b64Decode(response.data.manifest))
|
||||||
@ -103,37 +122,28 @@ export default class TidalAPI {
|
|||||||
audioQuality: response.data.audioQuality,
|
audioQuality: response.data.audioQuality,
|
||||||
bitDepth: response.data.bitDepth,
|
bitDepth: response.data.bitDepth,
|
||||||
bitRate: response.data.bitRate,
|
bitRate: response.data.bitRate,
|
||||||
mimeType: response.data.manifestMimeType
|
mimeType: response.data.manifestMimeType,
|
||||||
},
|
},
|
||||||
...decodedManifest
|
...decodedManifest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static async getTrackMetadata({
|
static async getTrackMetadata({ track_id, access_token, country }) {
|
||||||
track_id,
|
|
||||||
access_token,
|
|
||||||
country,
|
|
||||||
}) {
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `https://api.tidal.com/v1/tracks/${track_id}`,
|
url: `https://api.tidal.com/v1/tracks/${track_id}`,
|
||||||
params: {
|
params: {
|
||||||
"countryCode": country
|
countryCode: country,
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
"Origin": "http://listen.tidal.com",
|
Origin: "http://listen.tidal.com",
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getTrackManifest({
|
static async getTrackManifest({ track_id, quality, access_token, country }) {
|
||||||
track_id,
|
|
||||||
quality,
|
|
||||||
access_token,
|
|
||||||
country,
|
|
||||||
}) {
|
|
||||||
const playback = await TidalAPI.getTrackPlaybackUrl({
|
const playback = await TidalAPI.getTrackPlaybackUrl({
|
||||||
track_id,
|
track_id,
|
||||||
quality,
|
quality,
|
||||||
@ -149,14 +159,11 @@ export default class TidalAPI {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
playback,
|
playback,
|
||||||
metadata
|
metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async search({
|
static async search({ query, type = "all" }) {
|
||||||
query,
|
|
||||||
type = "all"
|
|
||||||
}) {
|
|
||||||
let url = `https://api.tidal.com/v1/search`
|
let url = `https://api.tidal.com/v1/search`
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -182,81 +189,61 @@ export default class TidalAPI {
|
|||||||
url: url,
|
url: url,
|
||||||
params: {
|
params: {
|
||||||
query: query,
|
query: query,
|
||||||
countryCode: "AZ"
|
countryCode: "AZ",
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
"Origin": "http://listen.tidal.com",
|
Origin: "http://listen.tidal.com",
|
||||||
"x-tidal-token": TIDAL_CLIENT_ID
|
"x-tidal-token": TIDAL_CLIENT_ID,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.data.tracks.items.map((item) => {
|
return response.data.tracks.items.map(item => {
|
||||||
item._id = item.id
|
item = tranformTrackData(item)
|
||||||
|
|
||||||
const coverUID = item.album.cover.replace(/-/g, "/")
|
|
||||||
|
|
||||||
item.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg`
|
|
||||||
|
|
||||||
item.artist = item.artists.map(artist => artist.name).join(", ")
|
|
||||||
|
|
||||||
item.metadata = {
|
|
||||||
title: item.title,
|
|
||||||
artists: item.artists.map(artist => artist.name).join(", "),
|
|
||||||
artist: item.artists.map(artist => artist.name).join(", "),
|
|
||||||
album: item.album.title,
|
|
||||||
duration: item.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
item.service = "tidal"
|
|
||||||
|
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves favorite tracks for a user.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for retrieving favorite tracks.
|
||||||
|
* @param {number} options.user_id - The user ID.
|
||||||
|
* @param {string} options.country - The country code.
|
||||||
|
* @param {string} options.access_token - The access token.
|
||||||
|
* @param {number} [options.limit=100] - The maximum number of tracks to retrieve.
|
||||||
|
* @param {number} [options.offset=0] - The offset for pagination.
|
||||||
|
* @return {Object} The response object containing the total length and tracks.
|
||||||
|
*/
|
||||||
static async getFavoriteTracks({
|
static async getFavoriteTracks({
|
||||||
user_id,
|
user_id,
|
||||||
country,
|
|
||||||
access_token,
|
access_token,
|
||||||
|
country,
|
||||||
limit = 100,
|
limit = 100,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
|
order = "DATE",
|
||||||
|
orderDirection = "DESC",
|
||||||
}) {
|
}) {
|
||||||
const url = `https://api.tidal.com/v1/users/${user_id}/favorites/tracks?countryCode=${country}`
|
|
||||||
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
|
url: `${TidalAPI.API_USERS}/${user_id}/favorites/tracks`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url,
|
|
||||||
headers: {
|
headers: {
|
||||||
"Origin": "http://listen.tidal.com",
|
Origin: "http://listen.tidal.com",
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
order: "DATE",
|
countryCode: country,
|
||||||
orderDirection: "DESC",
|
order,
|
||||||
limit: limit,
|
orderDirection,
|
||||||
offset: offset,
|
limit,
|
||||||
}
|
offset,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
response.data.items = response.data.items.map((item) => {
|
response.data.items = response.data.items.map(item => {
|
||||||
// get js time
|
item.item = tranformTrackData(item.item)
|
||||||
|
|
||||||
item.item.liked_at = new Date(item.created).getTime()
|
item.item.liked_at = new Date(item.created).getTime()
|
||||||
item.item.service = "tidal"
|
|
||||||
|
|
||||||
item.item._id = item.item.id
|
|
||||||
|
|
||||||
const coverUID = item.item.album.cover.replace(/-/g, "/")
|
|
||||||
|
|
||||||
item.item.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg`
|
|
||||||
|
|
||||||
item.item.artist = item.item.artists.map(artist => artist.name).join(", ")
|
|
||||||
|
|
||||||
item.item.metadata = {
|
|
||||||
title: item.item.title,
|
|
||||||
artists: item.item.artists.map(artist => artist.name).join(", "),
|
|
||||||
artist: item.item.artists.map(artist => artist.name).join(", "),
|
|
||||||
album: item.item.album.title,
|
|
||||||
duration: item.item.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
item.item.liked = true
|
item.item.liked = true
|
||||||
item.item._computed = true
|
item.item._computed = true
|
||||||
|
|
||||||
@ -265,7 +252,182 @@ export default class TidalAPI {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
total_length: response.data.totalNumberOfItems,
|
total_length: response.data.totalNumberOfItems,
|
||||||
tracks: response.data.items
|
tracks: response.data.items,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves self favorite playlists based on specified parameters.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options object.
|
||||||
|
* @param {string} options.country - The country code.
|
||||||
|
* @param {string} options.access_token - The access token for authentication.
|
||||||
|
* @param {number} [options.limit=100] - The maximum number of playlists to retrieve.
|
||||||
|
* @param {number} [options.offset=0] - The offset for pagination.
|
||||||
|
* @param {string} [options.order="DATE"] - The field to order the playlists by.
|
||||||
|
* @param {string} [options.orderDirection="DESC"] - The direction to order the playlists in.
|
||||||
|
* @return {Object} - An object containing the total length and items of the playlists.
|
||||||
|
*/
|
||||||
|
static async getFavoritePlaylists({
|
||||||
|
country,
|
||||||
|
access_token,
|
||||||
|
limit = 100,
|
||||||
|
offset = 0,
|
||||||
|
order = "DATE",
|
||||||
|
orderDirection = "DESC",
|
||||||
|
}) {
|
||||||
|
const params = {
|
||||||
|
folderId: "root",
|
||||||
|
deviceType: "BROWSER",
|
||||||
|
countryCode: country,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
order,
|
||||||
|
orderDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await axios({
|
||||||
|
url: `${TidalAPI.API_V2}/my-collection/playlists/folders`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Origin: "http://listen.tidal.com",
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
Server: "envoy",
|
||||||
|
},
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.items = response.data.items.map(item => {
|
||||||
|
item.data._id = item.data.uuid
|
||||||
|
item.data.addedAt = item.addedAt
|
||||||
|
item.data.created_at = item.addedAt
|
||||||
|
|
||||||
|
item.data.service = "tidal"
|
||||||
|
|
||||||
|
const coverUID = item.data.squareImage.replace(/-/g, "/")
|
||||||
|
item.data.cover = `https://resources.tidal.com/images/${coverUID}/1080x1080.jpg`
|
||||||
|
|
||||||
|
return item.data
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
total_length: response.data.totalNumberOfItems,
|
||||||
|
items: response.data.items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves playlist items based on the provided parameters.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for retrieving playlist items.
|
||||||
|
* @param {string} options.uuid - The UUID of the playlist.
|
||||||
|
* @param {number} options.limit - The maximum number of items to retrieve.
|
||||||
|
* @param {number} options.offset - The offset of items to start retrieving from.
|
||||||
|
* @param {string} options.country - The country code for retrieving items.
|
||||||
|
* @param {string} options.access_token - The access token for authentication.
|
||||||
|
* @return {Object} An object containing the total length and items of the playlist.
|
||||||
|
*/
|
||||||
|
static async getPlaylistItems({
|
||||||
|
uuid,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
|
||||||
|
country,
|
||||||
|
access_token,
|
||||||
|
}) {
|
||||||
|
const params = {
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
countryCode: country,
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await axios({
|
||||||
|
url: `${TidalAPI.API_V1}/playlists/${uuid}/items`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Origin: "http://listen.tidal.com",
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
Server: "envoy",
|
||||||
|
},
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.items = response.data.items.map((item) => {
|
||||||
|
item = tranformTrackData(item.item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
total_length: response.data.totalNumberOfItems,
|
||||||
|
list: response.data.items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves playlist data from the Tidal API.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for retrieving the playlist data.
|
||||||
|
* @param {string} options.uuid - The UUID of the playlist.
|
||||||
|
* @param {string} options.access_token - The access token for authentication.
|
||||||
|
* @param {string} options.country - The country code for the playlist data.
|
||||||
|
* @param {boolean} [options.resolve_items=false] - Whether to resolve the playlist items.
|
||||||
|
* @param {number} [options.limit] - The maximum number of items to retrieve.
|
||||||
|
* @param {number} [options.offset] - The offset for pagination.
|
||||||
|
* @return {Object} The playlist data retrieved from the Tidal API.
|
||||||
|
*/
|
||||||
|
static async getPlaylistData({
|
||||||
|
uuid,
|
||||||
|
|
||||||
|
access_token,
|
||||||
|
country,
|
||||||
|
|
||||||
|
resolve_items = false,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
}) {
|
||||||
|
const params = {
|
||||||
|
countryCode: country,
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await axios({
|
||||||
|
url: `${TidalAPI.API_V1}/playlists/${uuid}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Origin: "http://listen.tidal.com",
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
Server: "envoy",
|
||||||
|
},
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
|
||||||
|
const coverUID = response.data.squareImage.replace(/-/g, "/")
|
||||||
|
response.data.cover = `https://resources.tidal.com/images/${coverUID}/1080x1080.jpg`
|
||||||
|
|
||||||
|
response.data.service = "tidal"
|
||||||
|
|
||||||
|
if (resolve_items) {
|
||||||
|
response.data.list = await TidalAPI.getPlaylistItems({
|
||||||
|
uuid,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
access_token,
|
||||||
|
country,
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.total_length = response.data.list.total_length
|
||||||
|
response.data.list = response.data.list.list
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
static async toggleTrackLike(track_id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static async togglePlaylistLike(playlist_id) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user