Files
MeBox/internal/helper/site_connectivity.go
T
2026-06-27 03:14:38 +08:00

97 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package helper
import (
"fmt"
"io"
"net/http"
"github.com/ShukeBta/MediaStationGo/internal/model"
"go.uber.org/zap"
)
// TestSiteConnectivity performs a site connectivity test with browser-like headers.
// If flareSolverrURL is non-empty AND the site has BrowserEmulation turned on,
// it will attempt to use FlareSolverr first.
// Returns (ok, message, error).
func TestSiteConnectivity(site *model.Site, flareSolverrURL string, timeout int, log *zap.Logger) (bool, string, error) {
// Try FlareSolverr first when (a) globally enabled and (b) the site
// asked for browser emulation. This matches the contract used by the
// search path (see service.SiteService.siteModelToConfig).
useFlare := flareSolverrURL != "" && site.BrowserEmulation
if useFlare {
log.Info("Trying FlareSolverr for site test", zap.String("url", site.URL))
body, err := FetchURLWithFlareSolverr(flareSolverrURL, site.URL, site.Cookie, timeout, "", log)
if err == nil {
// Successfully got page via FlareSolverr
if IsCloudflareChallenge(body) {
return false, "站点被 Cloudflare/WAF 拦截,但 FlareSolverr 未能完全解决", nil
}
return true, "连接成功 (via FlareSolverr)", nil
}
log.Warn("FlareSolverr failed, falling back to direct request", zap.Error(err))
// Fall through to direct request
}
// Direct HTTP request with browser-like headers. Honors HTTP(S)_PROXY
// when the site has UseProxy enabled — this makes the "use proxy"
// checkbox in the UI actually do something.
client := NewSiteHTTPClient(timeout, site.UseProxy)
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}
return nil
}
req, err := http.NewRequest("GET", site.URL, nil)
if err != nil {
return false, err.Error(), nil
}
headers := HTTPHeaderPresets()
for k, v := range headers {
req.Header.Set(k, v)
}
ApplySiteAuthHeaders(req, site)
resp, err := client.Do(req)
if err != nil {
return false, err.Error(), nil
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
bodyStr := string(body)
if IsCloudflareChallenge(bodyStr) {
log.Warn("Cloudflare challenge detected", zap.String("url", site.URL))
return false, "站点被 Cloudflare/WAF 拦截,请配置 FlareSolverr 或浏览器模拟", nil
}
// Evaluate status code (mirror the reference Python project's semantics:
// 200 → success, 3xx → success/redirect-to-self, 401/403 → failure with
// hint to check credentials, 4xx/5xx → failure with raw status text).
switch {
case resp.StatusCode >= 200 && resp.StatusCode < 300:
return true, fmt.Sprintf("连接成功 (%s)", resp.Status), nil
case resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307 || resp.StatusCode == 308:
loc := resp.Header.Get("Location")
if loc == "" {
loc = "(unknown)"
}
// Most PT sites redirect logged-out users to login; treat as failure.
return false, fmt.Sprintf("未登录或 Cookie 失效(重定向至 %s)", loc), nil
case resp.StatusCode == 401:
return false, "未授权(HTTP 401),请检查 API Key / Cookie", nil
case resp.StatusCode == 403:
return false, "认证失败(HTTP 403),请检查 Cookie / API Key 或站点是否需要浏览器模拟", nil
case resp.StatusCode == 429:
return false, "请求被限流(HTTP 429),请稍后再试", nil
case resp.StatusCode == 503:
return false, "服务暂时不可用(HTTP 503)", nil
default:
return false, resp.Status, nil
}
}