mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
refactor deezer session and pipe graphql flow
This commit is contained in:
@@ -88,30 +88,31 @@ func (c *Client) Login(ctx context.Context) error {
|
|||||||
c.refresh = strings.TrimSpace(c.cfg.Session.Deezer.RefreshToken)
|
c.refresh = strings.TrimSpace(c.cfg.Session.Deezer.RefreshToken)
|
||||||
c.license = ""
|
c.license = ""
|
||||||
c.userID = ""
|
c.userID = ""
|
||||||
email := strings.TrimSpace(c.cfg.Session.Deezer.Email)
|
if err := c.ensureLaunchSession(ctx); err != nil {
|
||||||
password := strings.TrimSpace(c.cfg.Session.Deezer.Password)
|
|
||||||
if c.arl != "" {
|
|
||||||
if err := c.refreshSessionFromARL(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if email != "" && password != "" {
|
|
||||||
if err := c.loginWithCredentials(ctx, email, password); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if c.refresh != "" {
|
|
||||||
if err := c.refreshJWT(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.refreshLicenseFromPipe(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("deezer login requires deezer.arl, deezer.email+deezer.password, or deezer.refresh_token")
|
|
||||||
}
|
|
||||||
c.loggedIn = true
|
c.loggedIn = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ensureLaunchSession(ctx context.Context) error {
|
||||||
|
email := strings.TrimSpace(c.cfg.Session.Deezer.Email)
|
||||||
|
password := strings.TrimSpace(c.cfg.Session.Deezer.Password)
|
||||||
|
if strings.TrimSpace(c.arl) != "" {
|
||||||
|
return c.refreshSessionFromARL(ctx)
|
||||||
|
}
|
||||||
|
if email != "" && password != "" {
|
||||||
|
return c.loginWithCredentials(ctx, email, password)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.refresh) != "" {
|
||||||
|
if err := c.refreshJWT(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.refreshLicenseFromPipe(ctx)
|
||||||
|
}
|
||||||
|
return errors.New("deezer login requires deezer.arl, deezer.email+deezer.password, or deezer.refresh_token")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) LoggedIn() bool {
|
func (c *Client) LoggedIn() bool {
|
||||||
return c.loggedIn
|
return c.loggedIn
|
||||||
}
|
}
|
||||||
@@ -270,25 +271,9 @@ func (c *Client) getArtistAlbums(ctx context.Context, artistID string) (map[stri
|
|||||||
|
|
||||||
func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*provider.Downloadable, error) {
|
func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*provider.Downloadable, error) {
|
||||||
if strings.TrimSpace(c.license) == "" {
|
if strings.TrimSpace(c.license) == "" {
|
||||||
if strings.TrimSpace(c.arl) != "" {
|
if err := c.ensureLaunchSession(ctx); err != nil {
|
||||||
if err := c.refreshSessionFromARL(ctx); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if strings.TrimSpace(c.refresh) != "" {
|
|
||||||
_ = c.refreshJWT(ctx)
|
|
||||||
if strings.TrimSpace(c.jwt) != "" {
|
|
||||||
_ = c.refreshLicenseFromPipe(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
email := strings.TrimSpace(c.cfg.Session.Deezer.Email)
|
|
||||||
password := strings.TrimSpace(c.cfg.Session.Deezer.Password)
|
|
||||||
if strings.TrimSpace(c.license) == "" && email != "" && password != "" {
|
|
||||||
if err := c.loginWithCredentials(ctx, email, password); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(c.license) == "" {
|
if strings.TrimSpace(c.license) == "" {
|
||||||
return nil, errors.New("deezer native download requires deezer.arl, deezer.email+deezer.password, or deezer.refresh_token")
|
return nil, errors.New("deezer native download requires deezer.arl, deezer.email+deezer.password, or deezer.refresh_token")
|
||||||
@@ -302,17 +287,6 @@ func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*prov
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
media, err := c.getMediaURL(ctx, trackToken, c.cfg.Session.Deezer.Quality, c.cfg.Session.Deezer.LowerQualityIfNotAvailable)
|
media, err := c.getMediaURL(ctx, trackToken, c.cfg.Session.Deezer.Quality, c.cfg.Session.Deezer.LowerQualityIfNotAvailable)
|
||||||
if err != nil {
|
|
||||||
var mediaErr *deezerMediaError
|
|
||||||
if errors.As(err, &mediaErr) && mediaErr.Code == 2004 {
|
|
||||||
if freshToken, tokenErr := c.getTrackTokenFromPipe(ctx, item); tokenErr == nil && strings.TrimSpace(freshToken) != "" && freshToken != trackToken {
|
|
||||||
if retryMedia, retryErr := c.getMediaURL(ctx, freshToken, c.cfg.Session.Deezer.Quality, c.cfg.Session.Deezer.LowerQualityIfNotAvailable); retryErr == nil {
|
|
||||||
media = retryMedia
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -572,31 +546,14 @@ func (c *Client) getTrackToken(ctx context.Context, trackID string) (string, err
|
|||||||
if token, err := c.getTrackTokenFromPipe(ctx, trackID); err == nil && strings.TrimSpace(token) != "" {
|
if token, err := c.getTrackTokenFromPipe(ctx, trackID); err == nil && strings.TrimSpace(token) != "" {
|
||||||
return token, nil
|
return token, nil
|
||||||
} else if errors.Is(err, errDeezerJWTExpired) {
|
} else if errors.Is(err, errDeezerJWTExpired) {
|
||||||
if strings.TrimSpace(c.refresh) != "" {
|
c.refreshJWTFromAvailableState(ctx)
|
||||||
_ = c.refreshJWT(ctx)
|
if token, retryErr := c.getTrackTokenFromPipe(ctx, trackID); retryErr == nil && strings.TrimSpace(token) != "" {
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) == "" && strings.TrimSpace(c.arl) != "" {
|
|
||||||
_ = c.refreshSessionFromARL(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) == "" {
|
|
||||||
if strings.TrimSpace(c.arl) != "" {
|
|
||||||
_ = c.refreshSessionFromARL(ctx)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) == "" && strings.TrimSpace(c.refresh) != "" {
|
|
||||||
_ = c.refreshJWT(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if token, err := c.getTrackTokenFromPipe(ctx, trackID); err == nil && strings.TrimSpace(token) != "" {
|
|
||||||
return token, nil
|
return token, nil
|
||||||
} else if errors.Is(err, errDeezerJWTExpired) {
|
|
||||||
if strings.TrimSpace(c.refresh) != "" {
|
|
||||||
_ = c.refreshJWT(ctx)
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(c.jwt) != "" {
|
|
||||||
if retryToken, retryErr := c.getTrackTokenFromPipe(ctx, trackID); retryErr == nil && strings.TrimSpace(retryToken) != "" {
|
|
||||||
return retryToken, nil
|
|
||||||
}
|
}
|
||||||
|
if err := c.ensureJWT(ctx, "deezer jwt unavailable for track media token"); err == nil {
|
||||||
|
if token, retryErr := c.getTrackTokenFromPipe(ctx, trackID); retryErr == nil && strings.TrimSpace(token) != "" {
|
||||||
|
return token, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := c.apiGet(ctx, "/track/"+url.PathEscape(strings.TrimSpace(trackID)), nil)
|
resp, err := c.apiGet(ctx, "/track/"+url.PathEscape(strings.TrimSpace(trackID)), nil)
|
||||||
@@ -611,12 +568,6 @@ func (c *Client) getTrackToken(ctx context.Context, trackID string) (string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getTrackTokenFromPipe(ctx context.Context, trackID string) (string, error) {
|
func (c *Client) getTrackTokenFromPipe(ctx context.Context, trackID string) (string, error) {
|
||||||
if strings.TrimSpace(c.jwt) == "" {
|
|
||||||
return "", errors.New("deezer jwt unavailable for track media token")
|
|
||||||
}
|
|
||||||
if err := c.limiter.Wait(ctx); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
query := `query KmpMpTrackMedia($trackId: String!) { track(trackId: $trackId) { media { __typename ...TrackMediaFields } } } fragment TrackMediaFields on TrackMedia { id version token { payload expiresAt version } estimatedSizes { flac: FLAC mp3_320: MP3_320 mp3_128: MP3_128 mp3_misc: MP3_MISC opus_std: OPUS_STD opus_high: OPUS_HIGH sbc_256: SBC_256 aac_96: AAC_96 aac_64: AAC_64 ac4_ims: AC4_IMS dd_joc: DD_JOC mp4_ra1: MP4_RA1 mp4_ra2: MP4_RA2 mp4_ra3: MP4_RA3 } gain rights { sub { available } ads { available } } }`
|
query := `query KmpMpTrackMedia($trackId: String!) { track(trackId: $trackId) { media { __typename ...TrackMediaFields } } } fragment TrackMediaFields on TrackMedia { id version token { payload expiresAt version } estimatedSizes { flac: FLAC mp3_320: MP3_320 mp3_128: MP3_128 mp3_misc: MP3_MISC opus_std: OPUS_STD opus_high: OPUS_HIGH sbc_256: SBC_256 aac_96: AAC_96 aac_64: AAC_64 ac4_ims: AC4_IMS dd_joc: DD_JOC mp4_ra1: MP4_RA1 mp4_ra2: MP4_RA2 mp4_ra3: MP4_RA3 } gain rights { sub { available } ads { available } } }`
|
||||||
body := map[string]any{
|
body := map[string]any{
|
||||||
"operationName": "KmpMpTrackMedia",
|
"operationName": "KmpMpTrackMedia",
|
||||||
@@ -629,52 +580,10 @@ func (c *Client) getTrackTokenFromPipe(ctx context.Context, trackID string) (str
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
encoded, err := json.Marshal(body)
|
out, err := c.pipeGraphQL(ctx, body, "deezer track media query")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pipeURL, bytes.NewReader(encoded))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", c.ua)
|
|
||||||
req.Header.Set("Accept", "multipart/mixed;deferSpec=20220824, application/graphql-response+json, application/json")
|
|
||||||
req.Header.Set("Accept-Charset", "UTF-8")
|
|
||||||
req.Header.Set("Accept-Language", "en-US")
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.jwt))
|
|
||||||
|
|
||||||
resp, err := c.http.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
raw, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
||||||
return "", fmt.Errorf("deezer track media query failed: status=%d body=%s", resp.StatusCode, string(raw))
|
|
||||||
}
|
|
||||||
out := map[string]any{}
|
|
||||||
if err = json.Unmarshal(raw, &out); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if errs, ok := out["errors"].([]any); ok && len(errs) > 0 {
|
|
||||||
msg := ""
|
|
||||||
typ := ""
|
|
||||||
if em, ok := errs[0].(map[string]any); ok {
|
|
||||||
msg = strings.TrimSpace(jsonutil.StringFromAny(em["message"]))
|
|
||||||
typ = strings.TrimSpace(jsonutil.StringFromAny(em["type"]))
|
|
||||||
}
|
|
||||||
if strings.EqualFold(typ, "JwtTokenExpiredError") || strings.Contains(strings.ToLower(msg), "not valid anymore") || strings.Contains(strings.ToLower(msg), "jwt") && strings.Contains(strings.ToLower(msg), "expired") {
|
|
||||||
return "", errDeezerJWTExpired
|
|
||||||
}
|
|
||||||
if msg == "" {
|
|
||||||
msg = "unknown graphql error"
|
|
||||||
}
|
|
||||||
return "", errors.New(msg)
|
|
||||||
}
|
|
||||||
payload := strings.TrimSpace(jsonutil.StringFromAny(jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(out, "data"), "track"), "media"), "token")["payload"]))
|
payload := strings.TrimSpace(jsonutil.StringFromAny(jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(out, "data"), "track"), "media"), "token")["payload"]))
|
||||||
if payload == "" {
|
if payload == "" {
|
||||||
return "", errors.New("deezer track media response missing token payload")
|
return "", errors.New("deezer track media response missing token payload")
|
||||||
@@ -697,50 +606,10 @@ func (c *Client) fetchLyricsFromPipe(ctx context.Context, trackID string) (*lyri
|
|||||||
"variables": map[string]any{"trackId": strings.TrimSpace(trackID)},
|
"variables": map[string]any{"trackId": strings.TrimSpace(trackID)},
|
||||||
"query": query,
|
"query": query,
|
||||||
}
|
}
|
||||||
encoded, err := json.Marshal(body)
|
out, err := c.pipeGraphQLWithJWT(ctx, jwt, body, "deezer lyrics query")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pipeURL, bytes.NewReader(encoded))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", c.ua)
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(jwt))
|
|
||||||
|
|
||||||
resp, err := c.http.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
raw, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
||||||
return nil, fmt.Errorf("deezer lyrics query failed: status=%d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
out := map[string]any{}
|
|
||||||
if err = json.Unmarshal(raw, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if errs, ok := out["errors"].([]any); ok && len(errs) > 0 {
|
|
||||||
msg := ""
|
|
||||||
typ := ""
|
|
||||||
if em, ok := errs[0].(map[string]any); ok {
|
|
||||||
msg = strings.TrimSpace(jsonutil.StringFromAny(em["message"]))
|
|
||||||
typ = strings.TrimSpace(jsonutil.StringFromAny(em["type"]))
|
|
||||||
}
|
|
||||||
if strings.EqualFold(typ, "JwtTokenExpiredError") || strings.Contains(strings.ToLower(msg), "not valid anymore") || strings.Contains(strings.ToLower(msg), "jwt") && strings.Contains(strings.ToLower(msg), "expired") {
|
|
||||||
return nil, errDeezerJWTExpired
|
|
||||||
}
|
|
||||||
if msg == "" {
|
|
||||||
msg = "unknown graphql error"
|
|
||||||
}
|
|
||||||
return nil, errors.New(msg)
|
|
||||||
}
|
|
||||||
lyrics := jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(out, "data"), "track"), "lyrics")
|
lyrics := jsonutil.NestedMap(jsonutil.NestedMap(jsonutil.NestedMap(out, "data"), "track"), "lyrics")
|
||||||
text := strings.TrimSpace(jsonutil.StringFromAny(lyrics["text"]))
|
text := strings.TrimSpace(jsonutil.StringFromAny(lyrics["text"]))
|
||||||
synced := buildSyncedLRC(lyrics["synchronizedLines"])
|
synced := buildSyncedLRC(lyrics["synchronizedLines"])
|
||||||
@@ -765,22 +634,12 @@ func (c *Client) fetchLyricsFromPipe(ctx context.Context, trackID string) (*lyri
|
|||||||
return &lyricsResult{Text: strings.Join(parts, "\n")}, nil
|
return &lyricsResult{Text: strings.Join(parts, "\n")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(c.jwt) == "" {
|
if err := c.ensureJWT(ctx, "deezer jwt unavailable for lyrics query"); err != nil {
|
||||||
if err := c.refreshSessionFromARL(ctx); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) == "" {
|
|
||||||
return nil, errors.New("deezer jwt unavailable for lyrics query")
|
|
||||||
}
|
|
||||||
res, err := fetchOnce(c.jwt)
|
res, err := fetchOnce(c.jwt)
|
||||||
if errors.Is(err, errDeezerJWTExpired) {
|
if errors.Is(err, errDeezerJWTExpired) {
|
||||||
if strings.TrimSpace(c.refresh) != "" {
|
c.refreshJWTFromAvailableState(ctx)
|
||||||
_ = c.refreshJWT(ctx)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) == "" && strings.TrimSpace(c.arl) != "" {
|
|
||||||
_ = c.refreshSessionFromARL(ctx)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(c.jwt) != "" {
|
if strings.TrimSpace(c.jwt) != "" {
|
||||||
return fetchOnce(c.jwt)
|
return fetchOnce(c.jwt)
|
||||||
}
|
}
|
||||||
@@ -1039,13 +898,99 @@ func (c *Client) refreshJWT(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) refreshLicenseFromPipe(ctx context.Context) error {
|
func (c *Client) ensureJWT(ctx context.Context, unavailableMsg string) error {
|
||||||
|
if strings.TrimSpace(c.jwt) != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.arl) != "" {
|
||||||
|
_ = c.refreshSessionFromARL(ctx)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.jwt) == "" && strings.TrimSpace(c.refresh) != "" {
|
||||||
|
_ = c.refreshJWT(ctx)
|
||||||
|
}
|
||||||
if strings.TrimSpace(c.jwt) == "" {
|
if strings.TrimSpace(c.jwt) == "" {
|
||||||
return errors.New("missing deezer jwt")
|
if strings.TrimSpace(unavailableMsg) == "" {
|
||||||
|
unavailableMsg = "deezer jwt unavailable"
|
||||||
|
}
|
||||||
|
return errors.New(unavailableMsg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) refreshJWTFromAvailableState(ctx context.Context) {
|
||||||
|
if strings.TrimSpace(c.refresh) != "" {
|
||||||
|
_ = c.refreshJWT(ctx)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.jwt) == "" && strings.TrimSpace(c.arl) != "" {
|
||||||
|
_ = c.refreshSessionFromARL(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) pipeGraphQL(ctx context.Context, body map[string]any, operation string) (map[string]any, error) {
|
||||||
|
return c.pipeGraphQLWithJWT(ctx, c.jwt, body, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) pipeGraphQLWithJWT(ctx context.Context, jwt string, body map[string]any, operation string) (map[string]any, error) {
|
||||||
|
jwt = strings.TrimSpace(jwt)
|
||||||
|
if jwt == "" {
|
||||||
|
return nil, errors.New("missing deezer jwt")
|
||||||
}
|
}
|
||||||
if err := c.limiter.Wait(ctx); err != nil {
|
if err := c.limiter.Wait(ctx); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
encoded, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pipeURL, bytes.NewReader(encoded))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", c.ua)
|
||||||
|
req.Header.Set("Accept", "multipart/mixed;deferSpec=20220824, application/graphql-response+json, application/json")
|
||||||
|
req.Header.Set("Accept-Charset", "UTF-8")
|
||||||
|
req.Header.Set("Accept-Language", "en-US")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+jwt)
|
||||||
|
|
||||||
|
resp, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
raw, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
if strings.TrimSpace(operation) == "" {
|
||||||
|
operation = "pipe graphql"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s failed: status=%d body=%s", operation, resp.StatusCode, string(raw))
|
||||||
|
}
|
||||||
|
out := map[string]any{}
|
||||||
|
if err = json.Unmarshal(raw, &out); err != nil {
|
||||||
|
return nil, errors.New("invalid pipe response")
|
||||||
|
}
|
||||||
|
if errs, ok := out["errors"].([]any); ok && len(errs) > 0 {
|
||||||
|
msg := ""
|
||||||
|
typ := ""
|
||||||
|
if em, ok := errs[0].(map[string]any); ok {
|
||||||
|
msg = strings.TrimSpace(jsonutil.StringFromAny(em["message"]))
|
||||||
|
typ = strings.TrimSpace(jsonutil.StringFromAny(em["type"]))
|
||||||
|
}
|
||||||
|
if strings.EqualFold(typ, "JwtTokenExpiredError") || strings.Contains(strings.ToLower(msg), "not valid anymore") || strings.Contains(strings.ToLower(msg), "jwt") && strings.Contains(strings.ToLower(msg), "expired") {
|
||||||
|
return nil, errDeezerJWTExpired
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
msg = "pipe response returned graphql error"
|
||||||
|
}
|
||||||
|
return nil, errors.New(msg)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) refreshLicenseFromPipe(ctx context.Context) error {
|
||||||
body := map[string]any{
|
body := map[string]any{
|
||||||
"operationName": "KmpMpMediaServiceLicenseToken",
|
"operationName": "KmpMpMediaServiceLicenseToken",
|
||||||
"query": "query KmpMpMediaServiceLicenseToken { tokens { mediaServiceLicenseToken { token expirationDate } } }",
|
"query": "query KmpMpMediaServiceLicenseToken { tokens { mediaServiceLicenseToken { token expirationDate } } }",
|
||||||
@@ -1057,40 +1002,10 @@ func (c *Client) refreshLicenseFromPipe(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
b, _ := json.Marshal(body)
|
out, err := c.pipeGraphQL(ctx, body, "pipe license refresh")
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pipeURL, bytes.NewReader(b))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", c.ua)
|
|
||||||
req.Header.Set("Accept", "multipart/mixed;deferSpec=20220824, application/graphql-response+json, application/json")
|
|
||||||
req.Header.Set("Accept-Charset", "UTF-8")
|
|
||||||
req.Header.Set("Accept-Language", "en-US")
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.jwt))
|
|
||||||
resp, err := c.http.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
raw, _ := io.ReadAll(resp.Body)
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
||||||
return fmt.Errorf("pipe license refresh failed: status=%d body=%s", resp.StatusCode, string(raw))
|
|
||||||
}
|
|
||||||
out := map[string]any{}
|
|
||||||
if json.Unmarshal(raw, &out) != nil {
|
|
||||||
return errors.New("invalid pipe response")
|
|
||||||
}
|
|
||||||
if errs, ok := out["errors"].([]any); ok && len(errs) > 0 {
|
|
||||||
msg := ""
|
|
||||||
if em, ok := errs[0].(map[string]any); ok {
|
|
||||||
msg = strings.TrimSpace(jsonutil.StringFromAny(em["message"]))
|
|
||||||
}
|
|
||||||
if msg == "" {
|
|
||||||
msg = "pipe response returned graphql error"
|
|
||||||
}
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
token := findStringByKey(out, "token")
|
token := findStringByKey(out, "token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return errors.New("pipe response missing license token")
|
return errors.New("pipe response missing license token")
|
||||||
|
|||||||
Reference in New Issue
Block a user