mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 19:36:36 +08:00
168 lines
4.8 KiB
Go
168 lines
4.8 KiB
Go
// Package service — Discuz site adapter.
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ─── Discuz 适配器 ───────────────────────────────────────────────────────────
|
|
|
|
// DiscuzAdapter 基于 Discuz! X 的站点适配器。
|
|
type DiscuzAdapter struct {
|
|
client *http.Client
|
|
}
|
|
|
|
// NewDiscuzAdapter 创建 Discuz 适配器。
|
|
func NewDiscuzAdapter() *DiscuzAdapter {
|
|
return &DiscuzAdapter{
|
|
client: &http.Client{Timeout: 30 * time.Second},
|
|
}
|
|
}
|
|
|
|
func (a *DiscuzAdapter) Authenticate(ctx context.Context, cfg SiteConfig) error {
|
|
u := cfg.URL + "/home.php?mod=space"
|
|
data, status, err := doRequest(ctx, a.client, "GET", u, cfg, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("authenticate: %w", err)
|
|
}
|
|
if status == http.StatusFound {
|
|
return fmt.Errorf("authentication failed: redirected to login")
|
|
}
|
|
if status != http.StatusOK {
|
|
return fmt.Errorf("authenticate failed: status %d", status)
|
|
}
|
|
body := string(data)
|
|
if strings.Contains(body, "login") && !strings.Contains(body, "我的空间") {
|
|
return fmt.Errorf("authentication failed: not logged in")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *DiscuzAdapter) Search(ctx context.Context, cfg SiteConfig, keyword string, page int) (*SiteSearchResult, error) {
|
|
params := url.Values{}
|
|
params.Set("mod", "forum")
|
|
params.Set("srchtxt", keyword)
|
|
params.Set("searchsubmit", "true")
|
|
params.Set("page", strconv.Itoa(page))
|
|
|
|
u := cfg.URL + "/search.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 parseDiscuzHTML(string(data), cfg.Name, cfg.URL)
|
|
}
|
|
|
|
func (a *DiscuzAdapter) Browse(ctx context.Context, cfg SiteConfig, category string, page int) (*SiteSearchResult, error) {
|
|
params := url.Values{}
|
|
if category != "" {
|
|
params.Set("fid", category)
|
|
}
|
|
params.Set("page", strconv.Itoa(page))
|
|
|
|
u := cfg.URL + "/forum.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 parseDiscuzHTML(string(data), cfg.Name, cfg.URL)
|
|
}
|
|
|
|
func (a *DiscuzAdapter) GetDetail(ctx context.Context, cfg SiteConfig, id string) (*TorrentDetail, error) {
|
|
u := cfg.URL + "/forum.php?mod=viewthread&tid=" + id
|
|
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)
|
|
}
|
|
|
|
html := string(data)
|
|
detail := &TorrentDetail{
|
|
ID: id,
|
|
DetailURL: cfg.URL + "/forum.php?mod=viewthread&tid=" + id,
|
|
}
|
|
|
|
// Title
|
|
titleRegex := regexp.MustCompile(`<span[^>]*id="thread_subject"[^>]*>([^<]+)</span>`)
|
|
if m := titleRegex.FindStringSubmatch(html); len(m) >= 2 {
|
|
detail.Title = strings.TrimSpace(m[1])
|
|
}
|
|
|
|
// Extract magnet/torrent links
|
|
magnetRegex := regexp.MustCompile(`magnet:\?[^\s"'<>]+`)
|
|
if m := magnetRegex.FindString(html); m != "" {
|
|
detail.DownloadURL = m
|
|
}
|
|
torrentRegex := regexp.MustCompile(`(attachment\.php\?aid=\d+)`)
|
|
if m := torrentRegex.FindString(html); m != "" && detail.DownloadURL == "" {
|
|
detail.DownloadURL = cfg.URL + "/" + m
|
|
}
|
|
|
|
// Description
|
|
descRegex := regexp.MustCompile(`<div[^>]*class="t_fsz"[^>]*>(.*?)</div>`)
|
|
if m := descRegex.FindStringSubmatch(html); len(m) >= 2 {
|
|
detail.Description = stripHTML(m[1])
|
|
}
|
|
|
|
return detail, nil
|
|
}
|
|
|
|
func (a *DiscuzAdapter) GetDownloadURL(ctx context.Context, cfg SiteConfig, id string) (string, error) {
|
|
return cfg.URL + "/forum.php?mod=viewthread&tid=" + id, nil
|
|
}
|
|
|
|
// parseDiscuzHTML 解析 Discuz HTML 响应。
|
|
func parseDiscuzHTML(html, siteName, baseURL string) (*SiteSearchResult, error) {
|
|
result := &SiteSearchResult{
|
|
SiteName: siteName,
|
|
Items: []TorrentItem{},
|
|
Page: 1,
|
|
}
|
|
|
|
// Extract thread links
|
|
threadRegex := regexp.MustCompile(`<a[^>]*href="(?:forum\.php\?mod=viewthread&tid=|thread-(\d+)-1-1)\.html"[^>]*>([^<]+)</a>`)
|
|
matches := threadRegex.FindAllStringSubmatch(html, -1)
|
|
|
|
for _, m := range matches {
|
|
item := TorrentItem{}
|
|
if m[1] != "" {
|
|
item.ID = m[1]
|
|
} else {
|
|
// Extract tid from URL
|
|
tidRegex := regexp.MustCompile(`tid=(\d+)`)
|
|
if tidM := tidRegex.FindStringSubmatch(m[0]); len(tidM) >= 2 {
|
|
item.ID = tidM[1]
|
|
}
|
|
}
|
|
if item.ID == "" {
|
|
continue
|
|
}
|
|
|
|
item.Title = strings.TrimSpace(m[2])
|
|
item.DetailURL = baseURL + "/forum.php?mod=viewthread&tid=" + item.ID
|
|
item.UploadTime = time.Now()
|
|
|
|
result.Items = append(result.Items, item)
|
|
}
|
|
|
|
result.Total = len(result.Items)
|
|
return result, nil
|
|
}
|