From b208d87309d78ddf7089f925b171e5d57f9cbeb1 Mon Sep 17 00:00:00 2001 From: Shiny Nematoda Date: Thu, 16 Mar 2023 17:26:12 +0000 Subject: [PATCH] Changes: - Proper structure for code - Support for musicCarouselShelfRenderer in genres --- .gitignore | 3 +- builder.go | 239 ----------------------------------- fetch.go | 203 ------------------------------ go.mod | 25 ++-- go.sum | 93 +++++++++++--- lib/artist.go | 86 +++++++++++++ lib/charts.go | 77 ++++++++++++ lib/common.go | 328 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/explore.go | 65 ++++++++++ lib/genre.go | 63 ++++++++++ lib/genres.go | 50 ++++++++ lib/lyrics.go | 49 ++++++++ lib/next.go | 124 +++++++++++++++++++ main.go | 59 ++++----- parser.go | 259 -------------------------------------- structs.go | 125 ------------------- types.go | 55 --------- utils.go | 56 --------- utils/fetch.go | 59 +++++++++ utils/types.go | 100 +++++++++++++++ 20 files changed, 1124 insertions(+), 994 deletions(-) delete mode 100644 builder.go delete mode 100644 fetch.go create mode 100644 lib/artist.go create mode 100644 lib/charts.go create mode 100644 lib/common.go create mode 100644 lib/explore.go create mode 100644 lib/genre.go create mode 100644 lib/genres.go create mode 100644 lib/lyrics.go create mode 100644 lib/next.go delete mode 100644 parser.go delete mode 100644 structs.go delete mode 100644 types.go delete mode 100644 utils.go create mode 100644 utils/fetch.go create mode 100644 utils/types.go diff --git a/.gitignore b/.gitignore index 6bcf0e5..1c434c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ log.txt -*.log \ No newline at end of file +*.log +app \ No newline at end of file diff --git a/builder.go b/builder.go deleted file mode 100644 index 5319446..0000000 --- a/builder.go +++ /dev/null @@ -1,239 +0,0 @@ -package main - -import ( - "fmt" - "log" - "sync" - - "github.com/tidwall/gjson" -) - -func GetThumbnails(j gjson.Result) []Thumbnail { - - t := []Thumbnail{} - - wg := sync.WaitGroup{} - - j.ForEach( - func(_, v gjson.Result) bool { - - wg.Add(1) - - go func() { - defer wg.Done() - - u, err := ParseUrl(v.Get("url").String()) - if err != nil { - log.Fatal(err) - } - - t = append(t, Thumbnail{ - Url: u, - Width: v.Get("width").Int(), - Height: v.Get("height").Int(), - }) - }() - - wg.Wait() - - return true - }, - ) - - return t -} - -func TwoRowItemRenderer(t string, a gjson.Result) []Item { - - r := []Item{} - - var id string - - if t == "album" { - 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() - - gid := j.Get(id).String() - - if len(gid) > 2 && gid[:2] == "VL" { - gid = gid[2:] - } - - r = append(r, Item{ - Id: gid, - 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 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" + - ".browseId").Get("0").String(), - Sub: flex.Get("#(navigationEndpoint.browseEndpoint" + - ".browseId).text").String(), - Thumbnails: GetThumbnails(j.Get("thumbnail.musicThumbnailRenderer" + - ".thumbnail.thumbnails")), - }) - - }() - - wg.Wait() - - return true - }, - ) - - return r -} - -func ResponsiveListItemRendererCH(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.text") - - r = append(r, Item{ - Id: j.Get("navigationEndpoint.browseEndpoint.browseId").String(), - Title: flex.Get("0").String() + " • " + flex.Get("1").String(), - Thumbnails: GetThumbnails(j.Get("thumbnail.musicThumbnailRenderer" + - ".thumbnail.thumbnails")), - }) - - }() - - wg.Wait() - - return true - }, - ) - - return r -} - -func NavigationButton(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("musicNavigationButtonRenderer") - - color := j.Get("solid.leftStripeColor").Uint() & 0xffffff - - r = append(r, Item{ - Id: j.Get("clickCommand.browseEndpoint.params").String(), - Title: RunsText(j.Get("buttonText")), - Sub: fmt.Sprintf("#%06x", color), - }) - }() - - wg.Wait() - - return true - }, - ) - - return r -} - -func MultiSelectMenuItemRenderer(j, ref gjson.Result) []Item { - - r := []Item{} - - wg := sync.WaitGroup{} - - j.ForEach( - func(_, v gjson.Result) bool { - - wg.Add(1) - - go func() { - defer wg.Done() - - steg := v.Get("formItemEntityKey").String() - id := ref.Get("#(entityKey == " + steg + ")" + - ".payload.musicFormBooleanChoice.opaqueToken") - - r = append(r, Item{ - Id: id.String(), - Title: RunsText(v.Get("title")), - }) - }() - - wg.Wait() - - return true - }, - ) - - return r -} diff --git a/fetch.go b/fetch.go deleted file mode 100644 index 17ca371..0000000 --- a/fetch.go +++ /dev/null @@ -1,203 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "io" - "log" - "net/http" - "strings" - - "github.com/tidwall/gjson" -) - -func Fetch(path string, data []byte) (string, int, error) { - - url := "https://music.youtube.com/youtubei/v1/" + path + "?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&prettyPrint=false" - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - return "", 500, err - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - req.Header.Set("Origin", "https://music.youtube.com") - req.Header.Set("x-origin", "https://music.youtube.com") - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36") - - client := &http.Client{} - - resp, err := client.Do(req) - if err != nil { - return "", 500, err - } - - defer resp.Body.Close() - - log.Println(resp.Status) - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", 500, err - } - - return string(body), resp.StatusCode, nil -} - -func FetchBrowse(browse BrowseData) (string, int) { - - data, err := json.Marshal(browse) - if err != nil { - return ErrorMessage(err), 500 - } - - raw, status, err := Fetch("browse", data) - if err != nil { - return ErrorMessage(err), 500 - } - - return raw, status - -} - -func FetchExplore() (string, int) { - - id := "FEmusic_explore" - - context := GetTypeBrowse("", id, "", "") - - raw, status := FetchBrowse(context) - - res, err := ParseExplore(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchGenres() (string, int) { - - id := "FEmusic_moods_and_genres" - - context := GetTypeBrowse("", id, "", "") - - raw, status := FetchBrowse(context) - - res, err := ParseGenres(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchGenre(param string) (string, int) { - - id := "FEmusic_moods_and_genres_category" - - context := GetTypeBrowse("", id, param, "") - - raw, status := FetchBrowse(context) - - res, err := ParseGenre(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchCharts(params, code string) (string, int) { - - id := "FEmusic_charts" - - context := GetTypeBrowse("", id, params, code) - - raw, status := FetchBrowse(context) - - res, err := ParseCharts(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchArtist(id string) (string, int) { - - context := GetTypeBrowse("artist", id, "", "") - - raw, status := FetchBrowse(context) - - res, err := ParseArtist(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchLyrics(id string) (string, int) { - - context := GetTypeBrowse("lyrics", id, "", "") - - raw, status := FetchBrowse(context) - - res, err := ParseLyrics(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} - -func FetchAlbum(id string) (string, int) { - - context := GetTypeBrowse("album", id, "", "") - - raw, status := FetchBrowse(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) { - - pldata, err := json.Marshal(GetTypeNext(id, "")) - if err != nil { - return ErrorMessage(err), 500 - } - - plraw, plstatus, err := Fetch("next", pldata) - if err != nil || plstatus > 399 { - return ErrorMessage(err), plstatus - } - - pl := gjson.Parse(plraw).Get("contents." + - "singleColumnMusicWatchNextResultsRenderer." + - "tabbedRenderer.watchNextTabbedResultsRenderer.tabs.0.tabRenderer.content." + - "musicQueueRenderer.content.playlistPanelRenderer.contents." + - "#(automixPreviewVideoRenderer).automixPreviewVideoRenderer." + - "content.automixPlaylistVideoRenderer.navigationEndpoint." + - "watchPlaylistEndpoint.playlistId").String() - - data, err := json.Marshal(GetTypeNext(id, pl)) - if err != nil { - return ErrorMessage(err), 500 - } - - raw, status, err := Fetch("next", data) - - res, err := ParseNext(raw) - if err != nil { - return ErrorMessage(err), 500 - } - - return res, status -} diff --git a/go.mod b/go.mod index b0b4c1e..8bc49bc 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,28 @@ module codeberg.org/Hyperpipe/hyperpipe-backend -go 1.18 +go 1.20 require ( - github.com/gofiber/fiber/v2 v2.31.0 - github.com/tidwall/gjson v1.14.0 + github.com/gofiber/fiber/v2 v2.42.0 + github.com/tidwall/gjson v1.14.4 ) require ( - github.com/andybalholm/brotli v1.0.4 // indirect - github.com/klauspost/compress v1.15.0 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tinylib/msgp v1.1.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.34.0 // indirect + github.com/valyala/fasthttp v1.45.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index ac46bad..52044a6 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,87 @@ -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow= -github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= -github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= -github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8= +github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= -github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/lib/artist.go b/lib/artist.go new file mode 100644 index 0000000..2205f26 --- /dev/null +++ b/lib/artist.go @@ -0,0 +1,86 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Artist struct { + Title string `json:"title"` + Description string `json:"description,omitempty"` + BrowsePlaylistId string `json:"browsePlaylistId,omitempty"` + PlaylistId string `json:"playlistId,omitempty"` + SubscriberCount string `json:"subscriberCount,omitempty"` + Thumbnails []Thumbnail `json:"thumbnails"` + Items Items `json:"items"` +} + +func parseArtist(raw string) (string, error) { + + j := gjson.Parse(raw) + + h := j.Get("header.musicImmersiveHeaderRenderer") + c := j.Get( + "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents", + ) + + s := c.Get("#(musicShelfRenderer.title.runs.0.text == Songs).musicShelfRenderer") + + a := c.Get( + "#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer", + ).Get("title.runs.0.text == Albums).musicCarouselShelfRenderer") + + m := c.Get( + "#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer", + ).Get("title.runs.0.text == Singles).musicCarouselShelfRenderer") + + u := c.Get( + "#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer", + ).Get("title.runs.0.text == Fans might also like).musicCarouselShelfRenderer") + + val := Artist{ + Title: RunsText(h.Get("title")), + Description: RunsText(h.Get("description")), + SubscriberCount: RunsText( + h.Get("subscriptionButton.subscribeButtonRenderer.subscriberCountText"), + ), + Thumbnails: GetThumbnails( + h.Get("thumbnail.musicThumbnailRenderer.thumbnail.thumbnails"), + ), + BrowsePlaylistId: h.Get( + "playButton.buttonRenderer.navigationEndpoint.watchEndpoint.playlistId", + ).String(), + PlaylistId: s.Get( + "contents.0.musicResponsiveListItemRenderer.flexColumns.0.musicResponsiveListItemFlexColumnRenderer", + ).Get("text.runs.0.navigationEndpoint.watchEndpoint.playlistId").String(), + Items: Items{ + Songs: ResponsiveListItemRenderer(s.Get("contents")), + Albums: TwoRowItemRenderer(a.Get("contents"), true), + Singles: TwoRowItemRenderer(m.Get("contents"), true), + Artists: TwoRowItemRenderer(u.Get("contents"), false), + }, + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetArtist(id string) (string, int) { + + context := utils.TypeBrowse("artist", id, "", "") + + raw, status := utils.FetchBrowse(context) + + res, err := parseArtist(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/charts.go b/lib/charts.go new file mode 100644 index 0000000..c042a59 --- /dev/null +++ b/lib/charts.go @@ -0,0 +1,77 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Options struct { + Default string `json:"default"` + All []Item `json:"all"` +} + +type Charts struct { + Options Options `json:"options"` + Artists []Item `json:"artists"` + Trending []Item `json:"trending"` +} + +func parseCharts(raw string) (string, error) { + + j := gjson.Parse(raw) + + c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0").Get("tabRenderer.content.sectionListRenderer.contents") + + o := c.Get( + "0.musicShelfRenderer.subheaders.0.musicSideAlignedItemRenderer", + ).Get( + "startItems.0.musicSortFilterButtonRenderer", + ) + + a := c.Get( + "#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer", + ).Get( + "title.runs.0.text == Top artists).musicCarouselShelfRenderer.contents", + ) + + t := c.Get( + "#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer", + ).Get("title.runs.0.text == Trending).musicCarouselShelfRenderer.contents") + + opts := o.Get( + "menu.musicMultiSelectMenuRenderer.options.#.musicMultiSelectMenuItemRenderer", + ) + ref := j.Get("frameworkUpdates.entityBatchUpdate.mutations") + + val := Charts{ + Options: Options{ + Default: RunsText(o.Get("title")), + All: MultiSelectMenuItemRenderer(opts, ref), + }, + Artists: ResponsiveListItemRendererCH(a), + Trending: ResponsiveListItemRenderer(t), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetCharts(params, code string) (string, int) { + + context := utils.TypeBrowse("", "FEmusic_charts", params, code) + + raw, status := utils.FetchBrowse(context) + + res, err := parseCharts(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/common.go b/lib/common.go new file mode 100644 index 0000000..1e6b52d --- /dev/null +++ b/lib/common.go @@ -0,0 +1,328 @@ +package lib + +import ( + "fmt" + "log" + "net/url" + "os" + "strings" + "sync" + + "github.com/tidwall/gjson" +) + +type Thumbnail struct { + Height int64 `json:"height"` + Width int64 `json:"width"` + Url string `json:"url"` +} + +type Item struct { + Id string `json:"id"` + Title string `json:"title"` + Sub string `json:"subtitle,omitempty"` + SubId string `json:"subId,omitempty"` + Thumbnails []Thumbnail `json:"thumbnails,omitempty"` +} + +type Items struct { + Songs []Item `json:"songs"` + Albums []Item `json:"albums"` + Singles []Item `json:"singles"` + Artists []Item `json:"recommendedArtists"` +} + +func RunsText(j gjson.Result) string { + + var s []string + + a := j.Get("runs.#.text").Array() + + for i := 0; i < len(a); i++ { + s = append(s, a[i].String()) + } + + return strings.Join(s, "") +} + +func parseUrl(raw string) (string, error) { + + u, err := url.Parse(raw) + if err != nil { + return "", err + } + + prehost := u.Host + + u.Scheme = "https" + u.Host = os.Getenv("HYP_PROXY") + + q := u.Query() + q.Set("host", prehost) + + return u.String(), nil +} + +func GetThumbnails(j gjson.Result) []Thumbnail { + + t := make([]Thumbnail, j.Get("#").Int()) + + wg := sync.WaitGroup{} + + j.ForEach( + func(n, j gjson.Result) bool { + + wg.Add(1) + + go func(i int64, v gjson.Result) { + defer wg.Done() + + u, err := parseUrl(v.Get("url").String()) + if err != nil { + log.Fatal(err) + } + + t[i] = Thumbnail{ + Url: u, + Width: v.Get("width").Int(), + Height: v.Get("height").Int(), + } + }(n.Int(), j) + + return true + }, + ) + + wg.Wait() + + return t +} + +func TwoRowItemRenderer(a gjson.Result, t bool) []Item { + + var id string + + if t { + 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") + + r := make([]Item, v.Get("#").Int()) + + v.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(i int64, j gjson.Result) { + defer wg.Done() + + gid := j.Get(id).String() + + if len(gid) > 2 && gid[:2] == "VL" { + gid = gid[2:] + } + + r[i] = Item{ + Id: gid, + Title: RunsText(j.Get("title")), + Sub: RunsText(j.Get("subtitle")), + Thumbnails: GetThumbnails( + j.Get("thumbnailRenderer.musicThumbnailRenderer.thumbnail.thumbnails"), + ), + } + + }(n.Int(), s) + + return true + }, + ) + + wg.Wait() + + return r +} + +func ResponsiveListItemRenderer(s gjson.Result) []Item { + + r := make([]Item, s.Get("#").Int()) + + wg := sync.WaitGroup{} + + s.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(i int64, v gjson.Result) { + defer wg.Done() + + j := v.Get("musicResponsiveListItemRenderer") + flex := j.Get("flexColumns.#.musicResponsiveListItemFlexColumnRenderer" + + ".text.runs.0") + + r[i] = Item{ + Id: j.Get("playlistItemData.videoId").String(), + Title: flex.Get("#(navigationEndpoint.watchEndpoint.videoId).text").String(), + SubId: flex.Get( + "#.navigationEndpoint.browseEndpoint.browseId.0", + ).String(), + Sub: flex.Get( + "#(navigationEndpoint.browseEndpoint.browseId).text", + ).String(), + Thumbnails: GetThumbnails( + j.Get("thumbnail.musicThumbnailRenderer.thumbnail.thumbnails"), + ), + } + + }(n.Int(), s) + + return true + }, + ) + + wg.Wait() + + return r +} + +func ResponsiveListItemRendererCH(s gjson.Result) []Item { + + r := make([]Item, s.Get("#").Int()) + + wg := sync.WaitGroup{} + + s.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(i int64, v gjson.Result) { + defer wg.Done() + + j := v.Get("musicResponsiveListItemRenderer") + flex := j.Get("flexColumns.#.musicResponsiveListItemFlexColumnRenderer" + + ".text.runs.0.text") + + r[i] = Item{ + Id: j.Get("navigationEndpoint.browseEndpoint.browseId").String(), + Title: flex.Get("0").String() + " • " + flex.Get("1").String(), + Thumbnails: GetThumbnails( + j.Get("thumbnail.musicThumbnailRenderer.thumbnail.thumbnails"), + ), + } + + }(n.Int(), s) + + return true + }, + ) + + wg.Wait() + + return r +} + +func NavigationButton(s gjson.Result) []Item { + + r := make([]Item, s.Get("#").Int()) + + wg := sync.WaitGroup{} + + s.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(i int64, v gjson.Result) { + defer wg.Done() + + j := v.Get("musicNavigationButtonRenderer") + + color := j.Get("solid.leftStripeColor").Uint() & 0xffffff + + r[i] = Item{ + Id: j.Get("clickCommand.browseEndpoint.params").String(), + Title: RunsText(j.Get("buttonText")), + Sub: fmt.Sprintf("#%06x", color), + } + }(n.Int(), s) + + return true + }, + ) + + wg.Wait() + + return r +} + +func MultiSelectMenuItemRenderer(j, ref gjson.Result) []Item { + + r := make([]Item, j.Get("#").Int()) + + wg := sync.WaitGroup{} + + j.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(i int64, v gjson.Result) { + defer wg.Done() + + steg := v.Get("formItemEntityKey").String() + + id := ref.Get( + "#(entityKey == " + steg + ")", + ).Get(".payload.musicFormBooleanChoice.opaqueToken") + + r[i] = Item{ + Id: id.String(), + Title: RunsText(v.Get("title")), + } + }(n.Int(), s) + + return true + }, + ) + + wg.Wait() + + return r +} + +func ShelfRenderer(j gjson.Result) map[string]interface{} { + + r := make(map[string]interface{}) + + wg := sync.WaitGroup{} + + j.ForEach( + func(n, s gjson.Result) bool { + + wg.Add(1) + + go func(v gjson.Result) { + defer wg.Done() + + title := RunsText( + v.Get("header.musicCarouselShelfBasicHeaderRenderer.title"), + ) + + r[title] = TwoRowItemRenderer(v.Get("contents"), false) + }(s) + + return true + }, + ) + + wg.Wait() + + return r +} diff --git a/lib/explore.go b/lib/explore.go new file mode 100644 index 0000000..2d1a88c --- /dev/null +++ b/lib/explore.go @@ -0,0 +1,65 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Explore struct { + TrendingId string `json:"trendingId"` + ChartsId string `json:"chartsId"` + Albums []Item `json:"albums_and_singles"` + Trending []Item `json:"trending"` +} + +func parseExplore(raw string) (string, error) { + + j := gjson.Parse(raw) + + m := j.Get( + "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents", + ) + + c := m.Get("#.musicCarouselShelfRenderer") + + a := c.Get( + "#(header.musicCarouselShelfBasicHeaderRenderer.title.runs.0.text == New albums & singles)", + ) + t := c.Get("#(header.musicCarouselShelfBasicHeaderRenderer.title.runs.0.text == Trending)") + + charts := m.Get( + "#(gridRenderer).gridRenderer.items.#.musicNavigationButtonRenderer", + ).Get("#(buttonText.runs.0.text == Charts)") + + val := Explore{ + TrendingId: t.Get( + "header.musicCarouselShelfBasicHeaderRenderer.title.runs.0.navigationEndpoint.browseEndpoint.browseId", + ).String(), + ChartsId: charts.Get("clickCommand.browseEndpoint.params").String(), + Albums: TwoRowItemRenderer(a.Get("contents"), true), + Trending: ResponsiveListItemRenderer(t.Get("contents")), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetExplore() (string, int) { + + context := utils.TypeBrowse("", "FEmusic_explore", "", "") + + raw, status := utils.FetchBrowse(context) + + res, err := parseExplore(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/genre.go b/lib/genre.go new file mode 100644 index 0000000..6e381d1 --- /dev/null +++ b/lib/genre.go @@ -0,0 +1,63 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Genre struct { + Title string `json:"title"` + Spotlight []Item `json:"spotlight"` + Featured []Item `json:"featured"` + Community []Item `json:"community"` + Shelf map[string]interface{} `json:"shelf"` +} + +func parseGenre(raw string) (string, error) { + j := gjson.Parse(raw) + + c := j.Get( + "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents", + ) + + g := c.Get("#.gridRenderer") + + var val Genre + + s := g.Get("#(header.gridHeaderRenderer.title.runs.0.text == Spotlight)") + f := g.Get("#(header.gridHeaderRenderer.title.runs.0.text == Featured playlists)") + cp := g.Get("#(header.gridHeaderRenderer.title.runs.0.text == Community playlists)") + + val = Genre{ + Title: RunsText(j.Get("header.musicHeaderRenderer.title")), + Spotlight: TwoRowItemRenderer(s.Get("items"), false), + Featured: TwoRowItemRenderer(f.Get("items"), false), + Community: TwoRowItemRenderer(cp.Get("items"), false), + Shelf: ShelfRenderer( + c.Get("#.musicCarouselShelfRenderer"), + ), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetGenre(param string) (string, int) { + + context := utils.TypeBrowse("", "FEmusic_moods_and_genres_category", param, "") + + raw, status := utils.FetchBrowse(context) + + res, err := parseGenre(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/genres.go b/lib/genres.go new file mode 100644 index 0000000..a382ce7 --- /dev/null +++ b/lib/genres.go @@ -0,0 +1,50 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Genres struct { + Moods []Item `json:"moods"` + Genres []Item `json:"genres"` +} + +func parseGenres(raw string) (string, error) { + + j := gjson.Parse(raw) + + c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer" + + ".content.sectionListRenderer.contents.#.gridRenderer") + + m := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Moods & moments)") + g := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Genres)") + + val := Genres{ + Moods: NavigationButton(m.Get("items")), + Genres: NavigationButton(g.Get("items")), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetGenres() (string, int) { + + context := utils.TypeBrowse("", "FEmusic_moods_and_genres", "", "") + + raw, status := utils.FetchBrowse(context) + + res, err := parseGenres(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/lyrics.go b/lib/lyrics.go new file mode 100644 index 0000000..b9ad93e --- /dev/null +++ b/lib/lyrics.go @@ -0,0 +1,49 @@ +package lib + +import ( + "encoding/json" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type Lyrics struct { + Text string `json:"text"` + Source string `json:"source"` +} + +func parseLyrics(raw string) (string, error) { + + j := gjson.Parse(raw) + + d := j.Get("contents.sectionListRenderer.contents.0.musicDescriptionShelfRenderer") + + l := d.Get("description") + s := d.Get("footer") + + val := Lyrics{ + Text: RunsText(l), + Source: RunsText(s), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetLyrics(id string) (string, int) { + + context := utils.TypeBrowse("lyrics", id, "", "") + + raw, status := utils.FetchBrowse(context) + + res, err := parseLyrics(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/lib/next.go b/lib/next.go new file mode 100644 index 0000000..4281c63 --- /dev/null +++ b/lib/next.go @@ -0,0 +1,124 @@ +package lib + +import ( + "encoding/json" + "sync" + + "codeberg.org/Hyperpipe/hyperpipe-backend/utils" + "github.com/tidwall/gjson" +) + +type MediaSession struct { + Thumbnails []Thumbnail `json:"thumbnails"` + Album string `json:"album"` +} + +type Next struct { + LyricsId string `json:"lyricsId"` + Songs []Item `json:"songs"` + MediaSession MediaSession `json:"mediaSession"` +} + +func parseNextSongs(n gjson.Result) []Item { + + np := n.Get("#.playlistPanelVideoRenderer") + + r := make([]Item, n.Get("#").Int()) + + wg := sync.WaitGroup{} + + np.ForEach( + func(n, v gjson.Result) bool { + + wg.Add(1) + + go func(i int64, j gjson.Result) { + defer wg.Done() + + r[i] = Item{ + Id: j.Get("navigationEndpoint.watchEndpoint.videoId").String(), + Title: RunsText(j.Get("title")), + Sub: j.Get("longBylineText.runs.2.text").String(), + Thumbnails: GetThumbnails(j.Get("thumbnail.thumbnails")), + } + }(n.Int(), v) + + return true + }, + ) + + wg.Wait() + + return r +} + +func parseNext(raw string) (string, error) { + + j := gjson.Parse(raw) + + c := j.Get( + "contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer.tabs", + ) + m := j.Get( + "playerOverlays.playerOverlayRenderer.browserMediaSession.browserMediaSessionRenderer", + ) + + n := c.Get( + "#(tabRenderer.title == Up next).tabRenderer.content", + ).Get("musicQueueRenderer.content.playlistPanelRenderer") + + l := c.Get( + "#(tabRenderer.title == Lyrics).tabRenderer.endpoint.browseEndpoint.browseId", + ) + + val := Next{ + LyricsId: l.String(), + MediaSession: MediaSession{ + Album: RunsText(m.Get("album")), + Thumbnails: GetThumbnails(m.Get("thumbnailDetails.thumbnails")), + }, + Songs: parseNextSongs(n.Get("contents")), + } + + res, err := json.Marshal(val) + if err != nil { + return "", err + } + + return string(res), nil +} + +func GetNext(id string) (string, int) { + + pldata, err := json.Marshal(utils.TypeNext(id, "")) + if err != nil { + return utils.ErrMsg(err), 500 + } + + plraw, plstatus, err := utils.Fetch("next", pldata) + if err != nil || plstatus > 399 { + return utils.ErrMsg(err), plstatus + } + + pl := gjson.Parse(plraw).Get( + "contents.singleColumnMusicWatchNextResultsRenderer." + + "tabbedRenderer.watchNextTabbedResultsRenderer.tabs.0.tabRenderer.content." + + "musicQueueRenderer.content.playlistPanelRenderer.contents." + + "#(automixPreviewVideoRenderer).automixPreviewVideoRenderer." + + "content.automixPlaylistVideoRenderer.navigationEndpoint." + + "watchPlaylistEndpoint.playlistId").String() + + data, err := json.Marshal(utils.TypeNext(id, pl)) + if err != nil { + return utils.ErrMsg(err), 500 + } + + raw, status, err := utils.Fetch("next", data) + + res, err := parseNext(raw) + if err != nil { + return utils.ErrMsg(err), 500 + } + + return res, status +} diff --git a/main.go b/main.go index 517677a..0269e88 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,12 @@ package main import ( "fmt" + "os" + + "codeberg.org/Hyperpipe/hyperpipe-backend/lib" + "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/recover" - "os" ) func SetHeaders(c *fiber.Ctx) error { @@ -17,87 +20,84 @@ func SetHeaders(c *fiber.Ctx) error { } func HandleHealth(c *fiber.Ctx) error { - defer calc()() - fmt.Println("Health Check!!") return c.SendStatus(204) } func HandleExplore(c *fiber.Ctx) error { - defer calc()() - - res, status := FetchExplore() + res, status := lib.GetExplore() return c.Status(status).SendString(res) } func HandleNext(c *fiber.Ctx) error { - defer calc()() - - res, status := FetchNext(c.Params("id")) + res, status := lib.GetNext(c.Params("id")) return c.Status(status).SendString(res) } func HandleGenres(c *fiber.Ctx) error { - defer calc()() - - res, status := FetchGenres() + res, status := lib.GetGenres() return c.Status(status).SendString(res) } func HandleGenre(c *fiber.Ctx) error { - defer calc()() - - res, status := FetchGenre(c.Params("id")) + res, status := lib.GetGenre(c.Params("id")) return c.Status(status).SendString(res) } func HandleCharts(c *fiber.Ctx) error { - defer calc()() + res, status := lib.GetCharts(c.Query("params"), c.Query("code")) - res, status := FetchCharts(c.Query("params"), c.Query("code")) + return c.Status(status).SendString(res) +} + +func HandleLyrics(c *fiber.Ctx) error { + res, status := lib.GetLyrics(c.Params("id")) return c.Status(status).SendString(res) } func HandleBrowse(c *fiber.Ctx) error { - defer calc()() + + // Will be removed, Do not use id := c.Params("id") if len(id) < 4 { - return c.Status(500).SendString("{\"error\": \"Browse Id is too Short\"}") + return c.Status(500).SendString("{\"error\": \"browse id is too short\"}") } switch { case id[:2] == "UC": - res, status := FetchArtist(id) + res, status := lib.GetArtist(id) return c.Status(status).SendString(res) case id[:4] == "MPLY": - res, status := FetchLyrics(id) - return c.Status(status).SendString(res) - case id[:4] == "MPRE": - res, status := FetchAlbum(id) + res, status := lib.GetLyrics(id) return c.Status(status).SendString(res) default: - return c.Status(500).SendString("{\"error\": \"Invalid Browse URL\"}") + return c.Status(500).SendString("{\"error\": \"URL not supported\"}") } } func HandleArtist(c *fiber.Ctx) error { - defer calc()() - - res, status := FetchArtist(c.Params("id")) + res, status := lib.GetArtist(c.Params("id")) return c.Status(status).SendString(res) } func main() { - app := fiber.New() + if os.Getenv("HYP_PROXY") == "" { + fmt.Println("HYP_PROXY is empty!") + os.Exit(1) + } + + app := fiber.New(fiber.Config{ + Prefork: true, + }) app.Use(SetHeaders) app.Use(recover.New()) @@ -108,6 +108,7 @@ func main() { app.Get("/genres/:id", HandleGenre) app.Get("/charts", HandleCharts) app.Get("/next/:id", HandleNext) + app.Get("/lyrics/:id", HandleLyrics) app.Get("/browse/:id", HandleBrowse) app.Get("/channel/:id", HandleArtist) diff --git a/parser.go b/parser.go deleted file mode 100644 index d410eb3..0000000 --- a/parser.go +++ /dev/null @@ -1,259 +0,0 @@ -package main - -import ( - "encoding/json" - "sync" - - "github.com/tidwall/gjson" -) - -func GetNextSongs(n gjson.Result) []Item { - - r := []Item{} - - wg := sync.WaitGroup{} - - n.Get("#.playlistPanelVideoRenderer").ForEach( - func(_, j gjson.Result) bool { - - wg.Add(1) - - go func() { - defer wg.Done() - - r = append(r, Item{ - Id: j.Get("navigationEndpoint.watchEndpoint.videoId").String(), - Title: RunsText(j.Get("title")), - Sub: j.Get("longBylineText.runs.2.text").String(), - Thumbnails: GetThumbnails(j.Get("thumbnail.thumbnails")), - }) - }() - - wg.Wait() - - return true - }, - ) - - return r -} - -func ParseExplore(raw string) (string, error) { - - j := gjson.Parse(raw) - - m := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0" + - ".tabRenderer.content.sectionListRenderer.contents") - - c := m.Get("#.musicCarouselShelfRenderer") - - a := c.Get("#(header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == New albums & singles)") - t := c.Get("#(header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Trending)") - - charts := m.Get("#(gridRenderer).gridRenderer.items.#" + - ".musicNavigationButtonRenderer").Get("#(buttonText" + - ".runs.0.text == Charts)") - - val := Explore{ - TrendingId: t.Get("header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.navigationEndpoint.browseEndpoint.browseId").String(), - ChartsId: charts.Get("clickCommand.browseEndpoint.params").String(), - 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 ParseGenres(raw string) (string, error) { - - j := gjson.Parse(raw) - - c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer" + - ".content.sectionListRenderer.contents.#.gridRenderer") - - m := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Moods & moments)") - g := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Genres)") - - val := Genres{ - Moods: NavigationButton(m.Get("items")), - Genres: NavigationButton(g.Get("items")), - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} - -func ParseGenre(raw string) (string, error) { - - j := gjson.Parse(raw) - - c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer" + - ".content.sectionListRenderer.contents.#.gridRenderer") - - s := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Spotlight)") - f := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Featured playlists)") - cp := c.Get("#(header.gridHeaderRenderer.title.runs.0.text == Community playlists)") - - val := Genre{ - Title: RunsText(j.Get("header.musicHeaderRenderer.title")), - Spotlight: TwoRowItemRenderer("", s.Get("items")), - Featured: TwoRowItemRenderer("", f.Get("items")), - Community: TwoRowItemRenderer("", cp.Get("items")), - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} - -func ParseCharts(raw string) (string, error) { - - j := gjson.Parse(raw) - - c := j.Get("contents.singleColumnBrowseResultsRenderer" + - ".tabs.0.tabRenderer.content.sectionListRenderer.contents") - - o := c.Get("0.musicShelfRenderer.subheaders.0" + - ".musicSideAlignedItemRenderer.startItems.0" + - ".musicSortFilterButtonRenderer") - - a := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Top artists)" + - ".musicCarouselShelfRenderer.contents") - - t := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Trending)" + - ".musicCarouselShelfRenderer.contents") - - opts := o.Get("menu.musicMultiSelectMenuRenderer.options" + - ".#.musicMultiSelectMenuItemRenderer") - ref := j.Get("frameworkUpdates.entityBatchUpdate.mutations") - - val := Charts{ - Options: Options{ - Default: RunsText(o.Get("title")), - All: MultiSelectMenuItemRenderer(opts, ref), - }, - Artists: ResponsiveListItemRendererCH(a), - Trending: ResponsiveListItemRenderer(t), - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} - -func ParseArtist(raw string) (string, error) { - - j := gjson.Parse(raw) - - h := j.Get("header.musicImmersiveHeaderRenderer") - c := j.Get("contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content" + - ".sectionListRenderer.contents") - - s := c.Get("#(musicShelfRenderer.title.runs.0.text == Songs).musicShelfRenderer") - a := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Albums).musicCarouselShelfRenderer") - m := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Singles).musicCarouselShelfRenderer") - u := c.Get("#(musicCarouselShelfRenderer.header.musicCarouselShelfBasicHeaderRenderer" + - ".title.runs.0.text == Fans might also like).musicCarouselShelfRenderer") - - val := Artist{ - Title: RunsText(h.Get("title")), - Description: RunsText(h.Get("description")), - SubscriberCount: RunsText(h.Get("subscriptionButton" + - ".subscribeButtonRenderer.subscriberCountText")), - Thumbnails: GetThumbnails(h.Get("thumbnail.musicThumbnailRenderer" + - ".thumbnail.thumbnails")), - 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: ResponsiveListItemRenderer(s.Get("contents")), - Albums: TwoRowItemRenderer("album", a.Get("contents")), - Singles: TwoRowItemRenderer("album", m.Get("contents")), - Artists: TwoRowItemRenderer("", u.Get("contents")), - }, - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} - -func ParseLyrics(raw string) (string, error) { - - j := gjson.Parse(raw) - - d := j.Get("contents.sectionListRenderer.contents.0.musicDescriptionShelfRenderer") - - l := d.Get("description") - s := d.Get("footer") - - val := Lyrics{ - Text: RunsText(l), - Source: RunsText(s), - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} - -func ParseNext(raw string) (string, error) { - - j := gjson.Parse(raw) - - 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") - - val := Next{ - LyricsId: l.String(), - MediaSession: MediaSession{ - Album: RunsText(m.Get("album")), - Thumbnails: GetThumbnails(m.Get("thumbnailDetails.thumbnails")), - }, - Songs: GetNextSongs(n.Get("contents")), - } - - res, err := json.Marshal(val) - if err != nil { - return "", err - } - - return string(res), nil -} diff --git a/structs.go b/structs.go deleted file mode 100644 index a970762..0000000 --- a/structs.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -// Parsers -type Thumbnail struct { - Height int64 `json:"height"` - Width int64 `json:"width"` - Url string `json:"url"` -} - -type Item struct { - Id string `json:"id"` - Title string `json:"title"` - Sub string `json:"subtitle,omitempty"` - SubId string `json:"subId,omitempty"` - Thumbnails []Thumbnail `json:"thumbnails,omitempty"` -} - -type Items struct { - Songs []Item `json:"songs"` - Albums []Item `json:"albums"` - Singles []Item `json:"singles"` - Artists []Item `json:"recommendedArtists"` -} - -type Explore struct { - TrendingId string `json:"trendingId"` - ChartsId string `json:"chartsId"` - Albums []Item `json:"albums_and_singles"` - Trending []Item `json:"trending"` -} - -type Genres struct { - Moods []Item `json:"moods"` - Genres []Item `json:"genres"` -} - -type Genre struct { - Title string `json:"title"` - Spotlight []Item `json:"spotlight"` - Featured []Item `json:"featured"` - Community []Item `json:"community"` -} - -type Options struct { - Default string `json:"default"` - All []Item `json:"all"` -} - -type Charts struct { - Options Options `json:"options"` - Artists []Item `json:"artists"` - Trending []Item `json:"trending"` -} - -type Artist struct { - Title string `json:"title"` - Description string `json:"description,omitempty"` - BrowsePlaylistId string `json:"browsePlaylistId,omitempty"` - PlaylistId string `json:"playlistId,omitempty"` - SubscriberCount string `json:"subscriberCount,omitempty"` - Thumbnails []Thumbnail `json:"thumbnails"` - Items Items `json:"items"` -} - -type MediaSession struct { - Thumbnails []Thumbnail `json:"thumbnails"` - Album string `json:"album"` -} - -type Next struct { - LyricsId string `json:"lyricsId"` - Songs []Item `json:"songs"` - MediaSession MediaSession `json:"mediaSession"` -} - -type Lyrics struct { - Text string `json:"text"` - Source string `json:"source"` -} - -// Structs and Types -type Client struct { - Name string `json:"clientName,omitempty"` - Version string `json:"clientVersion,omitempty"` - Hl string `json:"hl",omitempty` -} - -type Context struct { - Client Client `json:"client",omitempty` -} - -type PageType struct { - PageType string `json:"pageType,omitempty"` -} - -type BrowseMusicConfig struct { - MusicConfig PageType `json:"browseEndpointContextMusicConfig,omitempty"` -} - -type Form struct { - Values []string `json:"selectedValues"` -} - -type WatchMusicConfig struct { - Panel bool `json:"hasPersistentPlaylistPanel,omitempty"` - Type string `json:"musicVideoType,omitempty"` -} - -type BrowseData struct { - Context Context `json:"context,omitempty"` - MusicConfig BrowseMusicConfig `json:"browseEndpointContextMusicConfig,omitempty"` - BrowseId string `json:"browseId,omitempty"` - Params string `json:"params,omitempty"` - Form Form `json:"formData,omitempty"` -} - -type NextData struct { - Id string `json:"videoId,omitempty"` - PlaylistId string `json:"playlistId",omitempty` - Context Context `json:"context,omitempty"` - Audio bool `json:"isAudioOnly,omitempty"` - Tuner string `json:"tunerSettingValue,omitempty"` - Panel bool `json:"enablePersistentPlaylistPanel,omitempty"` - MusicConfig WatchMusicConfig `json:"watchEndpointMusicConfig,omitempty"` -} diff --git a/types.go b/types.go deleted file mode 100644 index 2f992d9..0000000 --- a/types.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import "strings" - -var BaseContext Context = Context{ - Client: Client{ - Name: "WEB_REMIX", - Version: "1.20220926.01.00", - Hl: "en-US", - }, -} - -func GetTypeBrowse(t, id, params, form string) BrowseData { - if t != "" { - return BrowseData{ - Context: BaseContext, - MusicConfig: BrowseMusicConfig{ - MusicConfig: PageType{ - PageType: "MUSIC_PAGE_TYPE_" + strings.ToUpper(t), - }, - }, - BrowseId: id, - } - } else if form != "" { - return BrowseData{ - Context: BaseContext, - BrowseId: id, - Params: params, - Form: Form{ - Values: []string{form}, - }, - } - } else { - return BrowseData{ - Context: BaseContext, - BrowseId: id, - Params: params, - } - } -} - -func GetTypeNext(id, pid string) NextData { - return NextData{ - Id: id, - PlaylistId: pid, - Context: BaseContext, - Panel: true, - Audio: true, - Tuner: "AUTOMIX_SETTING_NORMAL", - MusicConfig: WatchMusicConfig{ - Panel: true, - Type: "MUSIC_VIDEO_TYPE_ATV", - }, - } -} diff --git a/utils.go b/utils.go deleted file mode 100644 index 1aad898..0000000 --- a/utils.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "fmt" - "github.com/tidwall/gjson" - "net/url" - "os" - "strings" - "time" -) - -func RunsText(j gjson.Result) string { - - var s []string - - a := j.Get("runs.#.text").Array() - - for i := 0; i < len(a); i++ { - s = append(s, a[i].String()) - } - - return strings.Join(s, "") -} - -func ParseUrl(raw string) (string, error) { - - u, err := url.Parse(raw) - if err != nil { - return "", err - } - - prehost := u.Host - - u.Scheme = "https" - u.Host = string(os.Getenv("HYP_PROXY")) - - q := u.Query() - q.Set("host", prehost) - u.RawQuery = q.Encode() - - return u.String(), nil -} - -func ErrorMessage(err error) string { - data := url.QueryEscape(err.Error()) - fmt.Println(err) - return fmt.Sprintf("{\"error\":\"%s\",\"message\":\"Got Error: %s\"}", data) -} - -func calc() func() { - start := time.Now() - return func() { - msg := fmt.Sprintf("Responded in %v\n", time.Since(start)) - fmt.Println(msg) - } -} diff --git a/utils/fetch.go b/utils/fetch.go new file mode 100644 index 0000000..0f2d48c --- /dev/null +++ b/utils/fetch.go @@ -0,0 +1,59 @@ +package utils + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +func ErrMsg(err error) string { + return fmt.Sprintf("{\"error\":true,\"message\":\"%s\"}", err) +} + +func Fetch(path string, data []byte) (string, int, error) { + + url := "https://music.youtube.com/youtubei/v1/" + path + "?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&prettyPrint=false" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + return "", 500, err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Origin", "https://music.youtube.com") + req.Header.Set("x-origin", "https://music.youtube.com") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36") + + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + return "", 500, err + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", 500, err + } + + return string(body), resp.StatusCode, nil +} + +func FetchBrowse(browse BrowseData) (string, int) { + + data, err := json.Marshal(browse) + if err != nil { + return ErrMsg(err), 500 + } + + raw, status, err := Fetch("browse", data) + if err != nil { + return ErrMsg(err), 500 + } + + return raw, status +} diff --git a/utils/types.go b/utils/types.go new file mode 100644 index 0000000..db4642d --- /dev/null +++ b/utils/types.go @@ -0,0 +1,100 @@ +package utils + +import "strings" + +type Client struct { + Name string `json:"clientName,omitempty"` + Version string `json:"clientVersion,omitempty"` + Hl string `json:"hl",omitempty` +} + +type Context struct { + Client Client `json:"client",omitempty` +} + +type PageType struct { + PageType string `json:"pageType,omitempty"` +} + +type BrowseMusicConfig struct { + MusicConfig PageType `json:"browseEndpointContextMusicConfig,omitempty"` +} + +type Form struct { + Values []string `json:"selectedValues"` +} + +type WatchMusicConfig struct { + Panel bool `json:"hasPersistentPlaylistPanel,omitempty"` + Type string `json:"musicVideoType,omitempty"` +} + +type BrowseData struct { + Context Context `json:"context,omitempty"` + MusicConfig BrowseMusicConfig `json:"browseEndpointContextMusicConfig,omitempty"` + BrowseId string `json:"browseId,omitempty"` + Params string `json:"params,omitempty"` + Form Form `json:"formData,omitempty"` +} + +type NextData struct { + Id string `json:"videoId,omitempty"` + PlaylistId string `json:"playlistId",omitempty` + Context Context `json:"context,omitempty"` + Audio bool `json:"isAudioOnly,omitempty"` + Tuner string `json:"tunerSettingValue,omitempty"` + Panel bool `json:"enablePersistentPlaylistPanel,omitempty"` + MusicConfig WatchMusicConfig `json:"watchEndpointMusicConfig,omitempty"` +} + +var BaseContext Context = Context{ + Client: Client{ + Name: "WEB_REMIX", + Version: "1.20220926.01.00", + Hl: "en-US", + }, +} + +func TypeBrowse(t, id, params, form string) BrowseData { + if t != "" { + return BrowseData{ + Context: BaseContext, + MusicConfig: BrowseMusicConfig{ + MusicConfig: PageType{ + PageType: "MUSIC_PAGE_TYPE_" + strings.ToUpper(t), + }, + }, + BrowseId: id, + } + } else if form != "" { + return BrowseData{ + Context: BaseContext, + BrowseId: id, + Params: params, + Form: Form{ + Values: []string{form}, + }, + } + } else { + return BrowseData{ + Context: BaseContext, + BrowseId: id, + Params: params, + } + } +} + +func TypeNext(id, pid string) NextData { + return NextData{ + Id: id, + PlaylistId: pid, + Context: BaseContext, + Panel: true, + Audio: true, + Tuner: "AUTOMIX_SETTING_NORMAL", + MusicConfig: WatchMusicConfig{ + Panel: true, + Type: "MUSIC_VIDEO_TYPE_ATV", + }, + } +}