Merge pull request #38 from ByteDream/v3/feature/non-premium-support

Extend support for non premium accounts
This commit is contained in:
ByteDream 2022-06-10 16:14:44 +02:00 committed by GitHub
commit 170fb2efb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 75 deletions

View file

@ -186,8 +186,10 @@ func archive(urls []string) error {
episodes, err := archiveExtractEpisodes(url) episodes, err := archiveExtractEpisodes(url)
if err != nil { if err != nil {
out.StopProgress("Failed to parse url %d", i+1) out.StopProgress("Failed to parse url %d", i+1)
out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + if crunchy.Config.Premium {
"try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " +
"try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information")
}
return err return err
} }
out.StopProgress("Parsed url %d", i+1) out.StopProgress("Parsed url %d", i+1)

View file

@ -132,8 +132,10 @@ func download(urls []string) error {
episodes, err := downloadExtractEpisodes(url) episodes, err := downloadExtractEpisodes(url)
if err != nil { if err != nil {
out.StopProgress("Failed to parse url %d", i+1) out.StopProgress("Failed to parse url %d", i+1)
out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + if crunchy.Config.Premium {
"try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " +
"try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information")
}
return err return err
} }
out.StopProgress("Parsed url %d", i+1) out.StopProgress("Parsed url %d", i+1)

