2025-08-01 10:55:37 +08:00
|
|
|
|
package ouge
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"net/url"
|
|
|
|
|
|
"regexp"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
"context"
|
2025-08-01 14:57:44 +08:00
|
|
|
|
"sync/atomic"
|
2025-08-01 10:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
"pansou/model"
|
|
|
|
|
|
"pansou/plugin"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-01 14:57:44 +08:00
|
|
|
|
const (
|
|
|
|
|
|
// 默认超时时间 - 优化为更短时间
|
|
|
|
|
|
DefaultTimeout = 8 * time.Second
|
|
|
|
|
|
|
|
|
|
|
|
// HTTP连接池配置
|
|
|
|
|
|
MaxIdleConns = 200
|
|
|
|
|
|
MaxIdleConnsPerHost = 50
|
|
|
|
|
|
MaxConnsPerHost = 100
|
|
|
|
|
|
IdleConnTimeout = 90 * time.Second
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 性能统计(原子操作)
|
|
|
|
|
|
var (
|
|
|
|
|
|
searchRequests int64 = 0
|
|
|
|
|
|
totalSearchTime int64 = 0 // 纳秒
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-01 10:55:37 +08:00
|
|
|
|
func init() {
|
|
|
|
|
|
plugin.RegisterGlobalPlugin(NewOugePlugin())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 预编译的正则表达式
|
|
|
|
|
|
var (
|
|
|
|
|
|
// 密码提取正则表达式
|
|
|
|
|
|
passwordRegex = regexp.MustCompile(`\?pwd=([0-9a-zA-Z]+)`)
|
|
|
|
|
|
|
|
|
|
|
|
// 常见网盘链接的正则表达式(支持16种类型)
|
|
|
|
|
|
quarkLinkRegex = regexp.MustCompile(`https?://pan\.quark\.cn/s/[0-9a-zA-Z]+`)
|
|
|
|
|
|
ucLinkRegex = regexp.MustCompile(`https?://drive\.uc\.cn/s/[0-9a-zA-Z]+(\?[^"'\s]*)?`)
|
|
|
|
|
|
baiduLinkRegex = regexp.MustCompile(`https?://pan\.baidu\.com/s/[0-9a-zA-Z_\-]+(\?pwd=[0-9a-zA-Z]+)?`)
|
|
|
|
|
|
aliyunLinkRegex = regexp.MustCompile(`https?://(www\.)?(aliyundrive\.com|alipan\.com)/s/[0-9a-zA-Z]+`)
|
|
|
|
|
|
xunleiLinkRegex = regexp.MustCompile(`https?://pan\.xunlei\.com/s/[0-9a-zA-Z_\-]+(\?pwd=[0-9a-zA-Z]+)?`)
|
|
|
|
|
|
tianyiLinkRegex = regexp.MustCompile(`https?://cloud\.189\.cn/t/[0-9a-zA-Z]+`)
|
|
|
|
|
|
link115Regex = regexp.MustCompile(`https?://115\.com/s/[0-9a-zA-Z]+`)
|
|
|
|
|
|
mobileLinkRegex = regexp.MustCompile(`https?://caiyun\.feixin\.10086\.cn/[0-9a-zA-Z]+`)
|
|
|
|
|
|
weiyunLinkRegex = regexp.MustCompile(`https?://share\.weiyun\.com/[0-9a-zA-Z]+`)
|
|
|
|
|
|
lanzouLinkRegex = regexp.MustCompile(`https?://(www\.)?(lanzou[uixys]*|lan[zs]o[ux])\.(com|net|org)/[0-9a-zA-Z]+`)
|
|
|
|
|
|
jianguoyunLinkRegex = regexp.MustCompile(`https?://(www\.)?jianguoyun\.com/p/[0-9a-zA-Z]+`)
|
|
|
|
|
|
link123Regex = regexp.MustCompile(`https?://123pan\.com/s/[0-9a-zA-Z]+`)
|
|
|
|
|
|
pikpakLinkRegex = regexp.MustCompile(`https?://mypikpak\.com/s/[0-9a-zA-Z]+`)
|
|
|
|
|
|
magnetLinkRegex = regexp.MustCompile(`magnet:\?xt=urn:btih:[0-9a-fA-F]{40}`)
|
|
|
|
|
|
ed2kLinkRegex = regexp.MustCompile(`ed2k://\|file\|.+\|\d+\|[0-9a-fA-F]{32}\|/`)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// OugeAsyncPlugin Ouge异步插件
|
|
|
|
|
|
type OugeAsyncPlugin struct {
|
|
|
|
|
|
*plugin.BaseAsyncPlugin
|
2025-08-01 14:57:44 +08:00
|
|
|
|
optimizedClient *http.Client
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// createOptimizedHTTPClient 创建优化的HTTP客户端
|
|
|
|
|
|
func createOptimizedHTTPClient() *http.Client {
|
|
|
|
|
|
transport := &http.Transport{
|
|
|
|
|
|
MaxIdleConns: MaxIdleConns,
|
|
|
|
|
|
MaxIdleConnsPerHost: MaxIdleConnsPerHost,
|
|
|
|
|
|
MaxConnsPerHost: MaxConnsPerHost,
|
|
|
|
|
|
IdleConnTimeout: IdleConnTimeout,
|
|
|
|
|
|
DisableKeepAlives: false,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &http.Client{
|
|
|
|
|
|
Transport: transport,
|
|
|
|
|
|
Timeout: DefaultTimeout,
|
|
|
|
|
|
}
|
2025-08-01 10:55:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewOugePlugin 创建新的Ouge异步插件
|
|
|
|
|
|
func NewOugePlugin() *OugeAsyncPlugin {
|
|
|
|
|
|
return &OugeAsyncPlugin{
|
|
|
|
|
|
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("ouge", 3),
|
2025-08-01 14:57:44 +08:00
|
|
|
|
optimizedClient: createOptimizedHTTPClient(),
|
2025-08-01 10:55:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Search 同步搜索接口
|
|
|
|
|
|
func (p *OugeAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
|
|
|
|
|
result, err := p.SearchWithResult(keyword, ext)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return result.Results, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SearchWithResult 带结果统计的搜索接口
|
|
|
|
|
|
func (p *OugeAsyncPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
|
|
|
|
|
|
return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// searchImpl 搜索实现
|
|
|
|
|
|
func (p *OugeAsyncPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
2025-08-01 14:57:44 +08:00
|
|
|
|
// 性能统计
|
|
|
|
|
|
start := time.Now()
|
|
|
|
|
|
atomic.AddInt64(&searchRequests, 1)
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
duration := time.Since(start).Nanoseconds()
|
|
|
|
|
|
atomic.AddInt64(&totalSearchTime, duration)
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// 使用优化的客户端
|
|
|
|
|
|
if p.optimizedClient != nil {
|
|
|
|
|
|
client = p.optimizedClient
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 10:55:37 +08:00
|
|
|
|
// 构建API搜索URL - 使用ouge专用域名
|
|
|
|
|
|
searchURL := fmt.Sprintf("https://woog.nxog.eu.org/api.php/provide/vod?ac=detail&wd=%s", url.QueryEscape(keyword))
|
|
|
|
|
|
|
|
|
|
|
|
// 创建HTTP请求
|
2025-08-01 14:57:44 +08:00
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
|
2025-08-01 10:55:37 +08:00
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
|
|
|
|
|
req.Header.Set("Accept", "application/json, text/plain, */*")
|
|
|
|
|
|
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
|
|
|
|
|
req.Header.Set("Connection", "keep-alive")
|
|
|
|
|
|
req.Header.Set("Referer", "https://woog.nxog.eu.org/")
|
|
|
|
|
|
req.Header.Set("Cache-Control", "no-cache")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
resp, err := p.doRequestWithRetry(req, client)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 解析JSON响应
|
|
|
|
|
|
var apiResponse OugeAPIResponse
|
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("[%s] 解析JSON响应失败: %w", p.Name(), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查API响应状态
|
|
|
|
|
|
if apiResponse.Code != 1 {
|
|
|
|
|
|
return nil, fmt.Errorf("[%s] API返回错误: %s", p.Name(), apiResponse.Msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析搜索结果
|
|
|
|
|
|
var results []model.SearchResult
|
|
|
|
|
|
for _, item := range apiResponse.List {
|
|
|
|
|
|
if result := p.parseAPIItem(item); result.Title != "" {
|
|
|
|
|
|
results = append(results, result)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OugeAPIResponse API响应结构
|
|
|
|
|
|
type OugeAPIResponse struct {
|
|
|
|
|
|
Code int `json:"code"`
|
|
|
|
|
|
Msg string `json:"msg"`
|
|
|
|
|
|
Page int `json:"page"`
|
|
|
|
|
|
PageCount int `json:"pagecount"`
|
|
|
|
|
|
Limit int `json:"limit"`
|
|
|
|
|
|
Total int `json:"total"`
|
|
|
|
|
|
List []OugeAPIItem `json:"list"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OugeAPIItem API数据项
|
|
|
|
|
|
type OugeAPIItem struct {
|
|
|
|
|
|
VodID int `json:"vod_id"`
|
|
|
|
|
|
VodName string `json:"vod_name"`
|
|
|
|
|
|
VodActor string `json:"vod_actor"`
|
|
|
|
|
|
VodDirector string `json:"vod_director"`
|
|
|
|
|
|
VodDownFrom string `json:"vod_down_from"`
|
|
|
|
|
|
VodDownURL string `json:"vod_down_url"`
|
|
|
|
|
|
VodRemarks string `json:"vod_remarks"`
|
|
|
|
|
|
VodPubdate string `json:"vod_pubdate"`
|
|
|
|
|
|
VodArea string `json:"vod_area"`
|
|
|
|
|
|
VodYear string `json:"vod_year"`
|
|
|
|
|
|
VodContent string `json:"vod_content"`
|
|
|
|
|
|
VodPic string `json:"vod_pic"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// parseAPIItem 解析API数据项
|
|
|
|
|
|
func (p *OugeAsyncPlugin) parseAPIItem(item OugeAPIItem) model.SearchResult {
|
|
|
|
|
|
// 构建唯一ID
|
|
|
|
|
|
uniqueID := fmt.Sprintf("%s-%d", p.Name(), item.VodID)
|
|
|
|
|
|
|
|
|
|
|
|
// 构建标题
|
|
|
|
|
|
title := strings.TrimSpace(item.VodName)
|
|
|
|
|
|
if title == "" {
|
|
|
|
|
|
return model.SearchResult{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建描述
|
|
|
|
|
|
var contentParts []string
|
|
|
|
|
|
if item.VodActor != "" {
|
|
|
|
|
|
contentParts = append(contentParts, fmt.Sprintf("主演: %s", item.VodActor))
|
|
|
|
|
|
}
|
|
|
|
|
|
if item.VodDirector != "" {
|
|
|
|
|
|
contentParts = append(contentParts, fmt.Sprintf("导演: %s", item.VodDirector))
|
|
|
|
|
|
}
|
|
|
|
|
|
if item.VodArea != "" {
|
|
|
|
|
|
contentParts = append(contentParts, fmt.Sprintf("地区: %s", item.VodArea))
|
|
|
|
|
|
}
|
|
|
|
|
|
if item.VodYear != "" {
|
|
|
|
|
|
contentParts = append(contentParts, fmt.Sprintf("年份: %s", item.VodYear))
|
|
|
|
|
|
}
|
|
|
|
|
|
if item.VodRemarks != "" {
|
|
|
|
|
|
contentParts = append(contentParts, fmt.Sprintf("状态: %s", item.VodRemarks))
|
|
|
|
|
|
}
|
|
|
|
|
|
content := strings.Join(contentParts, " | ")
|
|
|
|
|
|
|
|
|
|
|
|
// 解析下载链接
|
|
|
|
|
|
links := p.parseDownloadLinks(item.VodDownFrom, item.VodDownURL)
|
|
|
|
|
|
|
|
|
|
|
|
// 构建标签
|
|
|
|
|
|
var tags []string
|
|
|
|
|
|
if item.VodYear != "" {
|
|
|
|
|
|
tags = append(tags, item.VodYear)
|
|
|
|
|
|
}
|
|
|
|
|
|
if item.VodArea != "" {
|
|
|
|
|
|
tags = append(tags, item.VodArea)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return model.SearchResult{
|
|
|
|
|
|
UniqueID: uniqueID,
|
|
|
|
|
|
Title: title,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Links: links,
|
|
|
|
|
|
Tags: tags,
|
|
|
|
|
|
Channel: "", // 插件搜索结果Channel为空
|
|
|
|
|
|
Datetime: time.Now(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// parseDownloadLinks 解析下载链接
|
|
|
|
|
|
func (p *OugeAsyncPlugin) parseDownloadLinks(vodDownFrom, vodDownURL string) []model.Link {
|
|
|
|
|
|
if vodDownFrom == "" || vodDownURL == "" {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按$$$分隔
|
|
|
|
|
|
fromParts := strings.Split(vodDownFrom, "$$$")
|
|
|
|
|
|
urlParts := strings.Split(vodDownURL, "$$$")
|
|
|
|
|
|
|
|
|
|
|
|
// 确保数组长度一致
|
|
|
|
|
|
minLen := len(fromParts)
|
|
|
|
|
|
if len(urlParts) < minLen {
|
|
|
|
|
|
minLen = len(urlParts)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var links []model.Link
|
|
|
|
|
|
for i := 0; i < minLen; i++ {
|
|
|
|
|
|
fromType := strings.TrimSpace(fromParts[i])
|
|
|
|
|
|
urlStr := strings.TrimSpace(urlParts[i])
|
|
|
|
|
|
|
|
|
|
|
|
if urlStr == "" || !p.isValidNetworkDriveURL(urlStr) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 映射网盘类型
|
|
|
|
|
|
linkType := p.mapCloudType(fromType, urlStr)
|
|
|
|
|
|
if linkType == "" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取密码
|
|
|
|
|
|
password := p.extractPassword(urlStr)
|
|
|
|
|
|
|
|
|
|
|
|
links = append(links, model.Link{
|
|
|
|
|
|
Type: linkType,
|
|
|
|
|
|
URL: urlStr,
|
|
|
|
|
|
Password: password,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return links
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mapCloudType 映射网盘类型
|
|
|
|
|
|
func (p *OugeAsyncPlugin) mapCloudType(apiType, url string) string {
|
|
|
|
|
|
// 优先根据API标识映射
|
|
|
|
|
|
switch strings.ToUpper(apiType) {
|
|
|
|
|
|
case "BD":
|
|
|
|
|
|
return "baidu"
|
|
|
|
|
|
case "KG":
|
|
|
|
|
|
return "quark"
|
|
|
|
|
|
case "UC":
|
|
|
|
|
|
return "uc"
|
|
|
|
|
|
case "ALY":
|
|
|
|
|
|
return "aliyun"
|
|
|
|
|
|
case "XL":
|
|
|
|
|
|
return "xunlei"
|
|
|
|
|
|
case "TY":
|
|
|
|
|
|
return "tianyi"
|
|
|
|
|
|
case "115":
|
|
|
|
|
|
return "115"
|
|
|
|
|
|
case "MB":
|
|
|
|
|
|
return "mobile"
|
|
|
|
|
|
case "WY":
|
|
|
|
|
|
return "weiyun"
|
|
|
|
|
|
case "LZ":
|
|
|
|
|
|
return "lanzou"
|
|
|
|
|
|
case "JGY":
|
|
|
|
|
|
return "jianguoyun"
|
|
|
|
|
|
case "123":
|
|
|
|
|
|
return "123"
|
|
|
|
|
|
case "PK":
|
|
|
|
|
|
return "pikpak"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果API标识无法识别,则通过URL模式匹配
|
|
|
|
|
|
return p.determineLinkType(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// isValidNetworkDriveURL 检查URL是否为有效的网盘链接
|
|
|
|
|
|
func (p *OugeAsyncPlugin) isValidNetworkDriveURL(url string) bool {
|
|
|
|
|
|
// 过滤掉明显无效的链接
|
|
|
|
|
|
if strings.Contains(url, "javascript:") ||
|
|
|
|
|
|
strings.Contains(url, "#") ||
|
|
|
|
|
|
url == "" ||
|
|
|
|
|
|
(!strings.HasPrefix(url, "http") && !strings.HasPrefix(url, "magnet:") && !strings.HasPrefix(url, "ed2k:")) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否匹配任何支持的网盘格式(16种)
|
|
|
|
|
|
return quarkLinkRegex.MatchString(url) ||
|
|
|
|
|
|
ucLinkRegex.MatchString(url) ||
|
|
|
|
|
|
baiduLinkRegex.MatchString(url) ||
|
|
|
|
|
|
aliyunLinkRegex.MatchString(url) ||
|
|
|
|
|
|
xunleiLinkRegex.MatchString(url) ||
|
|
|
|
|
|
tianyiLinkRegex.MatchString(url) ||
|
|
|
|
|
|
link115Regex.MatchString(url) ||
|
|
|
|
|
|
mobileLinkRegex.MatchString(url) ||
|
|
|
|
|
|
weiyunLinkRegex.MatchString(url) ||
|
|
|
|
|
|
lanzouLinkRegex.MatchString(url) ||
|
|
|
|
|
|
jianguoyunLinkRegex.MatchString(url) ||
|
|
|
|
|
|
link123Regex.MatchString(url) ||
|
|
|
|
|
|
pikpakLinkRegex.MatchString(url) ||
|
|
|
|
|
|
magnetLinkRegex.MatchString(url) ||
|
|
|
|
|
|
ed2kLinkRegex.MatchString(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// determineLinkType 根据URL确定链接类型(支持16种类型)
|
|
|
|
|
|
func (p *OugeAsyncPlugin) determineLinkType(url string) string {
|
|
|
|
|
|
switch {
|
|
|
|
|
|
case quarkLinkRegex.MatchString(url):
|
|
|
|
|
|
return "quark"
|
|
|
|
|
|
case ucLinkRegex.MatchString(url):
|
|
|
|
|
|
return "uc"
|
|
|
|
|
|
case baiduLinkRegex.MatchString(url):
|
|
|
|
|
|
return "baidu"
|
|
|
|
|
|
case aliyunLinkRegex.MatchString(url):
|
|
|
|
|
|
return "aliyun"
|
|
|
|
|
|
case xunleiLinkRegex.MatchString(url):
|
|
|
|
|
|
return "xunlei"
|
|
|
|
|
|
case tianyiLinkRegex.MatchString(url):
|
|
|
|
|
|
return "tianyi"
|
|
|
|
|
|
case link115Regex.MatchString(url):
|
|
|
|
|
|
return "115"
|
|
|
|
|
|
case mobileLinkRegex.MatchString(url):
|
|
|
|
|
|
return "mobile"
|
|
|
|
|
|
case weiyunLinkRegex.MatchString(url):
|
|
|
|
|
|
return "weiyun"
|
|
|
|
|
|
case lanzouLinkRegex.MatchString(url):
|
|
|
|
|
|
return "lanzou"
|
|
|
|
|
|
case jianguoyunLinkRegex.MatchString(url):
|
|
|
|
|
|
return "jianguoyun"
|
|
|
|
|
|
case link123Regex.MatchString(url):
|
|
|
|
|
|
return "123"
|
|
|
|
|
|
case pikpakLinkRegex.MatchString(url):
|
|
|
|
|
|
return "pikpak"
|
|
|
|
|
|
case magnetLinkRegex.MatchString(url):
|
|
|
|
|
|
return "magnet"
|
|
|
|
|
|
case ed2kLinkRegex.MatchString(url):
|
|
|
|
|
|
return "ed2k"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "" // 不支持的类型返回空字符串
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// extractPassword 从URL中提取密码
|
|
|
|
|
|
func (p *OugeAsyncPlugin) extractPassword(url string) string {
|
|
|
|
|
|
matches := passwordRegex.FindStringSubmatch(url)
|
|
|
|
|
|
if len(matches) > 1 {
|
|
|
|
|
|
return matches[1]
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 14:57:44 +08:00
|
|
|
|
// doRequestWithRetry 带重试的HTTP请求(优化JSON API的重试策略)
|
2025-08-01 10:55:37 +08:00
|
|
|
|
func (p *OugeAsyncPlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
|
2025-08-01 14:57:44 +08:00
|
|
|
|
maxRetries := 2 // 对于JSON API减少重试次数
|
2025-08-01 10:55:37 +08:00
|
|
|
|
var lastErr error
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < maxRetries; i++ {
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
|
|
|
|
return resp, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
lastErr = fmt.Errorf("HTTP状态码: %d", resp.StatusCode)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
lastErr = err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 14:57:44 +08:00
|
|
|
|
// JSON API快速重试:只等待很短时间
|
2025-08-01 10:55:37 +08:00
|
|
|
|
if i < maxRetries-1 {
|
2025-08-01 14:57:44 +08:00
|
|
|
|
time.Sleep(100 * time.Millisecond) // 从秒级改为100毫秒
|
2025-08-01 10:55:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("[%s] 请求失败,重试%d次后仍失败: %w", p.Name(), maxRetries, lastErr)
|
2025-08-01 14:57:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPerformanceStats 获取性能统计信息
|
|
|
|
|
|
func (p *OugeAsyncPlugin) GetPerformanceStats() map[string]interface{} {
|
|
|
|
|
|
totalRequests := atomic.LoadInt64(&searchRequests)
|
|
|
|
|
|
totalTime := atomic.LoadInt64(&totalSearchTime)
|
|
|
|
|
|
|
|
|
|
|
|
var avgTime float64
|
|
|
|
|
|
if totalRequests > 0 {
|
|
|
|
|
|
avgTime = float64(totalTime) / float64(totalRequests) / 1e6 // 转换为毫秒
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return map[string]interface{}{
|
|
|
|
|
|
"search_requests": totalRequests,
|
|
|
|
|
|
"avg_search_time_ms": avgTime,
|
|
|
|
|
|
"total_search_time_ns": totalTime,
|
|
|
|
|
|
}
|
2025-08-01 10:55:37 +08:00
|
|
|
|
}
|