Skip to content

Commit

Permalink
Changed bigint fields to string + Added null filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
thedaviddelta committed May 1, 2021
1 parent 8520fab commit d4a2c65
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 47 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kainet-scraper",
"version": "1.1.0",
"version": "1.1.1",
"description": "YouTube Music scraper for Kainet Music",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
11 changes: 8 additions & 3 deletions src/musiclists.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from "./utils/request";
import * as parse from "./utils/parse";
import * as filter from "./utils/filter";
import {
YtMusicAlbum,
YtMusicPlaylist,
Expand Down Expand Up @@ -76,7 +77,7 @@ const scrape = {
title: parse.text.header(data?.header, 0)!,
thumbnails: parse.thumbnails(data?.header?.musicDetailHeaderRenderer?.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail),
songCount: parse.num.simple(parse.text.header(data?.header, 0, "secondSubtitle")),
songs: parsePlaylist.songs(data)
songs: parsePlaylist.songs(data).filter(filter.songs)
}),
playlistSong: (song?: MusicResponsiveListItemRenderer): YtMusicSong & YtMusicVideo => ({
id: song?.flexColumns?.[0]?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.[0]?.navigationEndpoint?.watchEndpoint?.videoId!,
Expand All @@ -96,7 +97,7 @@ const scrape = {
thumbnails: parse.thumbnails(info.thumbnailDetails),
artist: info.artistDisplayName,
year: info.releaseDate?.year?.toString(),
songs: parseAlbum.songs(data)
songs: parseAlbum.songs(data).filter(filter.songs)
};
},
albumAsPlaylist: (data: PlaylistData | undefined, browseId: string): YtMusicAlbum => ({
Expand All @@ -106,7 +107,7 @@ const scrape = {
thumbnails: parse.thumbnails(data?.header?.musicDetailHeaderRenderer?.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail),
artist: parse.text.header(data?.header, 2, "subtitle"),
year: parse.text.header(data?.header, -1, "subtitle"),
songs: parsePlaylist.songs(data)
songs: parsePlaylist.songs(data).filter(filter.songs)
}),
albumSong: (song?: MusicTrack): YtMusicSong => ({
id: song?.videoId!,
Expand All @@ -127,6 +128,8 @@ export const getPlaylist = (browseId: string): Promise<YtMusicPlaylist | null> =
request("browse").with({ browseId })
.then(res =>
scrape.playlist(res.data, browseId)
).then(list =>
filter.playlists(list) ? list : null
).catch(
() => null
)
Expand All @@ -141,6 +144,8 @@ export const getAlbum = (browseId: string): Promise<YtMusicAlbum | null> => (
request("browse").with({ browseId })
.then(res =>
scrape.album(res.data, browseId) ?? scrape.albumAsPlaylist(res.data, browseId)
).then(list =>
filter.albums(list) ? list : null
).catch(
() => null
)
Expand Down
21 changes: 13 additions & 8 deletions src/search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from "./utils/request";
import * as parse from "./utils/parse";
import * as filter from "./utils/filter";
import {
YtMusicSong,
YtMusicVideo,
Expand Down Expand Up @@ -109,7 +110,7 @@ const scrape: Record<
duration: parse.duration.fromText(parse.text.columns(data?.flexColumns, 1, -1)),
durationText: parse.text.columns(data?.flexColumns, 1, -1),
thumbnails: parse.thumbnails(data?.thumbnail?.musicThumbnailRenderer?.thumbnail),
views: parse.num.big(parse.text.columns(data?.flexColumns, 1, -3))
views: parse.num.big(parse.text.columns(data?.flexColumns, 1, -3))?.toString()
}),
albums: (data?: SearchAlbumData): YtMusicAlbum => ({
id: parseId.albumOrPlaylist(data)!,
Expand All @@ -130,7 +131,7 @@ const scrape: Record<
id: parse.id.browse(data)!,
name: parse.text.columns(data?.flexColumns, 0, 0)!,
thumbnails: parse.thumbnails(data?.thumbnail?.musicThumbnailRenderer?.thumbnail),
subCount: parse.num.big(parse.text.columns(data?.flexColumns, 1, 2))
subCount: parse.num.big(parse.text.columns(data?.flexColumns, 1, 2))?.toString()
})
};

Expand Down Expand Up @@ -167,15 +168,19 @@ export type SearchTypes = typeof SearchType[keyof typeof SearchType];
* Retrieves a list of search results based on the queried type and the query text
* @param type - The type of item to search, as listed in {@link SearchType}
* @param query - The text to query for
* @returns An array of of elements of the specified type, or null if something went wrong
* @returns An array of elements of the specified type
*/
export const search = <T extends SearchTypes>(type: T, query: string) => (
export const search = <T extends SearchTypes>(type: T, query: string): Promise<SearchModels[T][]> => (
request("search").with({ params: searchParams[type], query })
.then(res =>
parseSearch<SearchResults[T]>(res.data)?.map(
el => scrape[type](el) as SearchModels[T]
) ?? null
parseSearch<SearchResults[T]>(
res.data
)?.map(result =>
scrape[type](result) as SearchModels[T]
)?.filter(item =>
(filter[type] as (item: SearchModels[T]) => boolean)(item)
) ?? []
).catch(
() => null
() => []
)
);
15 changes: 8 additions & 7 deletions src/suggestions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from "./utils/request";
import * as parse from "./utils/parse";
import * as filter from "./utils/filter";
import { YtMusicPlaylist } from "./utils/interfaces";
import { MusicTwoRowItemRenderer } from "./utils/types";

Expand All @@ -25,7 +26,7 @@ type SuggestionsData = {
}
};

const parseSuggestions = (data: SuggestionsData) => (
const parseSuggestions = (data?: SuggestionsData) => (
data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents
?.map(el => el?.musicCarouselShelfRenderer?.contents ?? el?.musicImmersiveCarouselShelfRenderer?.contents)
);
Expand All @@ -39,17 +40,17 @@ const scrapePlaylist = (item?: { musicTwoRowItemRenderer?: MusicTwoRowItemRender

/**
* Retrieves a list of suggested playlist, as on the YTMusic homepage
* @returns An array of playlists, or null if something went wrong
* @returns An array of playlists
*/
export const retrieveSuggestions = (): Promise<YtMusicPlaylist[] | null> => (
export const retrieveSuggestions = (): Promise<YtMusicPlaylist[]> => (
request("browse").with()
.then(res =>
parseSuggestions(res.data)?.flatMap(row => (
row?.map(scrapePlaylist)
))?.filter(
(el): el is YtMusicPlaylist => !!el
) ?? null
))?.filter((list): list is YtMusicPlaylist =>
!!list && filter.playlists(list)
) ?? []
).catch(
() => null
() => []
)
);
13 changes: 13 additions & 0 deletions src/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
YtMusicSong,
YtMusicVideo,
YtMusicAlbum,
YtMusicPlaylist,
YtMusicArtist
} from "./interfaces";

export const songs = (item: YtMusicSong) => !!item.id && !!item.title;
export const videos = (item: YtMusicVideo) => !!item.id && !!item.title;
export const albums = (item: YtMusicAlbum) => !!item.id && !!item.browseId && !!item.title;
export const playlists = (item: YtMusicPlaylist) => !!item.id && !!item.browseId && !!item.title;
export const artists = (item: YtMusicArtist) => !!item.id && !!item.name;
4 changes: 2 additions & 2 deletions src/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface YtMusicVideo {
duration?: number,
durationText?: string,
thumbnails: string[],
views?: bigint
views?: string // bigint is not serializable
}

export interface YtMusicAlbum {
Expand All @@ -41,5 +41,5 @@ export interface YtMusicArtist {
id: string,
name: string,
thumbnails: string[],
subCount?: bigint
subCount?: string // bigint is not serializable
}
7 changes: 5 additions & 2 deletions tests/album.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ const queries = [
"MPREb_b3D2y9bdjNa"
];

it("returns album item with all data correctly", () => (
expectFromQueries(queries, getAlbum)
it("returns album item with a songs array correctly", () => (
expectFromQueries(queries, getAlbum, album => {
expect(album).not.toBeNull();
expect(album?.songs).not.toStrictEqual([]);
})
));

it("returns null on no album result", () => (
Expand Down
7 changes: 5 additions & 2 deletions tests/playlist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ const queries = [
"VLPL5eNRayL5QIRiH9BR6dku6886u_LJmpth"
];

it("returns playlist item with all data correctly", () => (
expectFromQueries(queries, getPlaylist)
it("returns playlist item with a songs array correctly", () => (
expectFromQueries(queries, getPlaylist, playlist => {
expect(playlist).not.toBeNull();
expect(playlist?.songs).not.toStrictEqual([]);
})
));

it("returns null on no playlist result", () => (
Expand Down
26 changes: 14 additions & 12 deletions tests/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,61 @@ const queries = [
];

const expectFromType = (type: typeof SearchType[keyof typeof SearchType]): Promise<void> => (
expectFromQueries(queries, text => search(type, text))
expectFromQueries(queries, text => search(type, text), result => (
expect(result).not.toStrictEqual([])
))
);

const expectWrongFromType = (type: typeof SearchType[keyof typeof SearchType]): Promise<void> => (
expectFromWrong(text => search(type, text))
expectFromWrong(text => search(type, text), [])
);

describe("songs", () => {
it("returns youtube songs with all data correctly", () => (
it("returns youtube songs correctly", () => (
expectFromType(SearchType.SONGS)
));

it("returns null on no songs results", () => (
it("returns empty array on no songs results", () => (
expectWrongFromType(SearchType.SONGS)
));
});

describe("videos", () => {
it("returns youtube videos with all data correctly", () => (
it("returns youtube videos correctly", () => (
expectFromType(SearchType.VIDEOS)
));

it("returns null on no videos results", () => (
it("returns empty array on no videos results", () => (
expectWrongFromType(SearchType.VIDEOS)
));
});

describe("albums", () => {
it("returns youtube albums with all data correctly", () => (
it("returns youtube albums correctly", () => (
expectFromType(SearchType.ALBUMS)
));

it("returns null on no albums results", () => (
it("returns empty array on no albums results", () => (
expectWrongFromType(SearchType.ALBUMS)
));
});

describe("playlists", () => {
it("returns youtube playlists with all data correctly", () => (
it("returns youtube playlists correctly", () => (
expectFromType(SearchType.PLAYLISTS)
));

it("returns null on no playlists results", () => (
it("returns empty array on no playlists results", () => (
expectWrongFromType(SearchType.PLAYLISTS)
));
});

describe("artists", () => {
it("returns youtube artists with all data correctly", () => (
it("returns youtube artists correctly", () => (
expectFromType(SearchType.ARTISTS)
));

it("returns null on no artists results", () => (
it("returns empty array on no artists results", () => (
expectWrongFromType(SearchType.ARTISTS)
));
});
4 changes: 2 additions & 2 deletions tests/suggestions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { retrieveSuggestions } from "../src";

it("returns suggested playlists with all data correctly", () => (
it("returns suggested playlists correctly", () => (
retrieveSuggestions()
.then(suggestions => expect(suggestions).not.toBeNull())
.then(suggestions => expect(suggestions).not.toStrictEqual([]))
));
16 changes: 8 additions & 8 deletions tests/testUtils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
export const expectFromQueries = (
export const expectFromQueries = <T>(
queries: string[],
callback: (text: string) => Promise<any>
runCb: (text: string) => Promise<T>,
expectCb: (result: T) => any
): Promise<void> => (
Promise.all(queries.map(callback))
Promise.all(queries.map(runCb))
.then(results =>
results.forEach(result =>
expect(result).not.toBeNull()
)
results.forEach(expectCb)
)
);

export const expectFromWrong = async (
callback: (text: string) => Promise<any>
callback: (text: string) => Promise<any>,
errValue: any = null
): Promise<void> => (
callback("ad463b3c3298f77dd6d21c95020feb45baf98d7a")
.then(result => expect(result).toBeNull())
.then(result => expect(result).toStrictEqual(errValue))
);

0 comments on commit d4a2c65

Please sign in to comment.