View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -50,7 +51,7 @@ const (
type Crunchyroll struct { type Crunchyroll struct {
// Client is the http.Client to perform all requests over. // Client is the http.Client to perform all requests over.
Client *http.Client Client *http.Client
// Context can be used to stop requests with Client and is context.Background by default. // Context can be used to stop requests with Client.
Context context.Context Context context.Context
// Locale specifies in which language all results should be returned / requested. // Locale specifies in which language all results should be returned / requested.
Locale LOCALE Locale LOCALE
@ -67,7 +68,6 @@ type Crunchyroll struct {
CountryCode string CountryCode string
Premium bool Premium bool
Channel string
Policy string Policy string
Signature string Signature string
KeyPairID string KeyPairID string
@ -111,6 +111,9 @@ type loginResponse struct {
Scope string `json:"scope"` Scope string `json:"scope"`
Country string `json:"country"` Country string `json:"country"`
AccountID string `json:"account_id"` AccountID string `json:"account_id"`
Error bool `json:"error"`
Message string `json:"message"`
} }
// LoginWithCredentials logs in via crunchyroll username or email and password. // LoginWithCredentials logs in via crunchyroll username or email and password.
@ -136,6 +139,11 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h
var loginResp loginResponse var loginResp loginResponse
json.NewDecoder(resp.Body).Decode(&loginResp) json.NewDecoder(resp.Body).Decode(&loginResp)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to auth with credentials: %s", resp.Status)
} else if loginResp.Error {
return nil, fmt.Errorf("an unexpected login error occoured: %s", loginResp.Message)
}
var etpRt string var etpRt string
for _, cookie := range resp.Cookies() { for _, cookie := range resp.Cookies() {
@ -173,9 +181,16 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*
if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil {
return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) return nil, fmt.Errorf("failed to parse start session with session id response: %w", err)
} }
if isError, ok := jsonBody["error"]; ok && isError.(bool) { if isError, ok := jsonBody["error"]; ok && isError.(bool) {
return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"])
} }
data := jsonBody["data"].(map[string]interface{})
user := data["user"]
if user == nil {
return nil, errors.New("invalid session id, user is not logged in")
}
var etpRt string var etpRt string
for _, cookie := range resp.Cookies() { for _, cookie := range resp.Cookies() {
@ -244,22 +259,11 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt
cms := jsonBody["cms"].(map[string]any) cms := jsonBody["cms"].(map[string]any)
crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/")
if strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") { crunchy.Config.Premium = strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll")
crunchy.Config.Premium = true
crunchy.Config.Channel = "crunchyroll"
} else {
crunchy.Config.Premium = false
crunchy.Config.Channel = "-"
}
if strings.Contains(cms["bucket"].(string), "crunchyroll") {
crunchy.Config.Premium = true
crunchy.Config.Channel = "crunchyroll"
} else {
crunchy.Config.Premium = false
crunchy.Config.Channel = "-"
}
// / is trimmed so that urls which require it must be in .../{bucket}/... like format.
// this just looks cleaner
crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/")
crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Policy = cms["policy"].(string)
crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.Signature = cms["signature"].(string)
crunchy.Config.KeyPairID = cms["key_pair_id"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string)

View file

@ -89,10 +89,8 @@ type HistoryEpisode struct {
// EpisodeFromID returns an episode by its api id. // EpisodeFromID returns an episode by its api id.
func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -126,6 +124,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
// Every episode in a season (should) have the same audio locale, // Every episode in a season (should) have the same audio locale,
// so if you want to get the audio locale of a season, just call // so if you want to get the audio locale of a season, just call
// this method on the first episode of the season. // this method on the first episode of the season.
// Will fail if no streams are available, thus use Episode.Available
// to prevent any misleading errors.
func (e *Episode) AudioLocale() (LOCALE, error) { func (e *Episode) AudioLocale() (LOCALE, error) {
streams, err := e.Streams() streams, err := e.Streams()
if err != nil { if err != nil {
@ -134,6 +134,11 @@ func (e *Episode) AudioLocale() (LOCALE, error) {
return streams[0].AudioLocale, nil return streams[0].AudioLocale, nil
} }
// Available returns if downloadable streams for this episodes are available.
func (e *Episode) Available() bool {
return e.crunchy.Config.Premium || !e.IsPremiumOnly
}
// GetFormat returns the format which matches the given resolution and subtitle locale. // GetFormat returns the format which matches the given resolution and subtitle locale.
func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) { func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) {
streams, err := e.Streams() streams, err := e.Streams()
@ -206,10 +211,8 @@ func (e *Episode) Streams() ([]*Stream, error) {
return e.children, nil return e.children, nil
} }
streams, err := fromVideoStreams(e.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", streams, err := fromVideoStreams(e.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
e.crunchy.Config.CountryCode, e.crunchy.Config.Bucket,
e.crunchy.Config.MaturityRating,
e.crunchy.Config.Channel,
e.StreamID, e.StreamID,
e.crunchy.Locale, e.crunchy.Locale,
e.crunchy.Config.Signature, e.crunchy.Config.Signature,

View file

@ -41,10 +41,8 @@ type MovieListing struct {
// MovieListingFromID returns a movie listing by its api id. // MovieListingFromID returns a movie listing by its api id.
func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) { func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) {
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -70,10 +68,8 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error)
// AudioLocale is same as Episode.AudioLocale. // AudioLocale is same as Episode.AudioLocale.
func (ml *MovieListing) AudioLocale() (LOCALE, error) { func (ml *MovieListing) AudioLocale() (LOCALE, error) {
resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
ml.crunchy.Config.CountryCode, ml.crunchy.Config.Bucket,
ml.crunchy.Config.MaturityRating,
ml.crunchy.Config.Channel,
ml.ID, ml.ID,
ml.crunchy.Locale, ml.crunchy.Locale,
ml.crunchy.Config.Signature, ml.crunchy.Config.Signature,
@ -91,10 +87,8 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) {
// Streams returns all streams which are available for the movie listing. // Streams returns all streams which are available for the movie listing.
func (ml *MovieListing) Streams() ([]*Stream, error) { func (ml *MovieListing) Streams() ([]*Stream, error) {
return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
ml.crunchy.Config.CountryCode, ml.crunchy.Config.Bucket,
ml.crunchy.Config.MaturityRating,
ml.crunchy.Config.Channel,
ml.ID, ml.ID,
ml.crunchy.Locale, ml.crunchy.Locale,
ml.crunchy.Config.Signature, ml.crunchy.Config.Signature,

View file

@ -38,17 +38,15 @@ type Season struct {
AvailabilityNotes string `json:"availability_notes"` AvailabilityNotes string `json:"availability_notes"`
// the locales are always empty, idk why this may change in the future // the locales are always empty, idk why, this may change in the future
AudioLocales []LOCALE AudioLocales []LOCALE
SubtitleLocales []LOCALE SubtitleLocales []LOCALE
} }
// SeasonFromID returns a season by its api id. // SeasonFromID returns a season by its api id.
func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -73,6 +71,8 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
} }
// AudioLocale returns the audio locale of the season. // AudioLocale returns the audio locale of the season.
// Will fail if no streams are available, thus use Season.Available
// to prevent any misleading errors.
func (s *Season) AudioLocale() (LOCALE, error) { func (s *Season) AudioLocale() (LOCALE, error) {
episodes, err := s.Episodes() episodes, err := s.Episodes()
if err != nil { if err != nil {
@ -81,16 +81,23 @@ func (s *Season) AudioLocale() (LOCALE, error) {
return episodes[0].AudioLocale() return episodes[0].AudioLocale()
} }
// Available returns if downloadable streams for this season are available.
func (s *Season) Available() (bool, error) {
episodes, err := s.Episodes()
if err != nil {
return false, err
}
return episodes[0].Available(), nil
}
// Episodes returns all episodes which are available for the season. // Episodes returns all episodes which are available for the season.
func (s *Season) Episodes() (episodes []*Episode, err error) { func (s *Season) Episodes() (episodes []*Episode, err error) {
if s.children != nil { if s.children != nil {
return s.children, nil return s.children, nil
} }
resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes?season_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes?season_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
s.crunchy.Config.CountryCode, s.crunchy.Config.Bucket,
s.crunchy.Config.MaturityRating,
s.crunchy.Config.Channel,
s.ID, s.ID,
s.crunchy.Locale, s.crunchy.Locale,
s.crunchy.Config.Signature, s.crunchy.Config.Signature,
@ -112,8 +119,10 @@ func (s *Season) Episodes() (episodes []*Episode, err error) {
} }
if episode.Playback != "" { if episode.Playback != "" {
streamHref := item.(map[string]interface{})["__links__"].(map[string]interface{})["streams"].(map[string]interface{})["href"].(string) streamHref := item.(map[string]interface{})["__links__"].(map[string]interface{})["streams"].(map[string]interface{})["href"].(string)
if match := regexp.MustCompile(`(?m)^/cms/v2/\S+videos/(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 { if match := regexp.MustCompile(`(?m)(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 {
episode.StreamID = match[0][1] episode.StreamID = match[0][1]
} else {
fmt.Println()
} }
} }
episodes = append(episodes, episode) episodes = append(episodes, episode)

View file

@ -25,10 +25,8 @@ type Stream struct {
// StreamsFromID returns a stream by its api id. // StreamsFromID returns a stream by its api id.
func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) { func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -82,8 +80,12 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream,
json.NewDecoder(resp.Body).Decode(&jsonBody) json.NewDecoder(resp.Body).Decode(&jsonBody)
if len(jsonBody) == 0 { if len(jsonBody) == 0 {
// this may get thrown when the crunchyroll account has just a normal account and not one with premium // this may get thrown when the crunchyroll account is just a normal account and not one with premium
return nil, fmt.Errorf("no stream available") if !crunchy.Config.Premium {
return nil, fmt.Errorf("no stream available, this might be the result of using a non-premium account")
} else {
return nil, fmt.Errorf("no stream available")
}
} }
audioLocale := jsonBody["audio_locale"].(string) audioLocale := jsonBody["audio_locale"].(string)
@ -105,7 +107,7 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream,
var id string var id string
var formatType FormatType var formatType FormatType
href := jsonBody["__links__"].(map[string]interface{})["resource"].(map[string]interface{})["href"].(string) href := jsonBody["__links__"].(map[string]interface{})["resource"].(map[string]interface{})["href"].(string)
if match := regexp.MustCompile(`(?sm)^/cms/v2/\S+/crunchyroll/(\w+)/(\w+)$`).FindAllStringSubmatch(href, -1); len(match) > 0 { if match := regexp.MustCompile(`(?sm)/(\w+)/(\w+)$`).FindAllStringSubmatch(href, -1); len(match) > 0 {
formatType = FormatType(match[0][1]) formatType = FormatType(match[0][1])
id = match[0][2] id = match[0][2]
} }

20
url.go
View file

@ -13,6 +13,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
} }
var eps []*Episode var eps []*Episode
var notAvailableContinue bool
if series != nil { if series != nil {
seasons, err := series.Seasons() seasons, err := series.Seasons()
@ -21,6 +22,13 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
} }
for _, season := range seasons { for _, season := range seasons {
if audio != nil { if audio != nil {
if available, err := season.Available(); err != nil {
return nil, err
} else if !available {
notAvailableContinue = true
continue
}
locale, err := season.AudioLocale() locale, err := season.AudioLocale()
if err != nil { if err != nil {
return nil, err return nil, err
@ -49,6 +57,12 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
} }
for _, episode := range episodes { for _, episode := range episodes {
// if no episode streams are available, calling episode.AudioLocale
// will result in an unwanted error
if !episode.Available() {
notAvailableContinue = true
continue
}
locale, err := episode.AudioLocale() locale, err := episode.AudioLocale()
if err != nil { if err != nil {
return nil, err return nil, err
@ -71,7 +85,11 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
} }
if len(eps) == 0 { if len(eps) == 0 {
return nil, fmt.Errorf("could not find any matching episode") if notAvailableContinue {
return nil, fmt.Errorf("could not find any matching episode which is accessable with a non-premium account")
} else {
return nil, fmt.Errorf("could not find any matching episode")
}
} }
return eps, nil return eps, nil

View file

@ -52,6 +52,9 @@ func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCAL
var wg sync.WaitGroup var wg sync.WaitGroup
var lock sync.Mutex var lock sync.Mutex
for _, episode := range episodes { for _, episode := range episodes {
if !episode.Available() {
continue
}
episode := episode episode := episode
wg.Add(1) wg.Add(1)
go func() { go func() {

View file

@ -70,10 +70,8 @@ type Movie struct {
// MovieFromID returns a movie by its api id. // MovieFromID returns a movie by its api id.
func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -103,10 +101,8 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
return m.children, nil return m.children, nil
} }
resp, err := m.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := m.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
m.crunchy.Config.CountryCode, m.crunchy.Config.Bucket,
m.crunchy.Config.MaturityRating,
m.crunchy.Config.Channel,
m.ID, m.ID,
m.crunchy.Locale, m.crunchy.Locale,
m.crunchy.Config.Signature, m.crunchy.Config.Signature,
@ -166,10 +162,8 @@ type Series struct {
// SeriesFromID returns a series by its api id. // SeriesFromID returns a series by its api id.
func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
crunchy.Config.CountryCode, crunchy.Config.Bucket,
crunchy.Config.MaturityRating,
crunchy.Config.Channel,
id, id,
crunchy.Locale, crunchy.Locale,
crunchy.Config.Signature, crunchy.Config.Signature,
@ -199,10 +193,8 @@ func (s *Series) Seasons() (seasons []*Season, err error) {
return s.children, nil return s.children, nil
} }
resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
s.crunchy.Config.CountryCode, s.crunchy.Config.Bucket,
s.crunchy.Config.MaturityRating,
s.crunchy.Config.Channel,
s.ID, s.ID,
s.crunchy.Locale, s.crunchy.Locale,
s.crunchy.Config.Signature, s.crunchy.Config.Signature,