From bb70f30f174eecea3d27de12ef78960d3a514c4a Mon Sep 17 00:00:00 2001 From: Shiny Nematoda Date: Wed, 15 Jun 2022 15:38:11 +0000 Subject: [PATCH] Added --- builder.go | 104 ++++++++++++++++++++++++++++++++++ fetch.go | 114 +++++++++++++++++++++++++++++++++---- main.go | 48 ++++++++++++---- parser.go | 162 +++++++++++++++++++++++++---------------------------- structs.go | 13 ++++- types.go | 23 +++++--- utils.go | 4 +- 7 files changed, 353 insertions(+), 115 deletions(-) create mode 100644 builder.go diff --git a/builder.go b/builder.go new file mode 100644 index 0000000..daceaa7 --- /dev/null +++ b/builder.go @@ -0,0 +1,104 @@ +package main + +import ( + "sync" + + "github.com/tidwall/gjson" +) + +func TwoRowItemRenderer(t string, a gjson.Result) []Item { + + r := []Item{} + + var id string + + if t == "album" || t == "singles" { + id = "menu.menuRenderer.items.#(menuNavigationItemRenderer.text.runs.0.text == Shuffle play).menuNavigationItemRenderer.navigationEndpoint.watchPlaylistEndpoint.playlistId" + } else { + id = "navigationEndpoint.browseEndpoint.browseId" + } + + wg := sync.WaitGroup{} + + v := a.Get("#.musicTwoRowItemRenderer") + + v.ForEach( + func(_, j gjson.Result) bool { + + wg.Add(1) + + go func() { + defer wg.Done() + + r = append(r, Item{ + Id: j.Get(id).String(), + Title: RunsText(j.Get("title")), + Sub: RunsText(j.Get("subtitle")), + Thumbnails: GetThumbnails(j.Get("thumbnailRenderer.musicThumbnailRenderer.thumbnail.thumbnails")), + }) + + }() + + wg.Wait() + + return true + }, + ) + + return r +} + +func CarouselShelfRenderer(i gjson.Result) map[string]interface{} { + + shelf := make(map[string]interface{}) + + shelf["title"] = RunsText(i.Get("header.musicCarouselShelfBasicHeaderRenderer.title")) + + ct := i.Get("contents") + + if ct.Get("#(musicTwoRowItemRenderer)").Exists() { + shelf["contents"] = TwoRowItemRenderer("", ct) + } else if ct.Get("#(musicResponsiveListItemRenderer)").Exists() { + shelf["contents"] = ResponsiveListItemRenderer(ct) + } + + shelf["raw"] = ct.Value() + + return shelf +} + +func ResponsiveListItemRenderer(s gjson.Result) []Item { + + r := []Item{} + + wg := sync.WaitGroup{} + + s.ForEach( + func(_, v gjson.Result) bool { + + wg.Add(1) + + go func() { + defer wg.Done() + + j := v.Get("musicResponsiveListItemRenderer") + flex := j.Get("flexColumns.#.musicResponsiveListItemFlexColumnRenderer.text.runs.0") + + r = append(r, Item{ + Id: j.Get("playlistItemData.videoId").String(), + Title: flex.Get("#(navigationEndpoint.watchEndpoint.videoId).text").String(), + SubId: flex.Get("#.navigationEndpoint.browseEndpoint").Get("#(browseId).browseId").String(), + Sub: flex.Get("#(navigationEndpoint.browseEndpoint.browseId).text").String(), + Thumbnails: GetThumbnails(j.Get("thumbnail.musicThumbnailRenderer.thumbnail.thumbnails")), + }) + + }() + + wg.Wait() + + return true + }, + ) + + return r +} diff --git a/fetch.go b/fetch.go index b055671..7c6e7b0 100644 --- a/fetch.go +++ b/fetch.go @@ -6,11 +6,24 @@ import ( "io" "log" "net/http" + "strings" + + "github.com/tidwall/gjson" ) -func Fetch(path string, data []byte) (string, int, error) { +func Fetch(path string, data []byte, c string) (string, int, error) { - url := "https://music.youtube.com/youtubei/v1/" + path + "?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30" + var query string + + if c != "" { + query = "&ctoken=" + c + "&continuation=" + c + "&type=next" + } else { + query = "" + } + + url := "https://music.youtube.com/youtubei/v1/" + path + "?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30" + query + "&prettyPrint=false" + + log.Println(url) req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) if err != nil { @@ -41,14 +54,14 @@ func Fetch(path string, data []byte) (string, int, error) { return string(body), resp.StatusCode, nil } -func FetchBrowse(id string, browse BrowseData) (string, int) { +func FetchBrowse(id string, c string, browse BrowseData) (string, int) { data, err := json.Marshal(browse) if err != nil { return ErrorMessage(err), 500 } - raw, status, err := Fetch("browse", data) + raw, status, err := Fetch("browse", data, c) if err != nil { return ErrorMessage(err), 500 } @@ -57,11 +70,68 @@ func FetchBrowse(id string, browse BrowseData) (string, int) { } +func FetchHome(c string) (string, int) { + + /* WIP */ + + id := "FEmusic_home" + + if c != "" { + id = "" + } + + log.Println(id) + + context := GetTypeBrowse("home", id) + + raw, status := FetchBrowse(id, c, context) + + /*res, err := ParseHome(raw) + if err != nil { + return ErrorMessage(err), 500 + }*/ + + return raw, status +} + +func FetchExplore() (string, int) { + + id := "FEmusic_explore" + + log.Println(id) + + context := GetTypeBrowse("", id) + + raw, status := FetchBrowse(id, "", context) + + res, err := ParseExplore(raw) + if err != nil { + return ErrorMessage(err), 500 + } + + return res, status +} + +func FetchMoods() (string, int) { + + /* WIP */ + + id := "FEmusic_moods_and_genres" + + log.Println(id) + + context := GetTypeBrowse("", id) + + raw, status := FetchBrowse(id, "", context) + + return raw, status +} + func FetchArtist(id string) (string, int) { - browse := GetTypeBrowse("artist", id) + context := GetTypeBrowse("artist", id) - raw, status := FetchBrowse(id, browse) + raw, status := FetchBrowse(id, "", context) res, err := ParseArtist(raw) if err != nil { @@ -73,9 +143,9 @@ func FetchArtist(id string) (string, int) { func FetchLyrics(id string) (string, int) { - browse := GetTypeBrowse("lyrics", id) + context := GetTypeBrowse("lyrics", id) - raw, status := FetchBrowse(id, browse) + raw, status := FetchBrowse(id, "", context) res, err := ParseLyrics(raw) if err != nil { @@ -85,6 +155,30 @@ func FetchLyrics(id string) (string, int) { return res, status } +func FetchPlaylist(id string) (string, int) { + + context := GetTypeBrowse("playlist", id) + + raw, status := FetchBrowse(id, "", context) + + return raw, status +} + +func FetchAlbum(id string) (string, int) { + + context := GetTypeBrowse("album", id) + + raw, status := FetchBrowse(id, "", context) + + url := gjson.Parse(raw).Get("microformat.microformatDataRenderer.urlCanonical").String() + + data := "{\"canonicalUrl\": \"" + + strings.Replace(url, "https://music.youtube.com", "", -1) + + "\"}" + + return data, status +} + func FetchNext(id string) (string, int) { data, err := json.Marshal(GetTypeNext(id)) @@ -92,7 +186,7 @@ func FetchNext(id string) (string, int) { return ErrorMessage(err), 500 } - raw, status, err := Fetch("next", data) + raw, status, err := Fetch("next", data, "") if err != nil { return ErrorMessage(err), 500 } @@ -103,4 +197,4 @@ func FetchNext(id string) (string, int) { } return res, status -} \ No newline at end of file +} diff --git a/main.go b/main.go index 90eaa4f..66be1d5 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,10 @@ package main import ( - "os" - + "fmt" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/recover" + "os" ) func SetHeaders(c *fiber.Ctx) error { @@ -17,21 +17,39 @@ func SetHeaders(c *fiber.Ctx) error { } func HandleHealth(c *fiber.Ctx) error { - defer calc()() + defer calc(c.OriginalURL())() + + fmt.Println("Health Check!!") return c.SendStatus(204) } -func HandleNext(c *fiber.Ctx) error { - defer calc()() +func HandleHome(c *fiber.Ctx) error { + defer calc(c.OriginalURL())() + + res, status := FetchHome(c.Params("id")) - res, status := FetchNext(c.Params("id")) return c.Status(status).SendString(res) } +func HandleExplore(c *fiber.Ctx) error { + defer calc(c.OriginalURL())() + + res, status := FetchExplore() + + return c.Status(status).SendString(res) +} + +func HandleNext(c *fiber.Ctx) error { + defer calc(c.OriginalURL())() + + res, status := FetchNext(c.Params("id")) + + return c.Status(status).SendString(res) +} func HandleBrowse(c *fiber.Ctx) error { - defer calc()() + defer calc(c.OriginalURL())() id := c.Params("id") @@ -42,15 +60,22 @@ func HandleBrowse(c *fiber.Ctx) error { case id[:4] == "MPLY": res, status := FetchLyrics(id) return c.Status(status).SendString(res) + case id[:4] == "MPRE": + res, status := FetchAlbum(id) + return c.Status(status).SendString(res) + case id[:4] == "VLRD" || id[:2] == "RD": + res, status := FetchPlaylist(id) + return c.Status(status).SendString(res) default: - return c.SendString("{error: \"Invalid URL\"}") + return c.SendString("{\"error\": \"Invalid Browse URL\"}") } } - func HandleArtist(c *fiber.Ctx) error { - defer calc()() + defer calc(c.OriginalURL())() + res, status := FetchArtist(c.Params("id")) + return c.Status(status).SendString(res) } @@ -61,6 +86,9 @@ func main() { app.Use(recover.New()) app.Get("/healthz", HandleHealth) + app.Get("/home", HandleHome) + app.Get("/home/:id", HandleHome) + app.Get("/explore", HandleExplore) app.Get("/next/:id", HandleNext) app.Get("/browse/:id", HandleBrowse) app.Get("/channel/:id", HandleArtist) diff --git a/parser.go b/parser.go index 07de80d..6f7765b 100644 --- a/parser.go +++ b/parser.go @@ -57,98 +57,20 @@ func GetThumbnails(j gjson.Result) []Thumbnail { return t } -func GetSongs(s gjson.Result) []Item { - - r := []Item{} - - wg := sync.WaitGroup{} - - s.ForEach( - func(_, v gjson.Result) bool { - - wg.Add(1) - - go func() { - defer wg.Done() - - j := v.Get("musicResponsiveListItemRenderer") - flex := j.Get("flexColumns.#.musicResponsiveListItemFlexColumnRenderer.text.runs.0") - - r = append(r, Item{ - Id: j.Get("playlistItemData.videoId").String(), - Title: flex.Get("#(navigationEndpoint.watchEndpoint.videoId).text").String(), - Thumbnails: GetThumbnails(j.Get("thumbnail.musicThumbnailRenderer.thumbnail.thumbnails")), - }) - - }() - - wg.Wait() - - return true - }, - ) - - return r -} - -func GetTwoRowItem(t string, a gjson.Result) []Item { - - r := []Item{} - - var id string - - if t == "album" || t == "singles" { - id = "menu.menuRenderer.items.#(menuNavigationItemRenderer.text.runs.0.text == Shuffle play).menuNavigationItemRenderer.navigationEndpoint.watchPlaylistEndpoint.playlistId" - } else { - id = "navigationEndpoint.browseEndpoint.browseId" - } - - wg := sync.WaitGroup{} - - a.ForEach( - func(_, v gjson.Result) bool { - - wg.Add(1) - - go func() { - defer wg.Done() - - j := v.Get("musicTwoRowItemRenderer") - - r = append(r, Item{ - Id: j.Get(id).String(), - Title: RunsText(j.Get("title")), - Sub: RunsText(j.Get("subtitle")), - Thumbnails: GetThumbnails(j.Get("thumbnailRenderer.musicThumbnailRenderer.thumbnail.thumbnails")), - }) - - }() - - wg.Wait() - - return true - }, - ) - - return r -} - func GetNextSongs(n gjson.Result) []Item { r := []Item{} wg := sync.WaitGroup{} - n.ForEach( - func(_, v gjson.Result) bool { + n.Get("#.playlistPanelRenderer").ForEach( + func(_, j gjson.Result) bool { wg.Add(1) go func() { defer wg.Done() - j := v.Get("playlistPanelVideoRenderer") - r = append(r, Item{ Id: j.Get("navigationEndpoint.watchEndpoint.videoId").String(), Title: RunsText(j.Get("title")), @@ -166,6 +88,76 @@ func GetNextSongs(n gjson.Result) []Item { return r } +func ParseHome(raw string) (string, error) { + + j := gjson.Parse(raw) + + c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer") + + m := c.Get("contents") + n := c.Get("continuations.0.nextContinuationData.continuation").String() + + var body []map[string]interface{} + + m.ForEach( + func(key, value gjson.Result) bool { + + var v map[string]interface{} + + icsr := value.Get("musicImmersiveCarouselShelfRenderer") + csr := value.Get("musicCarouselShelfRenderer") + + switch { + case icsr.Exists(): + v = CarouselShelfRenderer(icsr) + case csr.Exists(): + v = CarouselShelfRenderer(csr) + default: + v = make(map[string]interface{}) + v["raw"] = value.Value() + } + + body = append(body, v) + + return true + }, + ) + + data := Home{ + Contents: body, + Continue: n, + } + + res, err := json.Marshal(data) + if err != nil { + return "", err + } + + return string(res), nil +} + +func ParseExplore(raw string) (string, error) { + + j := gjson.Parse(raw) + + c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents") + + a := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer.title.runs.0.text == New albums & singles).musicCarouselShelfRenderer") + t := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer.title.runs.0.text == Trending).musicCarouselShelfRenderer") + + val := Explore{ + Albums: TwoRowItemRenderer("album", a.Get("contents")), + Trending: ResponsiveListItemRenderer(t.Get("contents")), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + func ParseArtist(raw string) (string, error) { j := gjson.Parse(raw) @@ -186,10 +178,10 @@ func ParseArtist(raw string) (string, error) { BrowsePlaylistId: h.Get("playButton.buttonRenderer.navigationEndpoint.watchEndpoint.playlistId").String(), PlaylistId: s.Get("contents.0.musicResponsiveListItemRenderer.flexColumns.0.musicResponsiveListItemFlexColumnRenderer.text.runs.0.navigationEndpoint.watchEndpoint.playlistId").String(), Items: Items{ - Songs: GetSongs(s.Get("contents")), - Albums: GetTwoRowItem("album", a.Get("contents")), - Singles: GetTwoRowItem("singles", m.Get("contents")), - Artists: GetTwoRowItem("artist", u.Get("contents")), + Songs: ResponsiveListItemRenderer(s.Get("contents")), + Albums: TwoRowItemRenderer("album", a.Get("contents")), + Singles: TwoRowItemRenderer("singles", m.Get("contents")), + Artists: TwoRowItemRenderer("artist", u.Get("contents")), }, } @@ -229,6 +221,7 @@ func ParseNext(raw string) (string, error) { c := j.Get("contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer.tabs") m := j.Get("playerOverlays.playerOverlayRenderer.browserMediaSession.browserMediaSessionRenderer") + n := c.Get("#(tabRenderer.title == Up next).tabRenderer.content.musicQueueRenderer.content.playlistPanelRenderer") l := c.Get("#(tabRenderer.title == Lyrics).tabRenderer.endpoint.browseEndpoint.browseId") @@ -237,7 +230,6 @@ func ParseNext(raw string) (string, error) { MediaSession: MediaSession{ Album: RunsText(m.Get("album")), Thumbnails: GetThumbnails(m.Get("thumbnailDetails.thumbnails")), - }, Songs: GetNextSongs(n.Get("contents")), } diff --git a/structs.go b/structs.go index 81be638..c882040 100644 --- a/structs.go +++ b/structs.go @@ -11,6 +11,7 @@ type Item struct { Id string `json:"id"` Title string `json:"title"` Sub string `json:"subtitle"` + SubId string `json:"subId"` Thumbnails []Thumbnail `json:"thumbnails"` } @@ -21,6 +22,16 @@ type Items struct { Artists []Item `json:"recommendedArtists"` } +type Home struct { + Contents []map[string]interface{} `json:"contents"` + Continue string `json:"continue"` +} + +type Explore struct { + Albums []Item `json:"albums_and_singles"` + Trending []Item `json:"trending"` +} + type Artist struct { Title string `json:"title"` Description string `json:"description"` @@ -37,7 +48,7 @@ type MediaSession struct { } type Next struct { - LyricsId string `json:"lyricsId"` + LyricsId string `json:"lyricsId"` Songs []Item `json:"songs"` MediaSession MediaSession `json:"mediaSession"` } diff --git a/types.go b/types.go index a3a2f63..585e8ba 100644 --- a/types.go +++ b/types.go @@ -10,15 +10,24 @@ var BaseContext = Context{ } func GetTypeBrowse(t, id string) BrowseData { - return BrowseData{ - Context: BaseContext, - MusicConfig: BrowseMusicConfig{ - MusicConfig: PageType{ - PageType: "MUSIC_PAGE_TYPE_" + strings.ToUpper(t), + + if t != "" { + return BrowseData{ + Context: BaseContext, + MusicConfig: BrowseMusicConfig{ + MusicConfig: PageType{ + PageType: "MUSIC_PAGE_TYPE_" + strings.ToUpper(t), + }, }, - }, - BrowseId: id, + BrowseId: id, + } + } else { + return BrowseData{ + Context: BaseContext, + BrowseId: id, + } } + } func GetTypeNext(id string) NextData { diff --git a/utils.go b/utils.go index 13511ed..cfbc365 100644 --- a/utils.go +++ b/utils.go @@ -27,10 +27,10 @@ func ErrorMessage(err error) string { return fmt.Sprintf("{\"error\": \"%s\", \"message\": \"Please Report this error\"}", err) } -func calc() func() { +func calc(url string) func() { start := time.Now() return func() { - msg := fmt.Sprintf("Responded in %v\n", time.Since(start)) + msg := fmt.Sprintf("Responded %v in %v\n", url, time.Since(start)) fmt.Println(msg) } }