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

Commit

Permalink
feat: add track.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
vaaski committed Feb 22, 2021
1 parent 5f47ef9 commit 1d7d166
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 7 deletions.
7 changes: 5 additions & 2 deletions demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { playlist, resolve, user, util, search } from "./src"
import { playlist, resolve, user, util, search, track } from "./src"
import { writeFileSync } from "fs"
import { getClientIDv2 } from "./src/util"
import { join } from "path"
Expand All @@ -25,6 +25,7 @@ const examplePlaylistID = Number(process.env.EXAMPLE_PLAYLIST_ID) || 0
const exampleUserURL = process.env.EXAMPLE_USER_URL || ""
const exampleUserID = Number(process.env.EXAMPLE_USER_ID) || 0
const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""
const exampleTrackID = Number(process.env.EXAMPLE_TRACK_ID) || 0
const exampleSearchTerm = "noisia"

!(async () => {
Expand All @@ -48,7 +49,9 @@ const exampleSearchTerm = "noisia"

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

const out = await search(exampleSearchTerm, { limit: 2 })
const out = await track(exampleTrackID)
// const out = await track(exampleTrackURL)
// const out = await search(exampleSearchTerm, { limit: 2 })
// const out = await search.users(exampleSearchTerm)
// const out = await search.albums(exampleSearchTerm)
// const out = await search.playlists(exampleSearchTerm)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"demo": "ts-node -r dotenv/config ./demo.ts",
"build": "rimraf lib && tsc",
"test": "nyc ava",
"coverage": "live-server coverage/lcov-report",
"coverage": "live-server --port=8069 coverage/lcov-report",
"commit": "git cz -S",
"prepare": "npm run build",
"prepublishOnly": "pinst --disable && npm test",
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import resolve from "./resolve"
import playlist from "./playlist"
import user from "./user"
import search from "./search"
import track from "./track"
import { ensureMin, getClientIDv2 } from "./util"

const util = { ensureMin, getClientIDv2 }

export default { resolve, playlist, user, util, search }
export { resolve, playlist, user, util, search }
export default { resolve, playlist, user, util, search, track }
export { resolve, playlist, user, util, search, track }
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 save one scrape request (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
52 changes: 52 additions & 0 deletions src/track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Track } from "../types/track"
import type { ClientIDv2, URLorID } from "../types"

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

/**
* Get a track by ID using the APIv2
* @param id A track ID
* @param client_id client_id for APIv2
*/
const getByID = async (id: number, client_id: ClientIDv2): Promise<Track> => {
const url = urlify(`tracks/${id}`, APIv2)
const searchParams = { client_id }

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

/**
* Get a track by URL using the APIv2
* @param url A track URL
*/
const getByURL = async (url: string) => {
const scraped = await scrapeData(urlify(url))
const trackData = scraped.find(({ id }) => id === ScrapeIDs.trackWithTranscodings)
if (!trackData) throw new Error("No track data found.")

const [data] = trackData.data
return data as Track
}

/**
* Get a track by either URL or ID using the APIv2.
*
* If you use a track 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 track URL or ID
* @param client_id Optional client_id for APIv2.
*/
const track = async (identifier: URLorID, client_id?: ClientIDv2): Promise<Track> => {
if (typeof identifier === "string") return await getByURL(identifier)

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

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

export default track
2 changes: 1 addition & 1 deletion src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,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 save one scrape request (recommended).
* If you use a user 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 user URL or ID
* @param client_id Optional client_id for APIv2.
Expand Down
1 change: 1 addition & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const scrapeData = async (url: string): Promise<ScrapedData> => {
export const ScrapeIDs = {
user: 30,
playlist: 45,
trackWithTranscodings: 18,
}

const scriptReg = /<script(?: crossorigin)? src="(https:\/\/a-v2\.sndcdn\.com\/assets\/.+\.js)"/gm
Expand Down
43 changes: 43 additions & 0 deletions tests/track.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import test from "ava"

import { track, util } from "../src"

const exampleTrackURL = process.env.EXAMPLE_TRACK_URL || ""
const exampleTrackID = Number(process.env.EXAMPLE_TRACK_ID) || 0

let client_id2: string

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

test("get track using URL", async t => {
const data = await track(exampleTrackURL)
t.is(data.kind, "track")
t.truthy(data.id)
})

test("get track using ID", async t => {
const data = await track(exampleTrackID)
t.is(data.kind, "track")
t.truthy(data.id)
})

test("get track using ID and client_id", async t => {
const data = await track(exampleTrackID, client_id2)
t.is(data.kind, "track")
t.truthy(data.id)
})

test("get track throws using wrong input", async t => {
// @ts-expect-error intentionally wrong input
await t.throwsAsync(track([]))
})

test("get track by URL throws when not found", async t => {
await t.throwsAsync(track(""))
})

test("get track by ID throws when not found", async t => {
await t.throwsAsync(track(0, client_id2))
})
159 changes: 159 additions & 0 deletions types/track.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// generated with https://app.quicktype.io?share=9Fd3zZTCel75c1MlXFjU

export interface Track {
artwork_url: null | string
caption: null
commentable: boolean
comment_count: number
created_at: Date
description: null | string
downloadable: boolean
download_count: number
duration: number
full_duration: number
embeddable_by: string
genre: string
has_downloads_left: boolean
id: number
kind: string
label_name: null | string
last_modified: Date
license: string
likes_count: number
permalink: string
permalink_url: string
playback_count: number
public: boolean
publisher_metadata: PublisherMetadata | null
purchase_title: null
purchase_url: null | string
release_date: Date | null
reposts_count: number
secret_token: null
sharing: string
state: string
streamable: boolean
tag_list: string
title: string
track_format: string
uri: string
urn: string
user_id: number
visuals: null
waveform_url: string
display_date: Date
media: Media
monetization_model: string
policy: string
user: User
}

export interface Media {
transcodings: Transcoding[]
}

export interface Transcoding {
url: string
preset: string
duration: number
snipped: boolean
format: Format
quality: Quality
}

export interface Format {
protocol: Protocol
mime_type: MIMEType
}

export enum MIMEType {
AudioMPEG = "audio/mpeg",
AudioOggCodecsOpus = 'audio/ogg; codecs="opus"',
}

export enum Protocol {
HLS = "hls",
Progressive = "progressive",
}

export enum Quality {
Sq = "sq",
}

export interface PublisherMetadata {
id: number
urn: string
contains_music: boolean
artist?: string
publisher?: string
upc_or_ean?: string
isrc?: string
writer_composer?: string
album_title?: string
explicit?: boolean
p_line?: string
p_line_for_display?: string
c_line?: string
c_line_for_display?: string
release_title?: string
}

export interface User {
avatar_url: string
first_name: string
followers_count: number
full_name: string
id: number
kind: string
last_modified: Date
last_name: string
permalink: string
permalink_url: string
uri: string
urn: string
username: string
verified: boolean
city: string
country_code: null
badges: Badges
comments_count?: number
created_at?: Date
creator_subscriptions?: CreatorSubscription[]
creator_subscription?: CreatorSubscription
description?: string
followings_count?: number
groups_count?: number
likes_count?: number
playlist_likes_count?: number
playlist_count?: number
reposts_count?: null
track_count?: number
visuals?: Visuals
}

export interface Badges {
pro: boolean
pro_unlimited: boolean
verified: boolean
}

export interface CreatorSubscription {
product: Product
}

export interface Product {
id: string
}

export interface Visuals {
urn: string
enabled: boolean
visuals: Visual[]
tracking: null
}

export interface Visual {
urn: string
entry_time: number
visual_url: string
}

0 comments on commit 1d7d166

Please sign in to comment.