♻️ 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 const ArtistBasic = type({
artistId: "string|null", // Only null for YouTube Music
artistId: "string|null",
name: "string",
})

View File

@ -20,9 +20,7 @@ import PlaylistParser from "./parsers/PlaylistParser"
import SearchParser from "./parsers/SearchParser"
import SongParser from "./parsers/SongParser"
import VideoParser from "./parsers/VideoParser"
import traverse from "./utils/traverse"
import traverseList from "./utils/traverseList"
import traverseString from "./utils/traverseString"
import { traverse, traverseList, traverseString } from "./utils/traverse"
export default class YTMusic {
private cookiejar: CookieJar
@ -358,7 +356,7 @@ export default class YTMusic {
const browseId = traverse(traverseList(data, "tabs", "tabRenderer")[1], "browseId")
const lyricsData = await this.constructRequest("browse", { browseId })
const lyrics = traverseString(lyricsData, "description", "runs", "text")()
const lyrics = traverseString(lyricsData, "description", "runs", "text")
return lyrics
? lyrics
@ -408,7 +406,7 @@ export default class YTMusic {
].map(s =>
SongParser.parseArtistSong(s, {
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 =>
AlbumParser.parseArtistAlbum(item, {
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 checkType from "../utils/checkType"
import { isArtist } from "../utils/filters"
import traverse from "../utils/traverse"
import traverseList from "../utils/traverseList"
import traverseString from "../utils/traverseString"
import { traverse, traverseList, traverseString } from "../utils/traverse"
import SongParser from "./SongParser"
export default class AlbumParser {
public static parse(data: any, albumId: string): AlbumFull {
const albumBasic: AlbumBasic = {
albumId,
name: traverseString(data, "header", "title", "text")(),
name: traverseString(data, "header", "title", "text"),
}
const artistData = traverse(data, "header", "subtitle", "runs")
const artistBasic: ArtistBasic = {
artistId: traverseString(artistData, "browseId")() || null,
name: traverseString(artistData, "text")(),
artistId: traverseString(artistData, "browseId") || null,
name: traverseString(artistData, "text"),
}
const thumbnails = traverseList(data, "header", "thumbnails")
@ -25,10 +23,10 @@ export default class AlbumParser {
{
type: "ALBUM",
...albumBasic,
playlistId: traverseString(data, "buttonRenderer", "playlistId")(),
playlistId: traverseString(data, "buttonRenderer", "playlistId"),
artist: artistBasic,
year: AlbumParser.processYear(
traverseString(data, "header", "subtitle", "text")(-1),
traverseList(data, "header", "subtitle", "text").at(-1),
),
thumbnails,
songs: traverseList(data, "musicResponsiveListItemRenderer").map(item =>
@ -49,14 +47,14 @@ export default class AlbumParser {
return checkType(
{
type: "ALBUM",
albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "overlay", "playlistId")(),
albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "overlay", "playlistId"),
artist: {
name: traverseString(artist, "text")(),
artistId: traverseString(artist, "browseId")() || null,
name: traverseString(artist, "text"),
artistId: traverseString(artist, "browseId") || null,
},
year: AlbumParser.processYear(traverseString(columns[1], "runs", "text")(-1)),
name: traverseString(title, "text")(),
year: AlbumParser.processYear(columns.at(-1).text),
name: traverseString(title, "text"),
thumbnails: traverseList(item, "thumbnails"),
},
AlbumDetailed,
@ -67,11 +65,11 @@ export default class AlbumParser {
return checkType(
{
type: "ALBUM",
albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(),
name: traverseString(item, "title", "text")(),
albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "thumbnailOverlay", "playlistId"),
name: traverseString(item, "title", "text"),
artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)),
thumbnails: traverseList(item, "thumbnails"),
},
AlbumDetailed,
@ -82,11 +80,11 @@ export default class AlbumParser {
return checkType(
{
type: "ALBUM",
albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(),
name: traverseString(item, "title", "text")(),
albumId: traverseList(item, "browseId").at(-1),
playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId"),
name: traverseString(item, "title", "text"),
artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)),
thumbnails: traverseList(item, "thumbnails"),
},
AlbumDetailed,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
import traverseString from "./traverseString"
import { traverseString } from "./traverse"
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) => {
return ["MUSIC_PAGE_TYPE_USER_CHANNEL", "MUSIC_PAGE_TYPE_ARTIST"].includes(
traverseString(data, "pageType")(),
traverseString(data, "pageType"),
)
}
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) => {
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 res = []
@ -27,4 +27,10 @@ const traverse = (data: any, ...keys: string[]) => {
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 || ""
}