mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-30 07:48:51 +02:00
- drop unused widgets (AnimatedStateSwitcher, GridSkeleton, adjacentHorizontalPageRoute, buildRemovalAnimation, SwingIcon, BottomSheetOptionTile) and empty _buildInfoCard placeholders - drop test-only Go wrapper methods (GetDownloadURL, MatchTrack, CheckAvailability 7-arg, CustomSearchForItemID) and their parsers - drop unused httputil getters/validators and hand-rolled base64 decoder in favor of encoding/base64 - remove unused riverpod_generator dev dependency
648 lines
20 KiB
Go
648 lines
20 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
func gojaValueIsEmpty(value goja.Value) bool {
|
|
return value == nil || goja.IsUndefined(value) || goja.IsNull(value)
|
|
}
|
|
|
|
func gojaObjectString(obj *goja.Object, keys ...string) string {
|
|
for _, key := range keys {
|
|
value := obj.Get(key)
|
|
if gojaValueIsEmpty(value) {
|
|
continue
|
|
}
|
|
if str, ok := value.Export().(string); ok {
|
|
return str
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func gojaObjectValue(obj *goja.Object, keys ...string) goja.Value {
|
|
for _, key := range keys {
|
|
value := obj.Get(key)
|
|
if !gojaValueIsEmpty(value) {
|
|
return value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func gojaObjectInt(obj *goja.Object, keys ...string) int {
|
|
for _, key := range keys {
|
|
value := obj.Get(key)
|
|
if gojaValueIsEmpty(value) {
|
|
continue
|
|
}
|
|
return int(value.ToInteger())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func gojaObjectInt64(obj *goja.Object, keys ...string) int64 {
|
|
for _, key := range keys {
|
|
value := obj.Get(key)
|
|
if gojaValueIsEmpty(value) {
|
|
continue
|
|
}
|
|
return value.ToInteger()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func gojaObjectBool(obj *goja.Object, keys ...string) bool {
|
|
for _, key := range keys {
|
|
value := obj.Get(key)
|
|
if gojaValueIsEmpty(value) {
|
|
continue
|
|
}
|
|
return value.ToBoolean()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func gojaObjectInterfaceMap(obj *goja.Object, keys ...string) map[string]any {
|
|
value := gojaObjectValue(obj, keys...)
|
|
if gojaValueIsEmpty(value) {
|
|
return nil
|
|
}
|
|
|
|
exported, ok := value.Export().(map[string]any)
|
|
if !ok || len(exported) == 0 {
|
|
return nil
|
|
}
|
|
return exported
|
|
}
|
|
|
|
func gojaObjectStringMap(vm *goja.Runtime, obj *goja.Object, keys ...string) map[string]string {
|
|
value := gojaObjectValue(obj, keys...)
|
|
if gojaValueIsEmpty(value) {
|
|
return nil
|
|
}
|
|
|
|
valueObj := value.ToObject(vm)
|
|
objectKeys := valueObj.Keys()
|
|
if len(objectKeys) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make(map[string]string, len(objectKeys))
|
|
for _, childKey := range objectKeys {
|
|
childValue := valueObj.Get(childKey)
|
|
if gojaValueIsEmpty(childValue) {
|
|
continue
|
|
}
|
|
result[childKey] = childValue.String()
|
|
}
|
|
if len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func gojaObjectStringSlice(obj *goja.Object, keys ...string) []string {
|
|
value := gojaObjectValue(obj, keys...)
|
|
if gojaValueIsEmpty(value) {
|
|
return nil
|
|
}
|
|
exported, ok := value.Export().([]any)
|
|
if !ok || len(exported) == 0 {
|
|
return nil
|
|
}
|
|
result := make([]string, 0, len(exported))
|
|
for _, item := range exported {
|
|
str, ok := item.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
str = strings.TrimSpace(str)
|
|
if str != "" {
|
|
result = append(result, str)
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func gojaArrayLength(value goja.Value, vm *goja.Runtime) (int, error) {
|
|
if gojaValueIsEmpty(value) {
|
|
return 0, nil
|
|
}
|
|
lengthValue := value.ToObject(vm).Get("length")
|
|
if gojaValueIsEmpty(lengthValue) {
|
|
return 0, fmt.Errorf("value is not an array")
|
|
}
|
|
length := lengthValue.ToInteger()
|
|
if length <= 0 {
|
|
return 0, nil
|
|
}
|
|
return int(length), nil
|
|
}
|
|
|
|
func parseExtensionTrackValue(vm *goja.Runtime, value goja.Value) ExtTrackMetadata {
|
|
obj := value.ToObject(vm)
|
|
return ExtTrackMetadata{
|
|
ID: gojaObjectString(obj, "id"),
|
|
Name: gojaObjectString(obj, "name"),
|
|
Artists: gojaObjectString(obj, "artists"),
|
|
AlbumName: gojaObjectString(obj, "album_name", "albumName"),
|
|
AlbumArtist: gojaObjectString(obj, "album_artist", "albumArtist"),
|
|
AlbumID: gojaObjectString(obj, "album_id", "albumId"),
|
|
AlbumURL: gojaObjectString(obj, "album_url", "albumUrl"),
|
|
ArtistID: gojaObjectString(obj, "artist_id", "artistId"),
|
|
ArtistURL: gojaObjectString(obj, "artist_url", "artistUrl"),
|
|
ExternalURL: gojaObjectString(obj, "external_urls", "externalUrls", "external_url", "externalUrl", "url"),
|
|
DurationMS: gojaObjectInt(obj, "duration_ms", "durationMs"),
|
|
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
|
PreviewURL: gojaObjectString(obj, "preview_url", "previewUrl"),
|
|
Images: gojaObjectString(obj, "images"),
|
|
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
|
TrackNumber: gojaObjectInt(obj, "track_number", "trackNumber"),
|
|
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
|
DiscNumber: gojaObjectInt(obj, "disc_number", "discNumber"),
|
|
TotalDiscs: gojaObjectInt(obj, "total_discs", "totalDiscs"),
|
|
ISRC: gojaObjectString(obj, "isrc"),
|
|
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
|
ItemType: gojaObjectString(obj, "item_type", "itemType"),
|
|
AlbumType: gojaObjectString(obj, "album_type", "albumType"),
|
|
Explicit: gojaObjectBool(obj, "explicit", "is_explicit", "isExplicit"),
|
|
TidalID: gojaObjectString(obj, "tidal_id", "tidalId"),
|
|
QobuzID: gojaObjectString(obj, "qobuz_id", "qobuzId"),
|
|
DeezerID: gojaObjectString(obj, "deezer_id", "deezerId"),
|
|
SpotifyID: gojaObjectString(obj, "spotify_id", "spotifyId"),
|
|
ExternalLinks: gojaObjectStringMap(vm, obj, "external_links", "externalLinks"),
|
|
Label: gojaObjectString(obj, "label"),
|
|
Copyright: gojaObjectString(obj, "copyright"),
|
|
Genre: gojaObjectString(obj, "genre"),
|
|
Composer: gojaObjectString(obj, "composer"),
|
|
AudioQuality: gojaObjectString(obj, "audio_quality", "audioQuality"),
|
|
AudioModes: gojaObjectString(obj, "audio_modes", "audioModes"),
|
|
}
|
|
}
|
|
|
|
func parseExtensionTrackArray(vm *goja.Runtime, value goja.Value) ([]ExtTrackMetadata, error) {
|
|
length, err := gojaArrayLength(value, vm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if length == 0 {
|
|
return []ExtTrackMetadata{}, nil
|
|
}
|
|
|
|
arrayObj := value.ToObject(vm)
|
|
tracks := make([]ExtTrackMetadata, 0, length)
|
|
for i := 0; i < length; i++ {
|
|
trackValue := arrayObj.Get(strconv.Itoa(i))
|
|
if gojaValueIsEmpty(trackValue) {
|
|
continue
|
|
}
|
|
tracks = append(tracks, parseExtensionTrackValue(vm, trackValue))
|
|
}
|
|
return tracks, nil
|
|
}
|
|
|
|
func parseExtensionAlbumValue(vm *goja.Runtime, value goja.Value) (ExtAlbumMetadata, error) {
|
|
if gojaValueIsEmpty(value) {
|
|
return ExtAlbumMetadata{}, nil
|
|
}
|
|
|
|
obj := value.ToObject(vm)
|
|
tracks := []ExtTrackMetadata{}
|
|
if tracksValue := gojaObjectValue(obj, "tracks"); !gojaValueIsEmpty(tracksValue) {
|
|
parsedTracks, err := parseExtensionTrackArray(vm, tracksValue)
|
|
if err != nil {
|
|
return ExtAlbumMetadata{}, err
|
|
}
|
|
tracks = parsedTracks
|
|
}
|
|
|
|
return ExtAlbumMetadata{
|
|
ID: gojaObjectString(obj, "id"),
|
|
Name: gojaObjectString(obj, "name"),
|
|
Artists: gojaObjectString(obj, "artists"),
|
|
ArtistID: gojaObjectString(obj, "artist_id", "artistId"),
|
|
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl", "images"),
|
|
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
|
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
|
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
|
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
|
AlbumType: gojaObjectString(obj, "album_type", "albumType"),
|
|
AudioTraits: gojaObjectStringSlice(obj, "audio_traits", "audioTraits"),
|
|
Tracks: tracks,
|
|
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
|
}.withTrackFallbacks(), nil
|
|
}
|
|
|
|
// withTrackFallbacks fills the album-level artist and release date from the
|
|
// album's tracks when the extension did not provide them at the album level.
|
|
// This is a generic mechanism so any extension benefits, without per-extension
|
|
// special-casing in the app.
|
|
func (a ExtAlbumMetadata) withTrackFallbacks() ExtAlbumMetadata {
|
|
if strings.TrimSpace(a.Artists) == "" {
|
|
a.Artists = albumArtistFromTracks(a.Tracks)
|
|
}
|
|
if strings.TrimSpace(a.ReleaseDate) == "" {
|
|
a.ReleaseDate = albumReleaseDateFromTracks(a.Tracks)
|
|
}
|
|
if len(a.AudioTraits) == 0 {
|
|
a.AudioTraits = albumAudioTraitsFromTracks(a.Tracks)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// albumArtistFromTracks prefers an explicit per-track album artist, then falls
|
|
// back to the most common track artist across the album.
|
|
func albumArtistFromTracks(tracks []ExtTrackMetadata) string {
|
|
for _, t := range tracks {
|
|
if s := strings.TrimSpace(t.AlbumArtist); s != "" {
|
|
return s
|
|
}
|
|
}
|
|
counts := map[string]int{}
|
|
order := []string{}
|
|
for _, t := range tracks {
|
|
artist := strings.TrimSpace(t.Artists)
|
|
if artist == "" {
|
|
continue
|
|
}
|
|
if _, ok := counts[artist]; !ok {
|
|
order = append(order, artist)
|
|
}
|
|
counts[artist]++
|
|
}
|
|
best := ""
|
|
bestCount := 0
|
|
for _, artist := range order {
|
|
if counts[artist] > bestCount {
|
|
best = artist
|
|
bestCount = counts[artist]
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// albumReleaseDateFromTracks returns the first non-empty track release date.
|
|
func albumReleaseDateFromTracks(tracks []ExtTrackMetadata) string {
|
|
for _, t := range tracks {
|
|
if s := strings.TrimSpace(t.ReleaseDate); s != "" {
|
|
return s
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// albumAudioTraitsFromTracks derives album-level audio badges (Dolby Atmos,
|
|
// Hi-Res Lossless, Lossless) from the per-track audio quality/mode fields that
|
|
// extensions like Tidal and Qobuz already provide. Tokens match what the album
|
|
// header understands ("dolby_atmos", "hi_res_lossless", "lossless").
|
|
func albumAudioTraitsFromTracks(tracks []ExtTrackMetadata) []string {
|
|
atmos := false
|
|
hiRes := false
|
|
lossless := false
|
|
|
|
for _, t := range tracks {
|
|
modes := strings.ToUpper(t.AudioModes)
|
|
quality := strings.ToUpper(t.AudioQuality)
|
|
if strings.Contains(modes, "ATMOS") || strings.Contains(quality, "ATMOS") {
|
|
atmos = true
|
|
}
|
|
if strings.Contains(quality, "HI_RES") ||
|
|
strings.Contains(quality, "HIRES") ||
|
|
strings.Contains(quality, "MASTER") ||
|
|
strings.Contains(quality, "MQA") {
|
|
hiRes = true
|
|
}
|
|
if strings.Contains(quality, "LOSSLESS") ||
|
|
strings.Contains(quality, "FLAC") {
|
|
lossless = true
|
|
}
|
|
if bd, sr := parseBitDepthSampleRate(quality); bd > 0 {
|
|
if bd > 16 || sr > 48 {
|
|
hiRes = true
|
|
} else {
|
|
lossless = true
|
|
}
|
|
}
|
|
}
|
|
|
|
traits := []string{}
|
|
if atmos {
|
|
traits = append(traits, "dolby_atmos")
|
|
}
|
|
if hiRes {
|
|
traits = append(traits, "hi_res_lossless")
|
|
} else if lossless {
|
|
traits = append(traits, "lossless")
|
|
}
|
|
return traits
|
|
}
|
|
|
|
// parseBitDepthSampleRate extracts a bit depth and sample rate (in kHz) from
|
|
// labels such as "24bit/96kHz", "16bit/44.1kHz" or "24bit".
|
|
func parseBitDepthSampleRate(quality string) (int, float64) {
|
|
lower := strings.ToLower(quality)
|
|
bitDepth := 0
|
|
sampleRate := 0.0
|
|
|
|
if idx := strings.Index(lower, "bit"); idx > 0 {
|
|
j := idx
|
|
for j > 0 && lower[j-1] >= '0' && lower[j-1] <= '9' {
|
|
j--
|
|
}
|
|
if n, err := strconv.Atoi(lower[j:idx]); err == nil {
|
|
bitDepth = n
|
|
}
|
|
}
|
|
if idx := strings.Index(lower, "khz"); idx > 0 {
|
|
j := idx
|
|
for j > 0 && ((lower[j-1] >= '0' && lower[j-1] <= '9') || lower[j-1] == '.') {
|
|
j--
|
|
}
|
|
if f, err := strconv.ParseFloat(lower[j:idx], 64); err == nil {
|
|
sampleRate = f
|
|
}
|
|
}
|
|
return bitDepth, sampleRate
|
|
}
|
|
|
|
func parseExtensionAlbumArray(vm *goja.Runtime, value goja.Value) ([]ExtAlbumMetadata, error) {
|
|
length, err := gojaArrayLength(value, vm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if length == 0 {
|
|
return []ExtAlbumMetadata{}, nil
|
|
}
|
|
|
|
arrayObj := value.ToObject(vm)
|
|
albums := make([]ExtAlbumMetadata, 0, length)
|
|
for i := 0; i < length; i++ {
|
|
albumValue := arrayObj.Get(strconv.Itoa(i))
|
|
if gojaValueIsEmpty(albumValue) {
|
|
continue
|
|
}
|
|
album, err := parseExtensionAlbumValue(vm, albumValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
albums = append(albums, album)
|
|
}
|
|
return albums, nil
|
|
}
|
|
|
|
func parseExtensionArtistValue(vm *goja.Runtime, value goja.Value) (ExtArtistMetadata, error) {
|
|
if gojaValueIsEmpty(value) {
|
|
return ExtArtistMetadata{}, nil
|
|
}
|
|
|
|
obj := value.ToObject(vm)
|
|
albums := []ExtAlbumMetadata{}
|
|
if albumsValue := gojaObjectValue(obj, "albums"); !gojaValueIsEmpty(albumsValue) {
|
|
parsedAlbums, err := parseExtensionAlbumArray(vm, albumsValue)
|
|
if err != nil {
|
|
return ExtArtistMetadata{}, err
|
|
}
|
|
albums = parsedAlbums
|
|
}
|
|
|
|
releases := []ExtAlbumMetadata{}
|
|
if releasesValue := gojaObjectValue(obj, "releases"); !gojaValueIsEmpty(releasesValue) {
|
|
parsedReleases, err := parseExtensionAlbumArray(vm, releasesValue)
|
|
if err != nil {
|
|
return ExtArtistMetadata{}, err
|
|
}
|
|
releases = parsedReleases
|
|
}
|
|
|
|
topTracks := []ExtTrackMetadata{}
|
|
if topTracksValue := gojaObjectValue(obj, "top_tracks", "topTracks"); !gojaValueIsEmpty(topTracksValue) {
|
|
parsedTopTracks, err := parseExtensionTrackArray(vm, topTracksValue)
|
|
if err != nil {
|
|
return ExtArtistMetadata{}, err
|
|
}
|
|
topTracks = parsedTopTracks
|
|
}
|
|
|
|
return ExtArtistMetadata{
|
|
ID: gojaObjectString(obj, "id"),
|
|
Name: gojaObjectString(obj, "name"),
|
|
ImageURL: gojaObjectString(obj, "image_url", "imageUrl"),
|
|
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
|
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
|
Listeners: gojaObjectInt(obj, "listeners"),
|
|
Albums: albums,
|
|
Releases: releases,
|
|
TopTracks: topTracks,
|
|
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
|
}, nil
|
|
}
|
|
|
|
func parseExtensionAvailabilityValue(vm *goja.Runtime, value goja.Value) ExtAvailabilityResult {
|
|
obj := value.ToObject(vm)
|
|
return ExtAvailabilityResult{
|
|
Available: gojaObjectBool(obj, "available"),
|
|
Reason: gojaObjectString(obj, "reason"),
|
|
TrackID: gojaObjectString(obj, "track_id", "trackId"),
|
|
SkipFallback: gojaObjectBool(obj, "skip_fallback", "skipFallback"),
|
|
}
|
|
}
|
|
|
|
func parseExtensionDownloadDecryptionValue(vm *goja.Runtime, value goja.Value) *DownloadDecryptionInfo {
|
|
if gojaValueIsEmpty(value) {
|
|
return nil
|
|
}
|
|
|
|
obj := value.ToObject(vm)
|
|
info := &DownloadDecryptionInfo{
|
|
Strategy: gojaObjectString(obj, "strategy"),
|
|
Key: gojaObjectString(obj, "key"),
|
|
IV: gojaObjectString(obj, "iv"),
|
|
InputFormat: gojaObjectString(obj, "input_format", "inputFormat"),
|
|
OutputExtension: gojaObjectString(obj, "output_extension", "outputExtension"),
|
|
Options: gojaObjectInterfaceMap(obj, "options"),
|
|
}
|
|
if info.Strategy == "" && info.Key == "" && info.IV == "" && info.InputFormat == "" && info.OutputExtension == "" && len(info.Options) == 0 {
|
|
return nil
|
|
}
|
|
return info
|
|
}
|
|
|
|
func parseExtensionDownloadResultValue(vm *goja.Runtime, value goja.Value) ExtDownloadResult {
|
|
obj := value.ToObject(vm)
|
|
return ExtDownloadResult{
|
|
Success: gojaObjectBool(obj, "success"),
|
|
FilePath: gojaObjectString(obj, "file_path", "filePath", "path"),
|
|
AlreadyExists: gojaObjectBool(obj, "already_exists", "alreadyExists"),
|
|
BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"),
|
|
SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"),
|
|
AudioCodec: gojaObjectString(obj, "audio_codec", "audioCodec", "codec"),
|
|
ErrorMessage: gojaObjectString(obj, "error_message", "errorMessage", "error"),
|
|
ErrorType: gojaObjectString(obj, "error_type", "errorType"),
|
|
RetryAfterSeconds: gojaObjectInt(obj, "retry_after_seconds", "retryAfterSeconds"),
|
|
Title: gojaObjectString(obj, "title"),
|
|
Artist: gojaObjectString(obj, "artist"),
|
|
Album: gojaObjectString(obj, "album"),
|
|
AlbumArtist: gojaObjectString(obj, "album_artist", "albumArtist"),
|
|
TrackNumber: gojaObjectInt(obj, "track_number", "trackNumber"),
|
|
DiscNumber: gojaObjectInt(obj, "disc_number", "discNumber"),
|
|
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
|
TotalDiscs: gojaObjectInt(obj, "total_discs", "totalDiscs"),
|
|
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
|
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
|
ISRC: gojaObjectString(obj, "isrc"),
|
|
Genre: gojaObjectString(obj, "genre"),
|
|
Label: gojaObjectString(obj, "label"),
|
|
Copyright: gojaObjectString(obj, "copyright"),
|
|
Composer: gojaObjectString(obj, "composer"),
|
|
LyricsLRC: gojaObjectString(obj, "lyrics_lrc", "lyricsLrc"),
|
|
DecryptionKey: gojaObjectString(obj, "decryption_key", "decryptionKey"),
|
|
Decryption: parseExtensionDownloadDecryptionValue(vm, gojaObjectValue(obj, "decryption")),
|
|
ActualExtension: gojaObjectString(obj, "actual_extension", "actualExtension"),
|
|
OutputExtension: gojaObjectString(obj, "output_extension", "outputExtension"),
|
|
ActualContainer: gojaObjectString(obj, "actual_container", "actualContainer", "container"),
|
|
RequiresContainerConversion: gojaObjectBool(
|
|
obj,
|
|
"requires_container_conversion",
|
|
"requiresContainerConversion",
|
|
),
|
|
}
|
|
}
|
|
|
|
func parseExtensionURLHandleValue(vm *goja.Runtime, value goja.Value) (ExtURLHandleResult, error) {
|
|
obj := value.ToObject(vm)
|
|
handleResult := ExtURLHandleResult{
|
|
Type: gojaObjectString(obj, "type"),
|
|
Name: gojaObjectString(obj, "name"),
|
|
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
|
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
|
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
|
}
|
|
|
|
if trackValue := gojaObjectValue(obj, "track"); !gojaValueIsEmpty(trackValue) {
|
|
track := parseExtensionTrackValue(vm, trackValue)
|
|
handleResult.Track = &track
|
|
}
|
|
if tracksValue := gojaObjectValue(obj, "tracks"); !gojaValueIsEmpty(tracksValue) {
|
|
tracks, err := parseExtensionTrackArray(vm, tracksValue)
|
|
if err != nil {
|
|
return ExtURLHandleResult{}, err
|
|
}
|
|
handleResult.Tracks = tracks
|
|
}
|
|
if albumValue := gojaObjectValue(obj, "album"); !gojaValueIsEmpty(albumValue) {
|
|
album, err := parseExtensionAlbumValue(vm, albumValue)
|
|
if err != nil {
|
|
return ExtURLHandleResult{}, err
|
|
}
|
|
handleResult.Album = &album
|
|
}
|
|
if artistValue := gojaObjectValue(obj, "artist"); !gojaValueIsEmpty(artistValue) {
|
|
artist, err := parseExtensionArtistValue(vm, artistValue)
|
|
if err != nil {
|
|
return ExtURLHandleResult{}, err
|
|
}
|
|
handleResult.Artist = &artist
|
|
}
|
|
|
|
return handleResult, nil
|
|
}
|
|
|
|
func parseExtensionPostProcessValue(vm *goja.Runtime, value goja.Value) PostProcessResult {
|
|
obj := value.ToObject(vm)
|
|
return PostProcessResult{
|
|
Success: gojaObjectBool(obj, "success"),
|
|
NewFilePath: gojaObjectString(obj, "new_file_path", "newFilePath"),
|
|
NewFileURI: gojaObjectString(obj, "new_file_uri", "newFileUri"),
|
|
Error: gojaObjectString(obj, "error"),
|
|
BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"),
|
|
SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"),
|
|
}
|
|
}
|
|
|
|
func parseExtensionLyricsLineArray(vm *goja.Runtime, value goja.Value) ([]ExtLyricsLine, error) {
|
|
length, err := gojaArrayLength(value, vm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if length == 0 {
|
|
return []ExtLyricsLine{}, nil
|
|
}
|
|
|
|
arrayObj := value.ToObject(vm)
|
|
lines := make([]ExtLyricsLine, 0, length)
|
|
for i := 0; i < length; i++ {
|
|
lineValue := arrayObj.Get(strconv.Itoa(i))
|
|
if gojaValueIsEmpty(lineValue) {
|
|
continue
|
|
}
|
|
lineObj := lineValue.ToObject(vm)
|
|
lines = append(lines, ExtLyricsLine{
|
|
StartTimeMs: gojaObjectInt64(lineObj, "startTimeMs", "start_time_ms"),
|
|
Words: gojaObjectString(lineObj, "words"),
|
|
EndTimeMs: gojaObjectInt64(lineObj, "endTimeMs", "end_time_ms"),
|
|
})
|
|
}
|
|
return lines, nil
|
|
}
|
|
|
|
func parseExtensionLyricsValue(vm *goja.Runtime, value goja.Value) (ExtLyricsResult, error) {
|
|
obj := value.ToObject(vm)
|
|
lines := []ExtLyricsLine{}
|
|
if linesValue := gojaObjectValue(obj, "lines"); !gojaValueIsEmpty(linesValue) {
|
|
parsedLines, err := parseExtensionLyricsLineArray(vm, linesValue)
|
|
if err != nil {
|
|
return ExtLyricsResult{}, err
|
|
}
|
|
lines = parsedLines
|
|
}
|
|
|
|
return ExtLyricsResult{
|
|
Lines: lines,
|
|
SyncType: gojaObjectString(obj, "syncType", "sync_type"),
|
|
Instrumental: gojaObjectBool(obj, "instrumental"),
|
|
PlainLyrics: gojaObjectString(obj, "plainLyrics", "plain_lyrics"),
|
|
Provider: gojaObjectString(obj, "provider"),
|
|
}, nil
|
|
}
|
|
|
|
func parseExtensionSearchResult(vm *goja.Runtime, value goja.Value) (ExtSearchResult, error) {
|
|
if gojaValueIsEmpty(value) {
|
|
return ExtSearchResult{}, nil
|
|
}
|
|
|
|
resultObj := value.ToObject(vm)
|
|
tracksValue := resultObj.Get("tracks")
|
|
if gojaValueIsEmpty(tracksValue) {
|
|
tracks, err := parseExtensionTrackArray(vm, value)
|
|
if err != nil {
|
|
return ExtSearchResult{}, err
|
|
}
|
|
return ExtSearchResult{
|
|
Tracks: tracks,
|
|
Total: len(tracks),
|
|
}, nil
|
|
}
|
|
|
|
tracks, err := parseExtensionTrackArray(vm, tracksValue)
|
|
if err != nil {
|
|
return ExtSearchResult{}, err
|
|
}
|
|
total := gojaObjectInt(resultObj, "total")
|
|
if total == 0 {
|
|
total = len(tracks)
|
|
}
|
|
return ExtSearchResult{
|
|
Tracks: tracks,
|
|
Total: total,
|
|
}, nil
|
|
}
|