mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-06 13:26:38 +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 ""
|
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