Skip to content
This repository has been archived by the owner on Mar 26, 2024. It is now read-only.

Commit

Permalink
feat: add user.tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
vaaski committed Feb 15, 2021
1 parent ceff85b commit bf15d0e
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 41 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"Likesv",
"Playlistv",
"Tracksv",
"Userv",
"lcov",
"npmrc",
Expand Down
15 changes: 12 additions & 3 deletions demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""
console.log("start")
const startTime = Date.now()

const out1 = await user.likes(exampleUserURL, { limit: 5 })
const out2 = await out1.next?.()
const out = [...out1.collection, ...(out2?.collection || [])]
// const out1 = await user.tracks(exampleUserID, { limit: 5 })
// const out2 = await out1.next?.()
// const out = [...out1.collection, ...(out2?.collection || [])]

// const out1 = await user.likes(exampleUserURL, { limit: 5 })
// const out2 = await out1.next?.()
// const out = [...out1.collection, ...(out2?.collection || [])]

const out = await user.tracks(exampleUserID, { limit: 50 })
// const out = await user.likes(exampleUserID)
// const out = await user(exampleUserID)
// const out = await user(exampleUserURL)
Expand All @@ -46,6 +51,10 @@ const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""
// const out = await resolve.browser(exampleTrackURL, client_id)
// const out = await resolve.browser(examplePlaylistURL, client_id)

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if ("collection" in out) console.log(`got ${out.collection.length} items in collection`)

