mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 16:26:36 +08:00
refactor: split site adapters and site page modules
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
// Package service — Gazelle site adapter.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ─── Gazelle 适配器 ──────────────────────────────────────────────────────────
|
||||
|
||||
// GazelleAdapter Gazelle 框架适配器(What.cd 开源)。
|
||||
type GazelleAdapter struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewGazelleAdapter 创建 Gazelle 适配器。
|
||||
func NewGazelleAdapter() *GazelleAdapter {
|
||||
return &GazelleAdapter{
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *GazelleAdapter) Authenticate(ctx context.Context, cfg SiteConfig) error {
|
||||
u := cfg.URL + "/ajax.php?action=index"
|
||||
data, status, err := doRequest(ctx, a.client, "GET", u, cfg, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticate: %w", err)
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return fmt.Errorf("authenticate failed: status %d", status)
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return fmt.Errorf("parse response: %w", err)
|
||||
}
|
||||
if statusMsg, ok := result["status"].(string); ok && statusMsg == "failure" {
|
||||
return fmt.Errorf("authentication failed: %v", result["error"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *GazelleAdapter) Search(ctx context.Context, cfg SiteConfig, keyword string, page int) (*SiteSearchResult, error) {
|
||||
params := url.Values{}
|
||||
params.Set("action", "browse")
|
||||
params.Set("searchstr", keyword)
|
||||
params.Set("page", strconv.Itoa(page))
|
||||
|
||||
u := cfg.URL + "/ajax.php?" + params.Encode()
|
||||
data, status, err := doRequest(ctx, a.client, "GET", u, cfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search: %w", err)
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return nil, fmt.Errorf("search failed: status %d", status)
|
||||
}
|
||||
|
||||
return parseGazelleJSON(data, cfg.Name, cfg.URL)
|
||||
}
|
||||
|
||||
func (a *GazelleAdapter) Browse(ctx context.Context, cfg SiteConfig, category string, page int) (*SiteSearchResult, error) {
|
||||
params := url.Values{}
|
||||
params.Set("action", "browse")
|
||||
if category != "" {
|
||||
params.Set("filter_cat["+category+"]", "1")
|
||||
}
|
||||
params.Set("page", strconv.Itoa(page))
|
||||
|
||||
u := cfg.URL + "/ajax.php?" + params.Encode()
|
||||
data, status, err := doRequest(ctx, a.client, "GET", u, cfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("browse: %w", err)
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return nil, fmt.Errorf("browse failed: status %d", status)
|
||||
}
|
||||
|
||||
return parseGazelleJSON(data, cfg.Name, cfg.URL)
|
||||
}
|
||||
|
||||
func (a *GazelleAdapter) GetDetail(ctx context.Context, cfg SiteConfig, id string) (*TorrentDetail, error) {
|
||||
params := url.Values{}
|
||||
params.Set("action", "torrent")
|
||||
params.Set("id", id)
|
||||
|
||||
u := cfg.URL + "/ajax.php?" + params.Encode()
|
||||
data, status, err := doRequest(ctx, a.client, "GET", u, cfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("detail: %w", err)
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return nil, fmt.Errorf("detail failed: status %d", status)
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
return nil, fmt.Errorf("parse response: %w", err)
|
||||
}
|
||||
|
||||
torrent, ok := resp["torrent"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("torrent not found")
|
||||
}
|
||||
|
||||
detail := &TorrentDetail{
|
||||
ID: id,
|
||||
DetailURL: cfg.URL + "/torrents.php?torrentid=" + id,
|
||||
DownloadURL: cfg.URL + "/torrents.php?action=download&id=" + id,
|
||||
}
|
||||
|
||||
if v, ok := torrent["groupName"].(string); ok {
|
||||
detail.Title = v
|
||||
}
|
||||
if v, ok := torrent["subName"].(string); ok {
|
||||
detail.Subtitle = v
|
||||
}
|
||||
if v, ok := torrent["size"].(float64); ok {
|
||||
detail.Size = int64(v)
|
||||
}
|
||||
if v, ok := torrent["seeders"].(float64); ok {
|
||||
detail.Seeders = int(v)
|
||||
}
|
||||
if v, ok := torrent["leechers"].(float64); ok {
|
||||
detail.Leechers = int(v)
|
||||
}
|
||||
if v, ok := torrent["snatched"].(float64); ok {
|
||||
detail.Snatched = int(v)
|
||||
}
|
||||
if v, ok := torrent["freeTorrent"].(string); ok && v == "1" {
|
||||
detail.Free = true
|
||||
}
|
||||
if v, ok := torrent["freeTorrent"].(bool); ok {
|
||||
detail.Free = v
|
||||
}
|
||||
if v, ok := torrent["infoHash"].(string); ok {
|
||||
detail.InfoHash = v
|
||||
}
|
||||
if v, ok := torrent["groupDesc"].(string); ok {
|
||||
detail.Description = stripHTML(v)
|
||||
}
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
func (a *GazelleAdapter) GetDownloadURL(ctx context.Context, cfg SiteConfig, id string) (string, error) {
|
||||
return cfg.URL + "/torrents.php?action=download&id=" + id, nil
|
||||
}
|
||||
|
||||
// parseGazelleJSON 解析 Gazelle JSON 响应。
|
||||
func parseGazelleJSON(data []byte, siteName, baseURL string) (*SiteSearchResult, error) {
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
return nil, fmt.Errorf("parse JSON: %w", err)
|
||||
}
|
||||
|
||||
result := &SiteSearchResult{
|
||||
SiteName: siteName,
|
||||
Items: []TorrentItem{},
|
||||
}
|
||||
|
||||
if status, ok := resp["status"].(string); ok && status == "failure" {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
results, ok := resp["results"].([]interface{})
|
||||
if !ok {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, r := range results {
|
||||
torrent, ok := r.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item := TorrentItem{}
|
||||
if v, ok := torrent["torrentId"].(float64); ok {
|
||||
item.ID = strconv.Itoa(int(v))
|
||||
}
|
||||
if v, ok := torrent["groupName"].(string); ok {
|
||||
item.Title = v
|
||||
}
|
||||
if v, ok := torrent["artist"].(string); ok {
|
||||
item.Subtitle = v
|
||||
}
|
||||
if v, ok := torrent["category"].(string); ok {
|
||||
item.Category = v
|
||||
}
|
||||
if v, ok := torrent["size"].(float64); ok {
|
||||
item.Size = int64(v)
|
||||
}
|
||||
if v, ok := torrent["seeders"].(float64); ok {
|
||||
item.Seeders = int(v)
|
||||
}
|
||||
if v, ok := torrent["leechers"].(float64); ok {
|
||||
item.Leechers = int(v)
|
||||
}
|
||||
if v, ok := torrent["snatched"].(float64); ok {
|
||||
item.Snatched = int(v)
|
||||
}
|
||||
if v, ok := torrent["freeTorrent"].(string); ok && v == "1" {
|
||||
item.Free = true
|
||||
}
|
||||
if v, ok := torrent["freeTorrent"].(bool); ok {
|
||||
item.Free = v
|
||||
}
|
||||
if v, ok := torrent["time"].(string); ok {
|
||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||
item.UploadTime = t
|
||||
}
|
||||
}
|
||||
|
||||
item.DetailURL = baseURL + "/torrents.php?torrentid=" + item.ID
|
||||
item.DownloadURL = baseURL + "/torrents.php?action=download&id=" + item.ID
|
||||
result.Items = append(result.Items, item)
|
||||
}
|
||||
|
||||
if total, ok := resp["totalResults"].(float64); ok {
|
||||
result.Total = int(total)
|
||||
} else {
|
||||
result.Total = len(result.Items)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user