- Proper structure for code
- Support for musicCarouselShelfRenderer in genres
This commit is contained in:
Shiny Nematoda
2023-03-16 17:26:12 +00:00
parent aad917393d
commit b208d87309
20 changed files with 1124 additions and 994 deletions
+86
View File
@@ -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
}
+77
View File
@@ -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
}
+328
View File
@@ -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
}
+65
View File
@@ -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
}
+63
View File
@@ -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
}
+50
View File
@@ -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
}
+49
View File
@@ -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
}
+124
View File
@@ -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
}