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.likes and pagination util fn
Browse files Browse the repository at this point in the history
  • Loading branch information
vaaski committed Feb 12, 2021
1 parent 8dcbb2d commit 6d71bff
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 23 deletions.
8 changes: 7 additions & 1 deletion demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ const exampleUserID = Number(process.env.EXAMPLE_USER_ID) || 0
const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""

!(async () => {
console.log("start")
const startTime = Date.now()

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

// const out = await user.likes(exampleUserID)
// const out = await user(exampleUserID)
// const out = await user(exampleUserURL)
// const out = await playlist(examplePlaylistURL)
// const out = await playlist(examplePlaylistID)
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
"extensions": [
"ts"
],
"files": [
"tests/**/*"
],
"require": [
"ts-node/register",
"dotenv/config"
Expand Down
51 changes: 42 additions & 9 deletions src/user.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import type { ClientIDv2, URLorID } from "../types"
import type { Userv2 } from "../types/user"
import type { Userv2, UserLikesv2, Collection } from "../types/user"

import ky from "ky-universal"
import { APIv2, getClientIDv2, scrapeData, ScrapeIDs, urlify } from "./util"
import {
APIv2,
getClientIDv2,
PaginatedResponse,
paginateNext,
scrapeData,
ScrapeIDs,
urlify,
} from "./util"
import resolve from "./resolve"

const byID = async (id: number, client_id: ClientIDv2) => {
// TODO JSDoc

const getByID = async (id: number, client_id: ClientIDv2) => {
const url = urlify(`users/${id}`, APIv2)
const searchParams = { client_id }

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

const byURL = async (url: string) => {
const getByURL = async (url: string) => {
const scraped = await scrapeData(urlify(url))
const userData = scraped.find(({ id }) => id === ScrapeIDs.user)
if (!userData) throw new Error("No user data found.")
Expand All @@ -21,18 +32,40 @@ const byURL = async (url: string) => {
return data as Userv2
}

const user = async (identifier: URLorID, client_id?: ClientIDv2): Promise<any> => {
if (typeof identifier === "string") return await byURL(identifier)
const user = async (identifier: URLorID, client_id?: ClientIDv2): Promise<Userv2> => {
if (typeof identifier === "string") return await getByURL(identifier)

if (typeof identifier === "number") {
if (!client_id) client_id = await getClientIDv2()
return await byID(identifier, client_id)
return await getByID(identifier, client_id)
}

throw new Error("Source must be a string (URL) or a number (ID)")
}

user.id = byID
user.url = byURL
export interface LikesOptions {
limit?: number
client_id?: ClientIDv2
}
user.likes = async (
identifier: URLorID,
{ limit = 50, client_id }: LikesOptions = {}
): Promise<PaginatedResponse<Collection[]>> => {
if (typeof identifier === "string") identifier = (await resolve(identifier)).id
if (!client_id) client_id = await getClientIDv2()

const url = urlify(`users/${identifier}/likes`, APIv2)
const searchParams = { client_id, limit }

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

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

user.id = getByID
user.url = getByURL

export default user
22 changes: 22 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,25 @@ export const getClientIDv2 = async (): Promise<string> => {
/* istanbul ignore next */
throw new Error(`Can't find client_id, please report this to ${issueURL}`)
}

export type PaginatedResponse<K> = {
collection: K
next?: () => Promise<PaginatedResponse<K>>
}
export const paginateNext = <K>(url: string, params: Record<string, string | number> = {}) => {
return async (): Promise<PaginatedResponse<K>> => {
const _url = new URL(url)
const _params = Object.fromEntries(_url.searchParams.entries())
const searchParams = { ..._params, ...params }

const data = (await ky.get(url, { searchParams }).json()) as Record<string, any>

const ret: PaginatedResponse<K> = {
collection: data.collection,
}
/* istanbul ignore next */
if (data.next_href) ret.next = paginateNext(data.next_href, params)

return ret
}
}
29 changes: 25 additions & 4 deletions tests/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { getClientIDv2 } from "../src/util"
const exampleUserURL = process.env.EXAMPLE_USER_URL || ""
const exampleUserID = Number(process.env.EXAMPLE_USER_ID) || 0

let client_id2: string
let client_id: string

test.before(async () => {
client_id2 = await getClientIDv2()
client_id = await getClientIDv2()
})

test("get user using URL", async t => {
Expand All @@ -25,7 +25,7 @@ test("get user using ID", async t => {
})

test("get user using ID and client_id", async t => {
const data = await user(exampleUserID, client_id2)
const data = await user(exampleUserID, client_id)
t.is(data.kind, "user")
t.truthy(data.id)
})
Expand All @@ -40,5 +40,26 @@ test("get user by URL throws when not found", async t => {
})

test("get user by ID throws when not found", async t => {
await t.throwsAsync(user(0, client_id2))
await t.throwsAsync(user(0, client_id))
})

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)
})

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)
})
13 changes: 13 additions & 0 deletions tests/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import test from "ava"
import { user } from "../src"

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

const testArray = ["one", "two", "three"]
const exampleUserID = Number(process.env.EXAMPLE_USER_ID) || 0

test("util function at works as expected", t => {
t.is(at(testArray, 0), "one")
Expand All @@ -18,3 +20,14 @@ test("get client_id for APIv2", async t => {
const id = await getClientIDv2()
t.regex(id, /\w{32}/)
})

test("pagination works as expected", async t => {
const data0 = await user.likes(exampleUserID, { limit: 2 })
const data1 = await data0.next?.()

t.assert(data0.collection.length === 2)
t.truthy(data1)
t.assert(data1?.collection.length === 2)
t.notDeepEqual(data0.collection[0], data1?.collection[0])
t.assert(data0.collection[0].track.id !== data1?.collection[0].track.id)
})
2 changes: 1 addition & 1 deletion types/playlist.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated using https://app.quicktype.io?share=UbYYuHKgg7Y1IQHehR6A
// generated with https://app.quicktype.io?share=UbYYuHKgg7Y1IQHehR6A

export interface Playlistv2 {
artwork_url: null
Expand Down
2 changes: 1 addition & 1 deletion types/resolve.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated using https://app.quicktype.io?share=31kt7pkt3K2AZvf7WbvW
// generated with https://app.quicktype.io?share=31kt7pkt3K2AZvf7WbvW

export type ResolvedKind = "playlist" | "user" | "track"

Expand Down
Loading

0 comments on commit 6d71bff

Please sign in to comment.