Files
tver/internal/tver/series.go
T

144 lines
4.4 KiB
Go

package tver
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
)
var tverSeriesIDPattern = regexp.MustCompile(`^[A-Za-z0-9]+$`)
// ErrSeriesNotFound は callSeries が HTTP 404 を返したことを表す。
var ErrSeriesNotFound = errors.New("series not found")
type seriesContents struct {
Code int `json:"code"`
Result struct {
Contents []struct {
Type string `json:"type"`
Content Episode `json:"content"`
} `json:"contents"`
} `json:"result"`
}
func (c *Client) FetchSeries(ctx context.Context, seriesID string) (string, error) {
if !tverSeriesIDPattern.MatchString(seriesID) {
return "", errors.New("invalid series id")
}
session, err := c.CreateSession(ctx)
if err != nil {
return "", err
}
endpoint := c.baseURL.ResolveReference(&url.URL{Path: "/service/api/v2/callSeries/" + seriesID})
query := endpoint.Query()
query.Set("platform_uid", session.PlatformUID)
query.Set("platform_token", session.PlatformToken)
endpoint.RawQuery = query.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return "", fmt.Errorf("series request: %w", err)
}
setTVerHeaders(request)
var response struct {
Code int `json:"code"`
Result struct {
Content struct {
Content struct {
Title string `json:"title"`
} `json:"content"`
} `json:"content"`
} `json:"result"`
}
if err := c.getJSON(request, &response); err != nil {
var statusErr *httpStatusError
if errors.As(err, &statusErr) && statusErr.statusCode == http.StatusNotFound {
return "", ErrSeriesNotFound
}
return "", fmt.Errorf("series: %w", err)
}
if response.Code != 0 || response.Result.Content.Content.Title == "" {
return "", fmt.Errorf("series: invalid API result (code %d)", response.Code)
}
return response.Result.Content.Content.Title, nil
}
func (c *Client) FetchSeriesEpisodes(ctx context.Context, seriesID string) ([]Episode, error) {
if !tverSeriesIDPattern.MatchString(seriesID) {
return nil, errors.New("invalid series id")
}
seasonsURL := c.serviceURL.ResolveReference(&url.URL{Path: "/api/v1/callSeriesSeasons/" + seriesID})
request, err := http.NewRequestWithContext(ctx, http.MethodGet, seasonsURL.String(), nil)
if err != nil {
return nil, fmt.Errorf("series seasons request: %w", err)
}
setTVerHeaders(request)
var seasons seriesContents
if err := c.getJSON(request, &seasons); err != nil {
return nil, fmt.Errorf("series seasons: %w", err)
}
if seasons.Code != 0 || seasons.Result.Contents == nil {
return nil, fmt.Errorf("series seasons: invalid API result (code %d)", seasons.Code)
}
var seasonIDs []string
for _, item := range seasons.Result.Contents {
if item.Type != "season" {
continue
}
if !tverSeriesIDPattern.MatchString(item.Content.ID) {
return nil, errors.New("series seasons: invalid season id")
}
seasonIDs = append(seasonIDs, item.Content.ID)
}
if len(seasonIDs) == 0 {
return []Episode{}, nil
}
session, err := c.CreateSession(ctx)
if err != nil {
return nil, err
}
seen := make(map[string]bool)
var episodes []Episode
for _, seasonID := range seasonIDs {
endpoint := c.baseURL.ResolveReference(&url.URL{Path: "/service/api/v1/callSeasonEpisodes/" + seasonID})
query := endpoint.Query()
query.Set("platform_uid", session.PlatformUID)
query.Set("platform_token", session.PlatformToken)
endpoint.RawQuery = query.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, fmt.Errorf("season episodes request: %w", err)
}
setTVerHeaders(request)
var response seriesContents
if err := c.getJSON(request, &response); err != nil {
return nil, fmt.Errorf("season episodes: %w", err)
}
if response.Code != 0 || response.Result.Contents == nil {
return nil, fmt.Errorf("season episodes: invalid API result (code %d)", response.Code)
}
for _, item := range response.Result.Contents {
if item.Type != "episode" {
continue
}
ep := item.Content
if !tverSeriesIDPattern.MatchString(ep.ID) || ep.SeriesID != seriesID {
return nil, errors.New("season episodes: invalid episode identity")
}
if !seen[ep.ID] {
episodes = append(episodes, ep)
seen[ep.ID] = true
}
}
}
return episodes, nil
}
func setTVerHeaders(request *http.Request) {
request.Header.Set("x-tver-platform-type", "web")
request.Header.Set("Origin", "https://tver.jp")
request.Header.Set("Referer", "https://tver.jp/")
}