console.log(
`fetched ${formatSize(byteLength(out))} of data in ${Date.now() - startTime}ms\n` +
`see ${join(__dirname, "demo_out.json")}`
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"scripts": {
"dev": "nodemon",
"demo": "ts-node -r dotenv/config ./demo.ts",
"build": "rimraf lib && tsc",
"test": "nyc --reporter=lcov ava",
"commit": "git cz -S",
Expand Down
38 changes: 29 additions & 9 deletions src/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { ClientIDv2, URLorID } from "../types"
import type { Userv2, UserLikesv2, Collection } from "../types/user"
import type { ClientIDv2, PaginatedOptions, URLorID } from "../types"
import type {
Userv2,
UserLikesv2,
UserLikesv2Collection,
TrackElement,
UserTracksv2,
} from "../types/user"

import ky from "ky-universal"
import {
Expand Down Expand Up @@ -58,12 +64,9 @@ const user = async (identifier: URLorID, client_id?: ClientIDv2): Promise<Userv2
throw new Error("Source must be a string (URL) or a number (ID)")
}

export interface LikesOptions {
limit?: number
client_id?: ClientIDv2
}
/**
* Get a user's likes by either URL or ID using the APIv2.
* Using an ID is recommended, it saves one resolve request.
*
* You can provide a v2 client_id to speed up the process (recommended).
* Uses `util.getClientIDv2` to find a client_id if none is provided.
Expand All @@ -74,8 +77,8 @@ export interface LikesOptions {
*/
user.likes = async (
identifier: URLorID,
{ limit = 50, client_id }: LikesOptions = {}
): Promise<PaginatedResponse<Collection[]>> => {
{ limit = 50, client_id }: PaginatedOptions = {}
): Promise<PaginatedResponse<UserLikesv2Collection[]>> => {
if (typeof identifier === "string") identifier = (await resolve(identifier)).id
if (!client_id) client_id = await getClientIDv2()

Expand All @@ -84,7 +87,24 @@ user.likes = async (

const data = (await ky(url, { searchParams }).json()) as UserLikesv2

const ret: PaginatedResponse<Collection[]> = data
const ret: PaginatedResponse<UserLikesv2Collection[]> = data
/* istanbul ignore next */
if (data.next_href) ret.next = paginateNext(data.next_href, searchParams)
return ret
}

user.tracks = async (
identifier: URLorID,
{ limit = 50, client_id }: PaginatedOptions = {}
) => {
if (typeof identifier === "string") identifier = (await resolve(identifier)).id
if (!client_id) client_id = await getClientIDv2()

const searchParams = { client_id, limit }
const url = urlify(`users/${identifier}/tracks`, APIv2)
const data = (await ky(url, { searchParams }).json()) as UserTracksv2

const ret: PaginatedResponse<TrackElement[]> = data
/* istanbul ignore next */
if (data.next_href) ret.next = paginateNext(data.next_href, searchParams)
return ret
Expand Down
3 changes: 3 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ export const paginateNext = <K>(url: string, params: Record<string, string | num
return ret
}
}

// TODO build something like ensureMin() which would tale a PaginatedResponse and
// return the specified amount of items regardless of what the weird soundcloud API does
27 changes: 24 additions & 3 deletions tests/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,12 @@ test("get user by ID throws when not found", async t => {

test("get users likes using URL", async t => {
const data = await user.likes(exampleUserURL, { limit: 2 })
t.assert(data.collection.length === 2)
t.truthy(data)
t.truthy(data.collection[0].track.id)
})

test("get users likes using ID", async t => {
const data = await user.likes(exampleUserID, { limit: 2 })
t.assert(data.collection.length === 2)
t.truthy(data)
t.truthy(data.collection[0].track.id)
})
Expand All @@ -65,7 +63,30 @@ test("get users likes using ID without explicit limit", async t => {

test("get users likes using ID and client_id", async t => {
const data = await user.likes(exampleUserID, { limit: 2, client_id })
t.assert(data.collection.length === 2)
t.truthy(data)
t.truthy(data.collection[0].track.id)
})

test("get users tracks using URL", async t => {
const data = await user.tracks(exampleUserURL, { limit: 2 })
t.truthy(data)
t.truthy(data.collection[0].id)
})

test("get users tracks using ID", async t => {
const data = await user.tracks(exampleUserID, { limit: 2 })
t.truthy(data)
t.truthy(data.collection[0].id)
})

test("get users tracks using ID without explicit limit", async t => {
const data = await user.tracks(exampleUserID)
t.truthy(data)
t.truthy(data.collection[0].id)
})

test("get users tracks using ID and client_id", async t => {
const data = await user.tracks(exampleUserID, { limit: 2, client_id })
t.truthy(data)
t.truthy(data.collection[0].id)
})
4 changes: 4 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export type URLorID = string | number
export type ClientIDv1 = string
export type ClientIDv2 = string
export interface PaginatedOptions {
limit?: number
client_id?: string
}
113 changes: 87 additions & 26 deletions types/user.d.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
// generated with https://app.quicktype.io?share=hs82nzuA9jUPyaliNYv9
// generated with https://app.quicktype.io?share=IAHWnozVhjhjEkl8jObh

export interface UserLikesv2 {
collection: Collection[]
export interface UserTracksv2 {
collection: TrackElement[]
next_href: string
query_urn: null
}

export interface Collection {
created_at: Date
kind: CollectionKind
track: Track
}

export enum CollectionKind {
Like = "like",
}

export interface Track {
export interface TrackElement {
artwork_url: null | string
caption: null | string
commentable: boolean
comment_count: number
created_at: Date
description: string
description: null | string
downloadable: boolean
download_count: number
duration: number
Expand Down Expand Up @@ -106,7 +96,9 @@ export enum Protocol {
}

export enum Preset {
Mp30_0 = "mp3_0_0",
Mp30_1 = "mp3_0_1",
Mp3Standard = "mp3_standard",
Opus0_0 = "opus_0_0",
}

Expand All @@ -128,17 +120,29 @@ export enum Policy {
export interface PublisherMetadata {
id: number
urn: string
contains_music?: boolean
artist?: string
album_title?: string
contains_music: boolean
isrc?: string
publisher?: string
writer_composer?: string
release_title?: string
explicit?: boolean
upc_or_ean?: string
p_line?: string
p_line_for_display?: string
writer_composer?: string
release_title?: string
isrc?: string
upc_or_ean?: string
c_line?: CLine
c_line_for_display?: CLineForDisplay
}

export enum CLine {
The2017DivisionRecordings = "2017 Division Recordings",
The2017VisionRecordings = "2017 Vision Recordings",
}

export enum CLineForDisplay {
The2017DivisionRecordings = "© 2017 Division Recordings",
The2017VisionRecordings = "© 2017 Vision Recordings",
}

export enum Sharing {
Expand All @@ -155,20 +159,20 @@ export enum TrackFormat {

export interface User {
avatar_url: string
first_name: string
full_name: string
first_name: FirstName
full_name: FullName
id: number
kind: UserKind
last_modified: Date
last_name: string
last_name: LastName
permalink: string
permalink_url: string
uri: string
urn: string
username: string
verified: boolean
city: null | string
country_code: null | string
country_code: CountryCode | null
badges: Badges
}

Expand All @@ -178,15 +182,72 @@ export interface Badges {
verified: boolean
}

export enum CountryCode {
Be = "BE",
CA = "CA",
GB = "GB",
Hn = "HN",
Jp = "JP",
Us = "US",
}

export enum FirstName {
Boneidol = "BONEIDOL",
Callister = "Callister",
Christian = "Christian",
Empty = "",
Fkof = "FKOF",
Jack = "Jack",
Richard = "Richard",
SouthFlorida = "South Florida",
}

export enum FullName {
Boneidol = "BONEIDOL",
CallisterJames = "Callister James",
ChristianFial = "Christian Fial",
Empty = "",
FKOFRecords = "FKOF Records",
JackHaigh = "Jack Haigh",
RichardLibor = "Richard Libor",
SouthFloridaBrowardCounty = "South Florida Broward County",
}

export enum UserKind {
User = "user",
}

export enum LastName {
BrowardCounty = "Broward County",
Empty = "",
Fial = "Fial",
Haigh = "Haigh",
James = "James",
Libor = "Libor",
Records = "Records",
}

export interface UserLikesv2 {
collection: UserLikesv2Collection[]
next_href: string
query_urn: null
}

export interface UserLikesv2Collection {
created_at: Date
kind: PurpleKind
track: TrackElement
}

export enum PurpleKind {
Like = "like",
}

export interface Userv2 {
avatar_url: string
city: string
comments_count: number
country_code: null | string
country_code: CountryCode | null
created_at: Date
creator_subscriptions: CreatorSubscription[]
creator_subscription: CreatorSubscription
Expand All @@ -199,7 +260,7 @@ export interface Userv2 {
id: number
kind: UserKind
last_modified: Date
last_name: string
last_name: LastName
likes_count: number
playlist_likes_count: number
permalink: string
Expand Down

0 comments on commit bf15d0e

Please sign in to comment.