♻️ refactor traverse utilities functions

This commit is contained in:
zS1L3NT Mac 2023-12-28 01:48:05 +08:00
parent 6be353b46f
commit 7cb39bca6a
No known key found for this signature in database
GPG Key ID: 02BE07CD431E4F42
12 changed files with 92 additions and 111 deletions

View File

@ -9,7 +9,7 @@ export const ThumbnailFull = type({
export type ArtistBasic = typeof ArtistBasic.infer export type ArtistBasic = typeof ArtistBasic.infer
export const ArtistBasic = type({ export const ArtistBasic = type({
artistId: "string|null", // Only null for YouTube Music artistId: "string|null",
name: "string", name: "string",
}) })

View File

@ -20,9 +20,7 @@ import PlaylistParser from "./parsers/PlaylistParser"
import SearchParser from "./parsers/SearchParser" import SearchParser from "./parsers/SearchParser"
import SongParser from "./parsers/SongParser" import SongParser from "./parsers/SongParser"
import VideoParser from "./parsers/VideoParser" import VideoParser from "./parsers/VideoParser"
import traverse from "./utils/traverse" import { traverse, traverseList, traverseString } from "./utils/traverse"
import traverseList from "./utils/traverseList"
import traverseString from "./utils/traverseString"
export default class YTMusic { export default class YTMusic {
private cookiejar: CookieJar private cookiejar: CookieJar
@ -358,7 +356,7 @@ export default class YTMusic {
const browseId = traverse(traverseList(data, "tabs", "tabRenderer")[1], "browseId") const browseId = traverse(traverseList(data, "tabs", "tabRenderer")[1], "browseId")
const lyricsData = await this.constructRequest("browse", { browseId }) const lyricsData = await this.constructRequest("browse", { browseId })
const lyrics = traverseString(lyricsData, "description", "runs", "text")() const lyrics = traverseString(lyricsData, "description", "runs", "text")
return lyrics return lyrics
? lyrics ? lyrics
@ -408,7 +406,7 @@ export default class YTMusic {
].map(s => ].map(s =>
SongParser.parseArtistSong(s, { SongParser.parseArtistSong(s, {
artistId, artistId,
name: traverseString(artistData, "header", "title", "text")(), name: traverseString(artistData, "header", "title", "text"),
}), }),
) )
} }
@ -431,7 +429,7 @@ export default class YTMusic {
return traverseList(albumsData, "musicTwoRowItemRenderer").map(item => return traverseList(albumsData, "musicTwoRowItemRenderer").map(item =>
AlbumParser.parseArtistAlbum(item, { AlbumParser.parseArtistAlbum(item, {
artistId, artistId,
name: traverseString(albumsData, "header", "runs", "text")(), name: traverseString(albumsData, "header", "runs", "text"),
}), }),
) )
} }

View File

@ -1,22 +1,20 @@
import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from "../@types/types" import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from "../@types/types"
import checkType from "../utils/checkType" import checkType from "../utils/checkType"
import { isArtist } from "../utils/filters" import { isArtist } from "../utils/filters"
import traverse from "../utils/traverse" import { traverse, traverseList, traverseString } from "../utils/traverse"
import traverseList from "../utils/traverseList"
import traverseString from "../utils/traverseString"
import SongParser from "./SongParser" import SongParser from "./SongParser"
export default class AlbumParser { export default class AlbumParser {
public static parse(data: any, albumId: string): AlbumFull { public static parse(data: any, albumId: string): AlbumFull {
const albumBasic: AlbumBasic = { const albumBasic: AlbumBasic = {
albumId, albumId,
name: traverseString(data, "header", "title", "text")(), name: traverseString(data, "header", "title", "text"),
} }
const artistData = traverse(data, "header", "subtitle", "runs") const artistData = traverse(data, "header", "subtitle", "runs")
const artistBasic: ArtistBasic = { const artistBasic: ArtistBasic = {
artistId: traverseString(artistData, "browseId")() || null, artistId: traverseString(artistData, "browseId") || null,
name: traverseString(artistData, "text")(), name: traverseString(artistData, "text"),
} }
const thumbnails = traverseList(data, "header", "thumbnails") const thumbnails = traverseList(data, "header", "thumbnails")
@ -25,10 +23,10 @@ export default class AlbumParser {
{ {
type: "ALBUM", type: "ALBUM",
...albumBasic, ...albumBasic,
playlistId: traverseString(data, "buttonRenderer", "playlistId")(), playlistId: traverseString(data, "buttonRenderer", "playlistId"),
artist: artistBasic, artist: artistBasic,
year: AlbumParser.processYear( year: AlbumParser.processYear(
traverseString(data, "header", "subtitle", "text")(-1), traverseList(data, "header", "subtitle", "text").at(-1),
), ),
thumbnails, thumbnails,
songs: traverseList(data, "musicResponsiveListItemRenderer").map(item => songs: traverseList(data, "musicResponsiveListItemRenderer").map(item =>
@ -49,14 +47,14 @@ export default class AlbumParser {
return checkType( return checkType(
{ {
type: "ALBUM", type: "ALBUM",
albumId: traverseString(item, "browseId")(-1), albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "overlay", "playlistId")(), playlistId: traverseString(item, "overlay", "playlistId"),
artist: { artist: {
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId")() || null, artistId: traverseString(artist, "browseId") || null,
}, },
year: AlbumParser.processYear(traverseString(columns[1], "runs", "text")(-1)), year: AlbumParser.processYear(columns.at(-1).text),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
AlbumDetailed, AlbumDetailed,
@ -67,11 +65,11 @@ export default class AlbumParser {
return checkType( return checkType(
{ {
type: "ALBUM", type: "ALBUM",
albumId: traverseString(item, "browseId")(-1), albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(), playlistId: traverseString(item, "thumbnailOverlay", "playlistId"),
name: traverseString(item, "title", "text")(), name: traverseString(item, "title", "text"),
artist: artistBasic, artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
AlbumDetailed, AlbumDetailed,
@ -82,11 +80,11 @@ export default class AlbumParser {
return checkType( return checkType(
{ {
type: "ALBUM", type: "ALBUM",
albumId: traverseString(item, "browseId")(-1), albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(), playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId"),
name: traverseString(item, "title", "text")(), name: traverseString(item, "title", "text"),
artist: artistBasic, artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
AlbumDetailed, AlbumDetailed,

View File

@ -1,7 +1,6 @@
import { ArtistDetailed, ArtistFull } from "../@types/types" import { ArtistDetailed, ArtistFull } from "../@types/types"
import checkType from "../utils/checkType" import checkType from "../utils/checkType"
import traverseList from "../utils/traverseList" import { traverseList, traverseString } from "../utils/traverse"
import traverseString from "../utils/traverseString"
import AlbumParser from "./AlbumParser" import AlbumParser from "./AlbumParser"
import PlaylistParser from "./PlaylistParser" import PlaylistParser from "./PlaylistParser"
import SongParser from "./SongParser" import SongParser from "./SongParser"
@ -11,7 +10,7 @@ export default class ArtistParser {
public static parse(data: any, artistId: string): ArtistFull { public static parse(data: any, artistId: string): ArtistFull {
const artistBasic = { const artistBasic = {
artistId, artistId,
name: traverseString(data, "header", "title", "text")(), name: traverseString(data, "header", "title", "text"),
} }
return checkType( return checkType(
@ -64,8 +63,8 @@ export default class ArtistParser {
return checkType( return checkType(
{ {
type: "ARTIST", type: "ARTIST",
artistId: traverseString(item, "browseId")(), artistId: traverseString(item, "browseId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
ArtistDetailed, ArtistDetailed,
@ -76,8 +75,8 @@ export default class ArtistParser {
return checkType( return checkType(
{ {
type: "ARTIST", type: "ARTIST",
artistId: traverseString(item, "browseId")(), artistId: traverseString(item, "browseId"),
name: traverseString(item, "runs", "text")(), name: traverseString(item, "runs", "text"),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
ArtistDetailed, ArtistDetailed,

View File

@ -1,9 +1,7 @@
import { ArtistBasic, PlaylistDetailed, PlaylistFull } from "../@types/types" import { ArtistBasic, PlaylistDetailed, PlaylistFull } from "../@types/types"
import checkType from "../utils/checkType" import checkType from "../utils/checkType"
import { isArtist } from "../utils/filters" import { isArtist } from "../utils/filters"
import traverse from "../utils/traverse" import { traverse, traverseList, traverseString } from "../utils/traverse"
import traverseList from "../utils/traverseList"
import traverseString from "../utils/traverseString"
export default class PlaylistParser { export default class PlaylistParser {
public static parse(data: any, playlistId: string): PlaylistFull { public static parse(data: any, playlistId: string): PlaylistFull {
@ -13,10 +11,10 @@ export default class PlaylistParser {
{ {
type: "PLAYLIST", type: "PLAYLIST",
playlistId, playlistId,
name: traverseString(data, "header", "title", "text")(), name: traverseString(data, "header", "title", "text"),
artist: { artist: {
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId")() || null, artistId: traverseString(artist, "browseId") || null,
}, },
videoCount: videoCount:
+traverseList(data, "header", "secondSubtitle", "text") +traverseList(data, "header", "secondSubtitle", "text")
@ -40,11 +38,11 @@ export default class PlaylistParser {
return checkType( return checkType(
{ {
type: "PLAYLIST", type: "PLAYLIST",
playlistId: traverseString(item, "overlay", "playlistId")(), playlistId: traverseString(item, "overlay", "playlistId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: { artist: {
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId")() || null, artistId: traverseString(artist, "browseId") || null,
}, },
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
@ -56,8 +54,8 @@ export default class PlaylistParser {
return checkType( return checkType(
{ {
type: "PLAYLIST", type: "PLAYLIST",
playlistId: traverseString(item, "navigationEndpoint", "browseId")(), playlistId: traverseString(item, "navigationEndpoint", "browseId"),
name: traverseString(item, "runs", "text")(), name: traverseString(item, "runs", "text"),
artist: artistBasic, artist: artistBasic,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },

View File

@ -1,5 +1,5 @@
import { SearchResult } from "../@types/types" import { SearchResult } from "../@types/types"
import traverseList from "../utils/traverseList" import { traverseList } from "../utils/traverse"
import AlbumParser from "./AlbumParser" import AlbumParser from "./AlbumParser"
import ArtistParser from "./ArtistParser" import ArtistParser from "./ArtistParser"
import PlaylistParser from "./PlaylistParser" import PlaylistParser from "./PlaylistParser"

View File

@ -1,8 +1,7 @@
import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from "../@types/types" import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from "../@types/types"
import checkType from "../utils/checkType" import checkType from "../utils/checkType"
import { isAlbum, isArtist, isDuration, isTitle } from "../utils/filters" import { isAlbum, isArtist, isDuration, isTitle } from "../utils/filters"
import traverseList from "../utils/traverseList" import { traverseList, traverseString } from "../utils/traverse"
import traverseString from "../utils/traverseString"
import Parser from "./Parser" import Parser from "./Parser"
export default class SongParser { export default class SongParser {
@ -10,13 +9,13 @@ export default class SongParser {
return checkType( return checkType(
{ {
type: "SONG", type: "SONG",
videoId: traverseString(data, "videoDetails", "videoId")(), videoId: traverseString(data, "videoDetails", "videoId"),
name: traverseString(data, "videoDetails", "title")(), name: traverseString(data, "videoDetails", "title"),
artist: { artist: {
name: traverseString(data, "author")(), name: traverseString(data, "author"),
artistId: traverseString(data, "videoDetails", "channelId")(), artistId: traverseString(data, "videoDetails", "channelId"),
}, },
duration: +traverseString(data, "videoDetails", "lengthSeconds")(), duration: +traverseString(data, "videoDetails", "lengthSeconds"),
thumbnails: traverseList(data, "videoDetails", "thumbnails"), thumbnails: traverseList(data, "videoDetails", "thumbnails"),
formats: traverseList(data, "streamingData", "formats"), formats: traverseList(data, "streamingData", "formats"),
adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"), adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"),
@ -36,15 +35,15 @@ export default class SongParser {
return checkType( return checkType(
{ {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: { artist: {
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId")() || null, artistId: traverseString(artist, "browseId") || null,
}, },
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text"),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId"),
}, },
duration: Parser.parseDuration(duration.text), duration: Parser.parseDuration(duration.text),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
@ -63,12 +62,12 @@ export default class SongParser {
return checkType( return checkType(
{ {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: artistBasic, artist: artistBasic,
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text"),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId"),
}, },
duration: duration ? Parser.parseDuration(duration.text) : null, duration: duration ? Parser.parseDuration(duration.text) : null,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
@ -86,12 +85,12 @@ export default class SongParser {
return checkType( return checkType(
{ {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: artistBasic, artist: artistBasic,
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text"),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId"),
}, },
duration: null, duration: null,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
@ -114,8 +113,8 @@ export default class SongParser {
return checkType( return checkType(
{ {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: artistBasic, artist: artistBasic,
album: albumBasic, album: albumBasic,
duration: duration ? Parser.parseDuration(duration.text) : null, duration: duration ? Parser.parseDuration(duration.text) : null,

View File

@ -1,22 +1,20 @@
import { ArtistBasic, VideoDetailed, VideoFull } from "../@types/types" import { ArtistBasic, VideoDetailed, VideoFull } from "../@types/types"
import checkType from "../utils/checkType" import checkType from "../utils/checkType"
import { isArtist, isDuration, isTitle } from "../utils/filters" import { isArtist, isDuration, isTitle } from "../utils/filters"
import traverse from "../utils/traverse" import { traverse, traverseList, traverseString } from "../utils/traverse"
import traverseList from "../utils/traverseList"
import traverseString from "../utils/traverseString"
import Parser from "./Parser" import Parser from "./Parser"
export default class VideoParser { export default class VideoParser {
public static parse(data: any): VideoFull { public static parse(data: any): VideoFull {
return { return {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(data, "videoDetails", "videoId")(), videoId: traverseString(data, "videoDetails", "videoId"),
name: traverseString(data, "videoDetails", "title")(), name: traverseString(data, "videoDetails", "title"),
artist: { artist: {
artistId: traverseString(data, "videoDetails", "channelId")(), artistId: traverseString(data, "videoDetails", "channelId"),
name: traverseString(data, "author")(), name: traverseString(data, "author"),
}, },
duration: +traverseString(data, "videoDetails", "lengthSeconds")(), duration: +traverseString(data, "videoDetails", "lengthSeconds"),
thumbnails: traverseList(data, "videoDetails", "thumbnails"), thumbnails: traverseList(data, "videoDetails", "thumbnails"),
unlisted: traverse(data, "unlisted"), unlisted: traverse(data, "unlisted"),
familySafe: traverse(data, "familySafe"), familySafe: traverse(data, "familySafe"),
@ -34,11 +32,11 @@ export default class VideoParser {
return { return {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(item, "playNavigationEndpoint", "videoId")(), videoId: traverseString(item, "playNavigationEndpoint", "videoId"),
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: { artist: {
artistId: traverseString(artist, "browseId") || null, artistId: traverseString(artist, "browseId") || null,
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
}, },
duration: Parser.parseDuration(duration.text), duration: Parser.parseDuration(duration.text),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
@ -48,8 +46,8 @@ export default class VideoParser {
public static parseArtistTopVideo(item: any, artistBasic: ArtistBasic): VideoDetailed { public static parseArtistTopVideo(item: any, artistBasic: ArtistBasic): VideoDetailed {
return { return {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(item, "videoId")(), videoId: traverseString(item, "videoId"),
name: traverseString(item, "runs", "text")(), name: traverseString(item, "runs", "text"),
artist: artistBasic, artist: artistBasic,
duration: null, duration: null,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
@ -67,14 +65,14 @@ export default class VideoParser {
{ {
type: "VIDEO", type: "VIDEO",
videoId: videoId:
traverseString(item, "playNavigationEndpoint", "videoId")() || traverseString(item, "playNavigationEndpoint", "videoId") ||
traverseList(item, "thumbnails")[0].url.match( traverseList(item, "thumbnails")[0].url.match(
/https:\/\/i\.ytimg\.com\/vi\/(.+)\//, /https:\/\/i\.ytimg\.com\/vi\/(.+)\//,
)[1], )[1],
name: traverseString(title, "text")(), name: traverseString(title, "text"),
artist: { artist: {
name: traverseString(artist, "text")(), name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId")() || null, artistId: traverseString(artist, "browseId") || null,
}, },
duration: duration ? Parser.parseDuration(duration.text) : null, duration: duration ? Parser.parseDuration(duration.text) : null,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),

View File

@ -1,19 +1,19 @@
import traverseString from "./traverseString" import { traverseString } from "./traverse"
export const isTitle = (data: any) => { export const isTitle = (data: any) => {
return traverseString(data, "musicVideoType")().startsWith("MUSIC_VIDEO_TYPE_") return traverseString(data, "musicVideoType").startsWith("MUSIC_VIDEO_TYPE_")
} }
export const isArtist = (data: any) => { export const isArtist = (data: any) => {
return ["MUSIC_PAGE_TYPE_USER_CHANNEL", "MUSIC_PAGE_TYPE_ARTIST"].includes( return ["MUSIC_PAGE_TYPE_USER_CHANNEL", "MUSIC_PAGE_TYPE_ARTIST"].includes(
traverseString(data, "pageType")(), traverseString(data, "pageType"),
) )
} }
export const isAlbum = (data: any) => { export const isAlbum = (data: any) => {
return traverseString(data, "pageType")() === "MUSIC_PAGE_TYPE_ALBUM" return traverseString(data, "pageType") === "MUSIC_PAGE_TYPE_ALBUM"
} }
export const isDuration = (data: any) => { export const isDuration = (data: any) => {
return traverseString(data, "text")().match(/(\d{1,2}:)?\d{1,2}:\d{1,2}/) return traverseString(data, "text").match(/(\d{1,2}:)?\d{1,2}:\d{1,2}/)
} }

View File

@ -1,4 +1,4 @@
const traverse = (data: any, ...keys: string[]) => { export const traverse = (data: any, ...keys: string[]) => {
const again = (data: any, key: string): any => { const again = (data: any, key: string): any => {
const res = [] const res = []
@ -27,4 +27,10 @@ const traverse = (data: any, ...keys: string[]) => {
return value return value
} }
export default traverse export const traverseList = (data: any, ...keys: string[]): any[] => {
return [traverse(data, ...keys)].flat()
}
export const traverseString = (data: any, ...keys: string[]): string => {
return traverseList(data, ...keys).at(0) || ""
}

View File

@ -1,7 +0,0 @@
import traverse from "./traverse"
export default (data: any, ...keys: string[]): any[] => {
const value = traverse(data, ...keys)
const flatValue = [value].flat()
return flatValue
}

View File

@ -1,8 +0,0 @@
import traverse from "./traverse"
export default (data: any, ...keys: string[]) =>
(index = 0): string => {
const value = traverse(data, ...keys)
const flatValue = [value].flat().at(index)
return flatValue || ""
}