make artists singular and delete descriptions

This commit is contained in:
zS1L3NT Mac 2023-12-28 01:16:20 +08:00
parent dd06c5ac65
commit aca523a303
No known key found for this signature in database
GPG Key ID: 02BE07CD431E4F42
7 changed files with 71 additions and 93 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", artistId: "string|null", // Only null for YouTube Music
name: "string", name: "string",
}) })
@ -24,7 +24,7 @@ export const SongDetailed = type({
type: '"SONG"', type: '"SONG"',
videoId: "string", videoId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
album: AlbumBasic, album: AlbumBasic,
duration: "number|null", duration: "number|null",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
@ -35,7 +35,7 @@ export const VideoDetailed = type({
type: '"VIDEO"', type: '"VIDEO"',
videoId: "string", videoId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
duration: "number|null", duration: "number|null",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
}) })
@ -54,7 +54,7 @@ export const AlbumDetailed = type({
albumId: "string", albumId: "string",
playlistId: "string", playlistId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
year: "number|null", year: "number|null",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
}) })
@ -73,10 +73,9 @@ export const SongFull = type({
type: '"SONG"', type: '"SONG"',
videoId: "string", videoId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
duration: "number", duration: "number",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
description: "string",
formats: "any[]", formats: "any[]",
adaptiveFormats: "any[]", adaptiveFormats: "any[]",
}) })
@ -86,10 +85,9 @@ export const VideoFull = type({
type: '"VIDEO"', type: '"VIDEO"',
videoId: "string", videoId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
duration: "number", duration: "number",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
description: "string",
unlisted: "boolean", unlisted: "boolean",
familySafe: "boolean", familySafe: "boolean",
paid: "boolean", paid: "boolean",
@ -102,7 +100,6 @@ export const ArtistFull = type({
name: "string", name: "string",
type: '"ARTIST"', type: '"ARTIST"',
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
description: "string",
topSongs: [SongDetailed, "[]"], topSongs: [SongDetailed, "[]"],
topAlbums: [AlbumDetailed, "[]"], topAlbums: [AlbumDetailed, "[]"],
topSingles: [AlbumDetailed, "[]"], topSingles: [AlbumDetailed, "[]"],
@ -117,7 +114,7 @@ export const AlbumFull = type({
albumId: "string", albumId: "string",
playlistId: "string", playlistId: "string",
name: "string", name: "string",
artists: [ArtistBasic, "[]"], artist: ArtistBasic,
year: "number|null", year: "number|null",
thumbnails: [ThumbnailFull, "[]"], thumbnails: [ThumbnailFull, "[]"],
songs: [SongDetailed, "[]"], songs: [SongDetailed, "[]"],

View File

@ -389,16 +389,12 @@ export default class YTMusic {
* @returns Artist's Songs * @returns Artist's Songs
*/ */
public async getArtistSongs(artistId: string): Promise<(typeof SongDetailed.infer)[]> { public async getArtistSongs(artistId: string): Promise<(typeof SongDetailed.infer)[]> {
const artistData = await this.constructRequest("browse", { const artistData = await this.constructRequest("browse", { browseId: artistId })
browseId: artistId,
})
const browseToken = traverse(artistData, "musicShelfRenderer", "title", "browseId") const browseToken = traverse(artistData, "musicShelfRenderer", "title", "browseId")
if (browseToken instanceof Array) return [] if (browseToken instanceof Array) return []
const songsData = await this.constructRequest("browse", { const songsData = await this.constructRequest("browse", { browseId: browseToken })
browseId: browseToken,
})
const continueToken = traverse(songsData, "continuation") const continueToken = traverse(songsData, "continuation")
const moreSongsData = await this.constructRequest( const moreSongsData = await this.constructRequest(
"browse", "browse",
@ -409,7 +405,12 @@ export default class YTMusic {
return [ return [
...traverseList(songsData, "musicResponsiveListItemRenderer"), ...traverseList(songsData, "musicResponsiveListItemRenderer"),
...traverseList(moreSongsData, "musicResponsiveListItemRenderer"), ...traverseList(moreSongsData, "musicResponsiveListItemRenderer"),
].map(SongParser.parseArtistSong) ].map(s =>
SongParser.parseArtistSong(s, {
artistId,
name: traverseString(artistData, "header", "title", "text")(),
}),
)
} }
/** /**

View File

@ -1,5 +1,7 @@
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 traverse from "../utils/traverse"
import traverseList from "../utils/traverseList" import traverseList from "../utils/traverseList"
import traverseString from "../utils/traverseString" import traverseString from "../utils/traverseString"
import SongParser from "./SongParser" import SongParser from "./SongParser"
@ -11,12 +13,11 @@ export default class AlbumParser {
name: traverseString(data, "header", "title", "text")(), name: traverseString(data, "header", "title", "text")(),
} }
const artists: ArtistBasic[] = traverseList(data, "header", "subtitle", "runs") const artistData = traverse(data, "header", "subtitle", "runs")
.filter(run => "navigationEndpoint" in run) const artistBasic: ArtistBasic = {
.map(run => ({ artistId: traverseString(artistData, "browseId")(),
artistId: traverseString(run, "browseId")(), name: traverseString(artistData, "text")(),
name: traverseString(run, "text")(), }
}))
const thumbnails = traverseList(data, "header", "thumbnails") const thumbnails = traverseList(data, "header", "thumbnails")
@ -25,13 +26,13 @@ export default class AlbumParser {
type: "ALBUM", type: "ALBUM",
...albumBasic, ...albumBasic,
playlistId: traverseString(data, "buttonRenderer", "playlistId")(), playlistId: traverseString(data, "buttonRenderer", "playlistId")(),
artists, artist: artistBasic,
year: AlbumParser.processYear( year: AlbumParser.processYear(
traverseString(data, "header", "subtitle", "text")(-1), traverseString(data, "header", "subtitle", "text")(-1),
), ),
thumbnails, thumbnails,
songs: traverseList(data, "musicResponsiveListItemRenderer").map(item => songs: traverseList(data, "musicResponsiveListItemRenderer").map(item =>
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails), SongParser.parseAlbumSong(item, artistBasic, albumBasic, thumbnails),
), ),
}, },
AlbumFull, AlbumFull,
@ -39,21 +40,23 @@ export default class AlbumParser {
} }
public static parseSearchResult(item: any): AlbumDetailed { public static parseSearchResult(item: any): AlbumDetailed {
const flexColumns = traverseList(item, "flexColumns") const columns = traverseList(item, "flexColumns", "runs").flat()
// No specific way to identify the title
const title = columns[0]
const artist = columns.find(isArtist) || columns[3]
return checkType( return checkType(
{ {
type: "ALBUM", type: "ALBUM",
albumId: traverseString(item, "browseId")(-1), albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "overlay", "playlistId")(), playlistId: traverseString(item, "overlay", "playlistId")(),
artists: traverseList(flexColumns[1], "runs") artist: {
.filter(run => "navigationEndpoint" in run) name: traverseString(artist, "text")(),
.map(run => ({ artistId: traverseString(artist, "browseId")() || null,
artistId: traverseString(run, "browseId")(), },
name: traverseString(run, "text")(), year: AlbumParser.processYear(traverseString(columns[1], "runs", "text")(-1)),
})), name: traverseString(title, "text")(),
year: AlbumParser.processYear(traverseString(flexColumns[1], "runs", "text")(-1)),
name: traverseString(flexColumns[0], "runs", "text")(),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
AlbumDetailed, AlbumDetailed,
@ -67,7 +70,7 @@ export default class AlbumParser {
albumId: traverseString(item, "browseId")(-1), albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(), playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(),
name: traverseString(item, "title", "text")(), name: traverseString(item, "title", "text")(),
artists: [artistBasic], artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
@ -82,7 +85,7 @@ export default class AlbumParser {
albumId: traverseString(item, "browseId")(-1), albumId: traverseString(item, "browseId")(-1),
playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(), playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(),
name: traverseString(item, "title", "text")(), name: traverseString(item, "title", "text")(),
artists: [artistBasic], artist: artistBasic,
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },

View File

@ -14,14 +14,11 @@ export default class ArtistParser {
name: traverseString(data, "header", "title", "text")(), name: traverseString(data, "header", "title", "text")(),
} }
const description = traverseString(data, "header", "description", "text")()
return checkType( return checkType(
{ {
type: "ARTIST", type: "ARTIST",
...artistBasic, ...artistBasic,
thumbnails: traverseList(data, "header", "thumbnails"), thumbnails: traverseList(data, "header", "thumbnails"),
description,
topSongs: traverseList(data, "musicShelfRenderer", "contents").map(item => topSongs: traverseList(data, "musicShelfRenderer", "contents").map(item =>
SongParser.parseArtistTopSong(item, artistBasic), SongParser.parseArtistTopSong(item, artistBasic),
), ),
@ -46,8 +43,9 @@ export default class ArtistParser {
featuredOn: featuredOn:
traverseList(data, "musicCarouselShelfRenderer") traverseList(data, "musicCarouselShelfRenderer")
?.at(3) ?.at(3)
?.contents.map((item: any) => PlaylistParser.parseArtistFeaturedOn(item)) ?? ?.contents.map((item: any) =>
[], PlaylistParser.parseArtistFeaturedOn(item, artistBasic),
) ?? [],
similarArtists: similarArtists:
traverseList(data, "musicCarouselShelfRenderer") traverseList(data, "musicCarouselShelfRenderer")
?.at(4) ?.at(4)
@ -58,7 +56,7 @@ export default class ArtistParser {
} }
public static parseSearchResult(item: any): ArtistDetailed { public static parseSearchResult(item: any): ArtistDetailed {
const columns = traverseList(item, "flexColumns") const columns = traverseList(item, "flexColumns", "runs").flat()
// No specific way to identify the title // No specific way to identify the title
const title = columns[0] const title = columns[0]
@ -67,7 +65,7 @@ export default class ArtistParser {
{ {
type: "ARTIST", type: "ARTIST",
artistId: traverseString(item, "browseId")(), artistId: traverseString(item, "browseId")(),
name: traverseString(title, "runs", "text")(), name: traverseString(title, "text")(),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
ArtistDetailed, ArtistDetailed,

View File

@ -1,4 +1,4 @@
import { 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 from "../utils/traverse"
@ -53,16 +53,13 @@ export default class PlaylistParser {
) )
} }
public static parseArtistFeaturedOn(item: any): PlaylistDetailed { public static parseArtistFeaturedOn(item: any, artistBasic: ArtistBasic): PlaylistDetailed {
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: { artist: artistBasic,
artistId: traverseString(item, "browseId")(),
name: traverseString(item, "runs", "text")(-3),
},
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
}, },
PlaylistDetailed, PlaylistDetailed,

View File

@ -12,15 +12,12 @@ export default class SongParser {
type: "SONG", type: "SONG",
videoId: traverseString(data, "videoDetails", "videoId")(), videoId: traverseString(data, "videoDetails", "videoId")(),
name: traverseString(data, "videoDetails", "title")(), name: traverseString(data, "videoDetails", "title")(),
artists: [ artist: {
{ name: traverseString(data, "author")(),
artistId: traverseString(data, "videoDetails", "channelId")(), artistId: traverseString(data, "videoDetails", "channelId")(),
name: traverseString(data, "author")(), },
},
],
duration: +traverseString(data, "videoDetails", "lengthSeconds")(), duration: +traverseString(data, "videoDetails", "lengthSeconds")(),
thumbnails: traverseList(data, "videoDetails", "thumbnails"), thumbnails: traverseList(data, "videoDetails", "thumbnails"),
description: traverseString(data, "description")(),
formats: traverseList(data, "streamingData", "formats"), formats: traverseList(data, "streamingData", "formats"),
adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"), adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"),
}, },
@ -41,12 +38,10 @@ export default class SongParser {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId")(),
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists: [ artist: {
{ name: traverseString(artist, "text")(),
name: traverseString(artist, "text")(), artistId: traverseString(artist, "browseId")(),
artistId: traverseString(artist, "browseId")(), },
},
],
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text")(),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId")(),
@ -58,11 +53,10 @@ export default class SongParser {
) )
} }
public static parseArtistSong(item: any): SongDetailed { public static parseArtistSong(item: any, artistBasic: ArtistBasic): SongDetailed {
const columns = traverseList(item, "flexColumns", "runs").flat() const columns = traverseList(item, "flexColumns", "runs").flat()
const title = columns.find(isTitle) const title = columns.find(isTitle)
const artist = columns.find(isArtist)
const album = columns.find(isAlbum) const album = columns.find(isAlbum)
const duration = columns.find(isDuration) const duration = columns.find(isDuration)
@ -71,12 +65,7 @@ export default class SongParser {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId")(),
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists: [ artist: artistBasic,
{
name: traverseString(artist, "text")(),
artistId: traverseString(artist, "browseId")(),
},
],
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text")(),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId")(),
@ -99,7 +88,7 @@ export default class SongParser {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId")(),
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists: [artistBasic], artist: artistBasic,
album: { album: {
name: traverseString(album, "text")(), name: traverseString(album, "text")(),
albumId: traverseString(album, "browseId")(), albumId: traverseString(album, "browseId")(),
@ -113,7 +102,7 @@ export default class SongParser {
public static parseAlbumSong( public static parseAlbumSong(
item: any, item: any,
artists: ArtistBasic[], artistBasic: ArtistBasic,
albumBasic: AlbumBasic, albumBasic: AlbumBasic,
thumbnails: ThumbnailFull[], thumbnails: ThumbnailFull[],
): SongDetailed { ): SongDetailed {
@ -127,7 +116,7 @@ export default class SongParser {
type: "SONG", type: "SONG",
videoId: traverseString(item, "playlistItemData", "videoId")(), videoId: traverseString(item, "playlistItemData", "videoId")(),
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists, artist: artistBasic,
album: albumBasic, album: albumBasic,
duration: duration ? Parser.parseDuration(duration.text) : null, duration: duration ? Parser.parseDuration(duration.text) : null,
thumbnails, thumbnails,

View File

@ -12,15 +12,12 @@ export default class VideoParser {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(data, "videoDetails", "videoId")(), videoId: traverseString(data, "videoDetails", "videoId")(),
name: traverseString(data, "videoDetails", "title")(), name: traverseString(data, "videoDetails", "title")(),
artists: [ 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"),
description: traverseString(data, "description")(),
unlisted: traverse(data, "unlisted"), unlisted: traverse(data, "unlisted"),
familySafe: traverse(data, "familySafe"), familySafe: traverse(data, "familySafe"),
paid: traverse(data, "paid"), paid: traverse(data, "paid"),
@ -39,12 +36,10 @@ export default class VideoParser {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(item, "playNavigationEndpoint", "videoId")(), videoId: traverseString(item, "playNavigationEndpoint", "videoId")(),
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists: [ artist: {
{ name: traverseString(artist, "text")(),
name: traverseString(artist, "text")(), artistId: traverseString(artist, "browseId")(),
artistId: traverseString(artist, "browseId")(), },
},
],
duration: Parser.parseDuration(duration.text), duration: Parser.parseDuration(duration.text),
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
} }
@ -55,7 +50,7 @@ export default class VideoParser {
type: "VIDEO", type: "VIDEO",
videoId: traverseString(item, "videoId")(), videoId: traverseString(item, "videoId")(),
name: traverseString(item, "runs", "text")(), name: traverseString(item, "runs", "text")(),
artists: [artistBasic], artist: artistBasic,
duration: null, duration: null,
thumbnails: traverseList(item, "thumbnails"), thumbnails: traverseList(item, "thumbnails"),
} }
@ -77,12 +72,10 @@ export default class VideoParser {
/https:\/\/i\.ytimg\.com\/vi\/(.+)\//, /https:\/\/i\.ytimg\.com\/vi\/(.+)\//,
)[1], )[1],
name: traverseString(title, "text")(), name: traverseString(title, "text")(),
artists: [ 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"),
}, },