mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
split discover provider rails
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Package service — Bangumi discovery calendar.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Calendar returns Bangumi's public on-air anime calendar as a recommendation
|
||||
// rail. It needs no token, but NewBangumiProvider still attaches one when set.
|
||||
func (b *BangumiProvider) Calendar(ctx context.Context) ([]ExternalMediaResult, error) {
|
||||
type subject struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
NameCN string `json:"name_cn"`
|
||||
Summary string `json:"summary"`
|
||||
AirDate string `json:"air_date"`
|
||||
Images struct {
|
||||
Large string `json:"large"`
|
||||
Common string `json:"common"`
|
||||
} `json:"images"`
|
||||
Rating struct {
|
||||
Score float32 `json:"score"`
|
||||
} `json:"rating"`
|
||||
}
|
||||
type day struct {
|
||||
Items []subject `json:"items"`
|
||||
}
|
||||
var days []day
|
||||
if err := b.getJSON(ctx, b.base+"/calendar", &days); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]ExternalMediaResult, 0, 24)
|
||||
seen := map[int]struct{}{}
|
||||
for _, day := range days {
|
||||
for _, item := range day.Items {
|
||||
if _, ok := seen[item.ID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item.ID] = struct{}{}
|
||||
title := strings.TrimSpace(item.NameCN)
|
||||
if title == "" {
|
||||
title = strings.TrimSpace(item.Name)
|
||||
}
|
||||
if title == "" {
|
||||
continue
|
||||
}
|
||||
poster := item.Images.Large
|
||||
if poster == "" {
|
||||
poster = item.Images.Common
|
||||
}
|
||||
poster = normalizeBangumiImageURL(poster)
|
||||
year := 0
|
||||
if len(item.AirDate) >= 4 {
|
||||
year, _ = strconv.Atoi(item.AirDate[:4])
|
||||
}
|
||||
out = append(out, ExternalMediaResult{
|
||||
Source: "bangumi",
|
||||
MediaType: "anime",
|
||||
Title: title,
|
||||
OriginalName: item.Name,
|
||||
Overview: item.Summary,
|
||||
PosterURL: poster,
|
||||
Year: year,
|
||||
Rating: item.Rating.Score,
|
||||
BangumiID: item.ID,
|
||||
SubscribeKeyword: buildSubscribeKeyword(title, year),
|
||||
SubscribeAliases: buildSubscribeAliases(title, item.Name, year),
|
||||
})
|
||||
if len(out) >= 24 {
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -198,150 +198,3 @@ func tmdbDiscoverPath(key string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Discover returns public Douban movie/TV rails. Douban does not require a
|
||||
// formal API key here; these are the same public web endpoints the site uses.
|
||||
func (d *DoubanProvider) Discover(ctx context.Context, key string, pages ...int) ([]ExternalMediaResult, error) {
|
||||
doubanType := "movie"
|
||||
tag := "热门"
|
||||
switch key {
|
||||
case "douban_hot_movie":
|
||||
doubanType = "movie"
|
||||
tag = "热门"
|
||||
case "douban_top_movie":
|
||||
doubanType = "movie"
|
||||
tag = "高分"
|
||||
case "douban_hot_tv":
|
||||
doubanType = "tv"
|
||||
tag = "热门"
|
||||
default:
|
||||
return []ExternalMediaResult{}, nil
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("type", doubanType)
|
||||
q.Set("tag", tag)
|
||||
q.Set("sort", "recommend")
|
||||
q.Set("page_limit", "24")
|
||||
pageNumber := 1
|
||||
if len(pages) > 0 && pages[0] > 0 {
|
||||
pageNumber = pages[0]
|
||||
}
|
||||
q.Set("page_start", strconv.Itoa((pageNumber-1)*24))
|
||||
u := "https://movie.douban.com/j/search_subjects?" + q.Encode()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.setHeaders(req)
|
||||
resp, err := d.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("douban discover: %d", resp.StatusCode)
|
||||
}
|
||||
var page struct {
|
||||
Subjects []struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Rate string `json:"rate"`
|
||||
Cover string `json:"cover"`
|
||||
URL string `json:"url"`
|
||||
} `json:"subjects"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&page); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]ExternalMediaResult, 0, len(page.Subjects))
|
||||
mediaType := "movie"
|
||||
if doubanType == "tv" {
|
||||
mediaType = "tv"
|
||||
}
|
||||
for _, subject := range page.Subjects {
|
||||
if strings.TrimSpace(subject.Title) == "" {
|
||||
continue
|
||||
}
|
||||
rating, _ := strconv.ParseFloat(subject.Rate, 32)
|
||||
out = append(out, ExternalMediaResult{
|
||||
Source: "douban",
|
||||
MediaType: mediaType,
|
||||
Title: subject.Title,
|
||||
PosterURL: subject.Cover,
|
||||
Rating: float32(rating),
|
||||
DoubanID: subject.ID,
|
||||
SubscribeKeyword: subject.Title,
|
||||
SubscribeAliases: buildSubscribeAliases(subject.Title, "", 0),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Calendar returns Bangumi's public on-air anime calendar as a recommendation
|
||||
// rail. It needs no token, but NewBangumiProvider still attaches one when set.
|
||||
func (b *BangumiProvider) Calendar(ctx context.Context) ([]ExternalMediaResult, error) {
|
||||
type subject struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
NameCN string `json:"name_cn"`
|
||||
Summary string `json:"summary"`
|
||||
AirDate string `json:"air_date"`
|
||||
Images struct {
|
||||
Large string `json:"large"`
|
||||
Common string `json:"common"`
|
||||
} `json:"images"`
|
||||
Rating struct {
|
||||
Score float32 `json:"score"`
|
||||
} `json:"rating"`
|
||||
}
|
||||
type day struct {
|
||||
Items []subject `json:"items"`
|
||||
}
|
||||
var days []day
|
||||
if err := b.getJSON(ctx, b.base+"/calendar", &days); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]ExternalMediaResult, 0, 24)
|
||||
seen := map[int]struct{}{}
|
||||
for _, day := range days {
|
||||
for _, item := range day.Items {
|
||||
if _, ok := seen[item.ID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item.ID] = struct{}{}
|
||||
title := strings.TrimSpace(item.NameCN)
|
||||
if title == "" {
|
||||
title = strings.TrimSpace(item.Name)
|
||||
}
|
||||
if title == "" {
|
||||
continue
|
||||
}
|
||||
poster := item.Images.Large
|
||||
if poster == "" {
|
||||
poster = item.Images.Common
|
||||
}
|
||||
poster = normalizeBangumiImageURL(poster)
|
||||
year := 0
|
||||
if len(item.AirDate) >= 4 {
|
||||
year, _ = strconv.Atoi(item.AirDate[:4])
|
||||
}
|
||||
out = append(out, ExternalMediaResult{
|
||||
Source: "bangumi",
|
||||
MediaType: "anime",
|
||||
Title: title,
|
||||
OriginalName: item.Name,
|
||||
Overview: item.Summary,
|
||||
PosterURL: poster,
|
||||
Year: year,
|
||||
Rating: item.Rating.Score,
|
||||
BangumiID: item.ID,
|
||||
SubscribeKeyword: buildSubscribeKeyword(title, year),
|
||||
SubscribeAliases: buildSubscribeAliases(title, item.Name, year),
|
||||
})
|
||||
if len(out) >= 24 {
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Package service — Douban discovery rails.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Discover returns public Douban movie/TV rails. Douban does not require a
|
||||
// formal API key here; these are the same public web endpoints the site uses.
|
||||
func (d *DoubanProvider) Discover(ctx context.Context, key string, pages ...int) ([]ExternalMediaResult, error) {
|
||||
doubanType := "movie"
|
||||
tag := "热门"
|
||||
switch key {
|
||||
case "douban_hot_movie":
|
||||
doubanType = "movie"
|
||||
tag = "热门"
|
||||
case "douban_top_movie":
|
||||
doubanType = "movie"
|
||||
tag = "高分"
|
||||
case "douban_hot_tv":
|
||||
doubanType = "tv"
|
||||
tag = "热门"
|
||||
default:
|
||||
return []ExternalMediaResult{}, nil
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("type", doubanType)
|
||||
q.Set("tag", tag)
|
||||
q.Set("sort", "recommend")
|
||||
q.Set("page_limit", "24")
|
||||
pageNumber := 1
|
||||
if len(pages) > 0 && pages[0] > 0 {
|
||||
pageNumber = pages[0]
|
||||
}
|
||||
q.Set("page_start", strconv.Itoa((pageNumber-1)*24))
|
||||
u := "https://movie.douban.com/j/search_subjects?" + q.Encode()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.setHeaders(req)
|
||||
resp, err := d.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("douban discover: %d", resp.StatusCode)
|
||||
}
|
||||
var page struct {
|
||||
Subjects []struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Rate string `json:"rate"`
|
||||
Cover string `json:"cover"`
|
||||
URL string `json:"url"`
|
||||
} `json:"subjects"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&page); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]ExternalMediaResult, 0, len(page.Subjects))
|
||||
mediaType := "movie"
|
||||
if doubanType == "tv" {
|
||||
mediaType = "tv"
|
||||
}
|
||||
for _, subject := range page.Subjects {
|
||||
if strings.TrimSpace(subject.Title) == "" {
|
||||
continue
|
||||
}
|
||||
rating, _ := strconv.ParseFloat(subject.Rate, 32)
|
||||
out = append(out, ExternalMediaResult{
|
||||
Source: "douban",
|
||||
MediaType: mediaType,
|
||||
Title: subject.Title,
|
||||
PosterURL: subject.Cover,
|
||||
Rating: float32(rating),
|
||||
DoubanID: subject.ID,
|
||||
SubscribeKeyword: subject.Title,
|
||||
SubscribeAliases: buildSubscribeAliases(subject.Title, "", 0),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user