mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 19:36:36 +08:00
263 lines
6.9 KiB
Go
263 lines
6.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
)
|
|
|
|
// TestConnection 测试 API 连接。
|
|
func (s *ApiConfigService) TestConnection(ctx context.Context, provider string) (string, error) {
|
|
cfg, err := s.GetByProvider(ctx, provider)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
|
|
// 根据不同提供者执行不同的测试逻辑
|
|
switch provider {
|
|
case "tmdb":
|
|
return s.testTMDb(cfg)
|
|
case "openai":
|
|
return s.testOpenAI(cfg)
|
|
case "deepseek":
|
|
return s.testDeepSeek(cfg)
|
|
case "siliconflow":
|
|
return s.testSiliconFlow(cfg)
|
|
case "adult":
|
|
return s.testAdult(ctx, cfg)
|
|
case "metatube":
|
|
return s.testMetaTube(ctx, cfg)
|
|
default:
|
|
return "unknown", fmt.Errorf("no test implemented for provider: %s", provider)
|
|
}
|
|
}
|
|
|
|
// testTMDb 测试 TMDb API 连接。
|
|
func (s *ApiConfigService) testTMDb(cfg *model.APIConfig) (string, error) {
|
|
if cfg.APIKey == "" {
|
|
return "error", errors.New("API key is required")
|
|
}
|
|
|
|
baseURL := strings.TrimRight(cfg.BaseURL, "/")
|
|
if baseURL == "" {
|
|
baseURL = strings.TrimRight(s.cfg.Secrets.TMDbAPIProxy, "/")
|
|
}
|
|
if baseURL == "" {
|
|
baseURL = "https://api.themoviedb.org/3"
|
|
}
|
|
testURL := baseURL + "/configuration?api_key=" + url.QueryEscape(cfg.APIKey)
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, testURL, nil)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
client := NewExternalHTTPClient(10 * time.Second)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return "error", fmt.Errorf("TMDb connection failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 200 {
|
|
return "success", nil
|
|
}
|
|
if resp.StatusCode == 401 {
|
|
return "invalid", errors.New("invalid API key")
|
|
}
|
|
return "error", fmt.Errorf("TMDb API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// testOpenAI 测试 OpenAI API 连接。
|
|
func (s *ApiConfigService) testOpenAI(cfg *model.APIConfig) (string, error) {
|
|
if cfg.APIKey == "" {
|
|
return "error", errors.New("API key is required")
|
|
}
|
|
|
|
baseURL := cfg.BaseURL
|
|
if baseURL == "" {
|
|
baseURL = "https://api.openai.com/v1"
|
|
}
|
|
|
|
testURL := baseURL + "/models"
|
|
req, err := http.NewRequest("GET", testURL, nil)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+cfg.APIKey)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req.WithContext(context.Background()))
|
|
if err != nil {
|
|
return "error", fmt.Errorf("OpenAI connection failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 200 {
|
|
return "success", nil
|
|
}
|
|
if resp.StatusCode == 401 {
|
|
return "invalid", errors.New("invalid API key")
|
|
}
|
|
return "error", fmt.Errorf("OpenAI API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// testDeepSeek 测试 DeepSeek API 连接。
|
|
func (s *ApiConfigService) testDeepSeek(cfg *model.APIConfig) (string, error) {
|
|
if cfg.APIKey == "" {
|
|
return "error", errors.New("API key is required")
|
|
}
|
|
|
|
baseURL := cfg.BaseURL
|
|
if baseURL == "" {
|
|
baseURL = "https://api.deepseek.com"
|
|
}
|
|
|
|
testURL := baseURL + "/models"
|
|
req, err := http.NewRequest("GET", testURL, nil)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+cfg.APIKey)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req.WithContext(context.Background()))
|
|
if err != nil {
|
|
return "error", fmt.Errorf("DeepSeek connection failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 200 {
|
|
return "success", nil
|
|
}
|
|
if resp.StatusCode == 401 {
|
|
return "invalid", errors.New("invalid API key")
|
|
}
|
|
return "error", fmt.Errorf("DeepSeek API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// testSiliconFlow 测试 SiliconFlow API 连接。
|
|
func (s *ApiConfigService) testSiliconFlow(cfg *model.APIConfig) (string, error) {
|
|
if cfg.APIKey == "" {
|
|
return "error", errors.New("API key is required")
|
|
}
|
|
|
|
baseURL := cfg.BaseURL
|
|
if baseURL == "" {
|
|
baseURL = "https://api.siliconflow.cn/v1"
|
|
}
|
|
|
|
testURL := baseURL + "/models"
|
|
req, err := http.NewRequest("GET", testURL, nil)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+cfg.APIKey)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req.WithContext(context.Background()))
|
|
if err != nil {
|
|
return "error", fmt.Errorf("SiliconFlow connection failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 200 {
|
|
return "success", nil
|
|
}
|
|
if resp.StatusCode == 401 {
|
|
return "invalid", errors.New("invalid API key")
|
|
}
|
|
return "error", fmt.Errorf("SiliconFlow API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// testAdult 测试 Adult (JavDB/JavBus) 刮削数据源连接与年龄验证。
|
|
func (s *ApiConfigService) testAdult(ctx context.Context, cfg *model.APIConfig) (string, error) {
|
|
bases := []string{}
|
|
if cfg.BaseURL != "" {
|
|
bases = append(bases, adultConfiguredBases(cfg.BaseURL)...)
|
|
}
|
|
if cfg.Extra != "" {
|
|
bases = append(bases, adultConfiguredBases(cfg.Extra)...)
|
|
}
|
|
if len(bases) == 0 {
|
|
bases = defaultAdultBases
|
|
}
|
|
cookie := defaultAdultCookies
|
|
if strings.TrimSpace(cfg.APIKey) != "" {
|
|
cookie = strings.TrimSpace(cfg.APIKey)
|
|
if !strings.Contains(cookie, "age=") {
|
|
cookie = cookie + "; age=verified"
|
|
}
|
|
if !strings.Contains(cookie, "existmag=") {
|
|
cookie = cookie + "; existmag=all"
|
|
}
|
|
}
|
|
|
|
client := NewExternalHTTPClient(10 * time.Second)
|
|
var lastErr error
|
|
successCount := 0
|
|
|
|
for _, base := range bases {
|
|
base = strings.TrimRight(base, "/")
|
|
probeURL := base
|
|
if adultSourceKind(base) == "javbus" {
|
|
probeURL = base + "/IPX-235"
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, probeURL, nil)
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
applyAdultHeaders(req, base, cookie)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("%s: %w", base, err)
|
|
continue
|
|
}
|
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 512<<10))
|
|
_ = resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 400 {
|
|
lastErr = fmt.Errorf("%s returned %d", base, resp.StatusCode)
|
|
continue
|
|
}
|
|
text := string(respBody)
|
|
if strings.Contains(text, "driver-verify") || strings.Contains(text, "Age Verification") || strings.Contains(text, "你是否已經成年") {
|
|
lastErr = fmt.Errorf("%s: age verification required / intercepted", base)
|
|
continue
|
|
}
|
|
successCount++
|
|
break
|
|
}
|
|
|
|
if successCount > 0 {
|
|
return "success", nil
|
|
}
|
|
if lastErr != nil {
|
|
return "error", fmt.Errorf("adult sources test failed: %w", lastErr)
|
|
}
|
|
return "error", errors.New("no adult source available")
|
|
}
|
|
|
|
// testMetaTube 测试 MetaTube Server 连接与 Token。
|
|
func (s *ApiConfigService) testMetaTube(ctx context.Context, cfg *model.APIConfig) (string, error) {
|
|
serverURL := strings.TrimRight(strings.TrimSpace(cfg.BaseURL), "/")
|
|
if serverURL == "" {
|
|
serverURL = "http://127.0.0.1:7700"
|
|
}
|
|
provider := NewMetaTubeProvider(s.log)
|
|
res, err := provider.TestConnection(ctx, serverURL, cfg.APIKey)
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
if !res.Success {
|
|
return "error", errors.New(res.Error)
|
|
}
|
|
return "success", nil
|
|
}
|