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

Commit

Permalink
feat: add ensureMin util fn
Browse files Browse the repository at this point in the history
  • Loading branch information
vaaski committed Feb 15, 2021
1 parent bf15d0e commit 4a88350
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 20 deletions.
6 changes: 4 additions & 2 deletions demo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { playlist, resolve, user } from "./src"
import { playlist, resolve, user, util } from "./src"
import { writeFileSync } from "fs"
import { getClientIDv2 } from "./src/util"
import { join } from "path"
import { TrackElement } from "./types/user"

const byteLength = (input: any) => Buffer.byteLength(JSON.stringify(input) || "", "utf8")
const formatSize = (bytes: number, decimals = 2) => {
Expand Down Expand Up @@ -37,7 +38,8 @@ const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""
// const out2 = await out1.next?.()
// const out = [...out1.collection, ...(out2?.collection || [])]

const out = await user.tracks(exampleUserID, { limit: 50 })
const out = await util.ensureMin(await user.tracks("space-laces", { limit: 10 }), 10)

// const out = await user.likes(exampleUserID)
// const out = await user(exampleUserID)
// const out = await user(exampleUserURL)
Expand Down
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import resolve from "./resolve"
import playlist from "./playlist"
import user from "./user"
import { ensureMin } from "./util"

export default { resolve, playlist, user }
export { resolve, playlist, user }
const util = { ensureMin }

export default { resolve, playlist, user, util }
export { resolve, playlist, user, util }
2 changes: 1 addition & 1 deletion src/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const byURL = async (url: string) => {
/**
* Get a playlist by either URL or ID using the APIv2.
*
* If you use a playlist ID, you can provide a v2 client_id to speed up the process (recommended).
* If you use a playlist ID, You can provide a v2 client_id to save one scrape request (recommended).
* Uses `util.getClientIDv2` to find a client_id if none is provided.
* @param identifier A playlist URL or ID
* @param client_id Optional client_id for APIv2.
Expand Down
26 changes: 17 additions & 9 deletions src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ClientIDv2, PaginatedOptions, URLorID } from "../types"
import type {
Userv2,
UserLikesv2,
UserLikesv2Collection,
UserLikesv2Element,
TrackElement,
UserTracksv2,
} from "../types/user"
Expand Down Expand Up @@ -48,7 +48,7 @@ const getByURL = async (url: string) => {
/**
* Get a user by either URL or ID using the APIv2.
*
* You can provide a v2 client_id to speed up the process (recommended).
* You can provide a v2 client_id to save one scrape request (recommended).
* Uses `util.getClientIDv2` to find a client_id if none is provided.
* @param identifier A user URL or ID
* @param client_id Optional client_id for APIv2.
Expand All @@ -66,19 +66,16 @@ const user = async (identifier: URLorID, client_id?: ClientIDv2): Promise<Userv2

/**
* Get a user's likes by either URL or ID using the APIv2.
* Using an ID is recommended, it saves one resolve request.
* Using an ID is recommended, as it saves one resolve request.
*
* You can provide a v2 client_id to speed up the process (recommended).
* You can provide a v2 client_id to save one scrape request (recommended).
* Uses `util.getClientIDv2` to find a client_id if none is provided.
* @param identifier A user URL or ID
* @param options Optional options object.
* @param options.limit Limit the amount of tracks returned. Defaults to 50.
* @param options.client_id client_id for APIv2.
*/
user.likes = async (
identifier: URLorID,
{ limit = 50, client_id }: PaginatedOptions = {}
): Promise<PaginatedResponse<UserLikesv2Collection[]>> => {
user.likes = async (identifier: URLorID, { limit = 50, client_id }: PaginatedOptions = {}) => {
if (typeof identifier === "string") identifier = (await resolve(identifier)).id
if (!client_id) client_id = await getClientIDv2()

Expand All @@ -87,12 +84,23 @@ user.likes = async (

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

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

/**
* Get a user's tracks by either URL or ID using the APIv2.
* Using an ID is recommended, as it saves one resolve request.
*
* You can provide a v2 client_id to save one scrape request (recommended).
* Uses `util.getClientIDv2` to find a client_id if none is provided.
* @param identifier A user URL or ID
* @param options Optional options object.
* @param options.limit Limit the amount of tracks returned. Defaults to 50.
* @param options.client_id client_id for APIv2.
*/
user.tracks = async (
identifier: URLorID,
{ limit = 50, client_id }: PaginatedOptions = {}
Expand Down
24 changes: 22 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,25 @@ export const paginateNext = <K>(url: string, params: Record<string, string | num
}
}

// 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
/**
* Automatically uses paginateNext to fetch the minimum amount of items requested.
* This is for users like "space-laces" for which the API returns no tracks for
* about the first 5 requests. Might return more than the specified minimum.
* @param paginated a `PaginatedResponse` as returned by `paginateNext`
* @param min the minimum amount of items you want to receive in the collection
* @example
* await util.ensureMin(await user.tracks("space-laces", { limit: 20 }), 10)
* // returns an object with a collection property that has a minimum of 10 items
*/
export const ensureMin = async <K extends any[]>(
paginated: PaginatedResponse<K>,
min: number
): Promise<PaginatedResponse<K>> => {
if (paginated.collection.length < min && paginated.next) {
const next = await paginated.next()
const collection = [...paginated.collection, ...next.collection] as K
return ensureMin({ ...next, collection }, min)
}

return paginated
}
11 changes: 9 additions & 2 deletions tests/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from "ava"
import { user } from "../src"
import { user, util } from "../src"

import { at, getClientIDv2, paginateNext } from "../src/util"

Expand Down Expand Up @@ -32,8 +32,15 @@ test("pagination works as expected", async t => {
t.assert(data0.collection[0].track.id !== data1?.collection[0].track.id)
})

// TODO write a better test than this
test("pagination without explicit params", t => {
const paginated = paginateNext("")
t.assert(typeof paginated === "function")
})

test("ensureMin always returns the correct amount of tracks", async t => {
// space-laces seems to especially problematic, often requiring about 5
// requests until something is actually returned
const limit = 10
const out = await util.ensureMin(await user.tracks("space-laces", { limit }), limit)
t.assert(out.collection.length >= limit)
})
4 changes: 2 additions & 2 deletions types/user.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,12 @@ export enum LastName {
}

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

export interface UserLikesv2Collection {
export interface UserLikesv2Element {
created_at: Date
kind: PurpleKind
track: TrackElement
Expand Down

0 comments on commit 4a88350

Please sign in to comment.