mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 11:46:36 +08:00
778bf77437
Complete the port of the remaining MediaStation features:
Backend services:
- service/backup.go: SQLite hot backup via VACUUM INTO, list, delete,
restore. Backup files stored under {data_dir}/backups/ with timestamps.
- service/notifier.go: Multi-channel push notifications (Telegram /
Bark / WeChat Server酱 / Webhook). Configuration via settings table
keys notify.telegram.*, notify.bark.*, etc.
- service/organizer.go: Auto-rename + move media files into library
directory structure. Movie: {Title} ({Year})/{Title} ({Year}).ext,
TV: {Title}/Season XX/{Title} - SxxExx.ext. Both per-media and
per-library batch endpoints.
- service/douban.go: Douban (豆瓣) metadata provider using the
unofficial subject_suggest API. Returns Chinese titles + posters.
Requires douban_cookie in secrets config.
Handlers:
- handler/backup.go: GET/POST/DELETE /admin/backups, POST restore.
- handler/organizer.go: POST /admin/media/:id/organize,
POST /admin/libraries/:id/organize.
- handler/notify.go: POST /admin/notify/test.
Frontend pages:
- WatchHistoryPage.tsx: full paginated history with progress bars,
resume buttons, poster thumbnails and timestamps.
- PosterWallPage.tsx: dense 8-column poster grid of all media
(matches the original MediaStation PosterWallView.vue).
Sidebar additions: 观看历史 + 海报墙 links in the main nav group.
Verified: go build, go vet, go test all pass; tsc -b && vite build
emits 30 lazy chunks; main bundle 254 KB / 85 KB gzipped.
121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
// Package service — Douban (豆瓣) metadata provider.
|
|
//
|
|
// Douban is the dominant Chinese movie/TV rating and metadata site. Its
|
|
// unofficial API returns rich Chinese-language titles, overviews, ratings
|
|
// and poster URLs. A valid Douban cookie is required to avoid IP bans.
|
|
//
|
|
// We use the search endpoint at:
|
|
// https://movie.douban.com/j/subject_suggest?q=...
|
|
//
|
|
// And the detail endpoint at:
|
|
// https://movie.douban.com/j/subject_abstract?subject_id=...
|
|
//
|
|
// The provider is used as a supplemental source: after TMDb matches we
|
|
// attempt a Douban lookup to grab a localized Chinese title + overview.
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ShukeBta/MediaStationGo/internal/config"
|
|
)
|
|
|
|
// DoubanProvider talks to the unofficial Douban movie API.
|
|
type DoubanProvider struct {
|
|
cfg *config.Config
|
|
log *zap.Logger
|
|
client *http.Client
|
|
}
|
|
|
|
// NewDoubanProvider is the constructor.
|
|
func NewDoubanProvider(cfg *config.Config, log *zap.Logger) *DoubanProvider {
|
|
return &DoubanProvider{
|
|
cfg: cfg,
|
|
log: log,
|
|
client: &http.Client{Timeout: 15 * time.Second},
|
|
}
|
|
}
|
|
|
|
// Enabled reports whether a Douban cookie is configured.
|
|
func (d *DoubanProvider) Enabled() bool {
|
|
return strings.TrimSpace(d.cfg.Secrets.DoubanCookie) != ""
|
|
}
|
|
|
|
// userAgents for anti-scraping randomization.
|
|
var userAgents = []string{
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
|
}
|
|
|
|
// DoubanMatch is the result of a Douban search hit.
|
|
type DoubanMatch struct {
|
|
DoubanID string `json:"douban_id"`
|
|
Title string `json:"title"`
|
|
Year string `json:"year"`
|
|
Img string `json:"img"`
|
|
Rating float32 `json:"rating"`
|
|
}
|
|
|
|
// Search runs a Douban subject_suggest query and returns the top match.
|
|
func (d *DoubanProvider) Search(ctx context.Context, query string) (*DoubanMatch, error) {
|
|
if !d.Enabled() || query == "" {
|
|
return nil, nil
|
|
}
|
|
u := "https://movie.douban.com/j/subject_suggest?q=" + url.QueryEscape(query)
|
|
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 search: %d", resp.StatusCode)
|
|
}
|
|
|
|
type suggestion struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Year string `json:"year"`
|
|
Img string `json:"img"`
|
|
}
|
|
var results []suggestion
|
|
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, nil
|
|
}
|
|
r := results[0]
|
|
return &DoubanMatch{
|
|
DoubanID: r.ID,
|
|
Title: r.Title,
|
|
Year: r.Year,
|
|
Img: r.Img,
|
|
}, nil
|
|
}
|
|
|
|
func (d *DoubanProvider) setHeaders(req *http.Request) {
|
|
req.Header.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
|
|
req.Header.Set("Referer", "https://movie.douban.com/")
|
|
req.Header.Set("Accept", "application/json, text/plain, */*")
|
|
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
|
if cookie := strings.TrimSpace(d.cfg.Secrets.DoubanCookie); cookie != "" {
|
|
req.Header.Set("Cookie", cookie)
|
|
}
|
|
}
|