新增插件libvio

This commit is contained in:
www.xueximeng.com
2025-08-22 19:15:55 +08:00
parent fae4cc3c77
commit 34a2601a29
5 changed files with 872 additions and 35 deletions

59
main.go
View File

@@ -24,35 +24,36 @@ import (
"pansou/util/cache"
// 以下是插件的空导入用于触发各插件的init函数实现自动注册
// 添加新插件时,只需在此处添加对应的导入语句即可
_ "pansou/plugin/hdr4k"
_ "pansou/plugin/hunhepan"
_ "pansou/plugin/jikepan"
_ "pansou/plugin/panwiki"
_ "pansou/plugin/pansearch"
_ "pansou/plugin/panta"
_ "pansou/plugin/qupansou"
_ "pansou/plugin/susu"
_ "pansou/plugin/thepiratebay"
_ "pansou/plugin/wanou"
_ "pansou/plugin/xuexizhinan"
_ "pansou/plugin/panyq"
_ "pansou/plugin/zhizhen"
_ "pansou/plugin/labi"
_ "pansou/plugin/muou"
_ "pansou/plugin/ouge"
_ "pansou/plugin/shandian"
_ "pansou/plugin/duoduo"
_ "pansou/plugin/huban"
_ "pansou/plugin/cyg"
_ "pansou/plugin/erxiao"
_ "pansou/plugin/miaoso"
_ "pansou/plugin/fox4k"
_ "pansou/plugin/pianku"
_ "pansou/plugin/clmao"
_ "pansou/plugin/wuji"
_ "pansou/plugin/cldi"
_ "pansou/plugin/xiaozhang"
// // 添加新插件时,只需在此处添加对应的导入语句即可
// _ "pansou/plugin/hdr4k"
// _ "pansou/plugin/hunhepan"
// _ "pansou/plugin/jikepan"
// _ "pansou/plugin/panwiki"
// _ "pansou/plugin/pansearch"
// _ "pansou/plugin/panta"
// _ "pansou/plugin/qupansou"
// _ "pansou/plugin/susu"
// _ "pansou/plugin/thepiratebay"
// _ "pansou/plugin/wanou"
// _ "pansou/plugin/xuexizhinan"
// _ "pansou/plugin/panyq"
// _ "pansou/plugin/zhizhen"
// _ "pansou/plugin/labi"
// _ "pansou/plugin/muou"
// _ "pansou/plugin/ouge"
// _ "pansou/plugin/shandian"
// _ "pansou/plugin/duoduo"
// _ "pansou/plugin/huban"
// _ "pansou/plugin/cyg"
// _ "pansou/plugin/erxiao"
// _ "pansou/plugin/miaoso"
// _ "pansou/plugin/fox4k"
// _ "pansou/plugin/pianku"
// _ "pansou/plugin/clmao"
// _ "pansou/plugin/wuji"
// _ "pansou/plugin/cldi"
// _ "pansou/plugin/xiaozhang"
_ "pansou/plugin/libvio"
)
// 全局缓存写入管理器

View File

@@ -0,0 +1,190 @@
# libvio插件HTML结构分析
## 网站信息
- 网站名称LIBVIO
- 主域名https://www.libvio.mov
- 网站类型:影视资源在线播放/下载网站
- 特点提供网盘下载链接夸克、UC等
## 访问流程说明
该网站需要通过三步来获取网盘链接:
1. **搜索页面** → 获取详情页链接
2. **详情页面** → 获取网盘下载页链接(注意选择"下载"而非"播放"
3. **播放页面** → 从JavaScript对象中提取网盘URL
## 1. 搜索页面结构
### 搜索URL格式
```
https://www.libvio.mov/search/-------------.html?wd={关键词}&submit=
```
### 搜索结果容器
- 主容器:`<ul class="stui-vodlist clearfix">`
- 单个结果项:`<li class="col-md-6 col-sm-4 col-xs-3">`
### 搜索结果项结构
```html
<li class="col-md-6 col-sm-4 col-xs-3">
<div class="stui-vodlist__box">
<a class="stui-vodlist__thumb lazyload" href="/detail/4095.html" title="瑞克和莫蒂 第五季"
data-original="https://xxx.jpg">
<span class="play hidden-xs"></span>
<span class="pic-text text-right">10集全</span>
<span class="pic-tag pic-tag-top">9.6</span>
</a>
<div class="stui-vodlist__detail">
<h4 class="title text-overflow">
<a href="/detail/4095.html" title="瑞克和莫蒂 第五季">瑞克和莫蒂 第五季</a>
</h4>
</div>
</div>
</li>
```
### 字段提取要点
- **标题**`.stui-vodlist__detail h4 a` 的文本内容或 `title` 属性
- **详情页链接**`.stui-vodlist__thumb``.stui-vodlist__detail h4 a``href` 属性
- **封面图片**`.stui-vodlist__thumb``data-original` 属性
- **集数信息**`.pic-text` 的文本内容
- **评分**`.pic-tag` 的文本内容
## 2. 详情页面结构
### 详情页URL格式
```
https://www.libvio.mov/detail/{id}.html
```
### 下载链接容器
详情页包含多个播放/下载源,我们需要查找带有"下载"关键字的源:
```html
<div class="stui-vodlist__head">
<div class="stui-pannel__head clearfix">
<span class="more text-muted pull-right"></span>
<h3 class="iconfont icon-iconfontplay2">视频下载(UC) </h3>
</div>
<ul class="stui-content__playlist clearfix">
<li>
<a href="/play/714892571-2-1.html">合集</a>
</li>
</ul>
</div>
<div class="stui-vodlist__head">
<div class="stui-pannel__head clearfix">
<span class="more text-muted pull-right"></span>
<h3 class="iconfont icon-iconfontplay2">视频下载 (夸克) </h3>
</div>
<ul class="stui-content__playlist clearfix">
<li>
<a href="/play/714892571-1-1.html">合集</a>
</li>
</ul>
</div>
```
### 字段提取要点
- **下载源标题**`h3` 标签内容,需要包含"下载"关键字
- **播放页链接**`.stui-content__playlist li a``href` 属性
- **网盘类型识别**:从标题中提取(如"UC"、"夸克"等)
### 筛选规则
- 只提取标题包含"下载"的播放源
- 忽略标题只有"播放"的源(这些通常是在线播放链接)
## 3. 播放页面结构(获取网盘链接)
### 播放页URL格式
```
https://www.libvio.mov/play/{id}-{sid}-{nid}.html
```
- `id`影片ID
- `sid`播放源ID
- `nid`集数ID
### 网盘链接提取方式
网盘链接存储在页面内的JavaScript对象中
```javascript
<script type="text/javascript">
var player_aaaa = {
"flag": "play",
"encrypt": 3,
"trysee": 10,
"points": 0,
"link": "/play/714892571-1-1.html",
"link_next": "",
"link_pre": "",
"url": "https://drive.uc.cn/s/132a6339c94d4?public=1",
"url_next": "",
"from": "uc",
"server": "no",
"note": "",
"id": "714892571",
"sid": 2,
"nid": 1
}
</script>
```
### 字段提取要点
- **网盘链接**`player_aaaa.url` 字段
- **网盘类型**`player_aaaa.from` 字段(如 "uc"、"quark" 等)
- **集数信息**`player_aaaa.nid` 字段
### 提取方法
1. 使用正则表达式匹配 `var player_aaaa = {...}` 内容
2. 解析JSON对象
3. 提取 `url` 字段即为网盘链接
## 4. 支持的网盘类型
根据HTML结构分析网站主要支持
- UC网盘drive.uc.cn
- 夸克网盘pan.quark.cn
## 5. 特殊处理事项
### JavaScript对象解析
- 需要处理转义字符(如 `\/``/`
- 注意JSON对象的格式可能不标准需要容错处理
### 多集处理
- 电视剧/动漫可能有多集,每集有独立的播放页链接
- 需要处理"合集"类型的资源(通常包含整季资源)
### 错误处理
- 需要处理播放页没有player_aaaa对象的情况
- 需要处理URL字段为空的情况
- 需要处理网站改版导致的结构变化
## 6. 实现建议
### 搜索流程
1. 发送搜索请求解析HTML获取搜索结果
2. 提取每个结果的详情页链接
### 详情页处理
1. 访问详情页,查找所有播放源
2. 筛选出标题包含"下载"的源
3. 提取对应的播放页链接
### 网盘链接提取
1. 访问播放页
2. 使用正则表达式提取player_aaaa对象
3. 解析JSON获取网盘URL
4. 根据from字段确定网盘类型
### 并发优化
- 可以对多个详情页访问进行并发
- 可以对多个播放页访问进行并发
- 使用缓存避免重复请求
### 链接类型映射
```
from字段 → 网盘类型
"uc" → "UC网盘"
"quark" → "夸克网盘"
```

649
plugin/libvio/libvio.go Normal file
View File

@@ -0,0 +1,649 @@
package libvio
import (
"compress/gzip"
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
"pansou/util/json"
)
const (
BaseURL = "https://www.libvio.mov"
SearchPath = "/search/-------------.html"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
MaxConcurrency = 20 // 详情页最大并发数
MaxPages = 1 // 最大搜索页数(暂时只搜索第一页)
)
// LibvioPlugin LIBVIO插件
type LibvioPlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
detailCache sync.Map // 缓存详情页结果
playCache sync.Map // 缓存播放页结果
cacheTTL time.Duration
}
// NewLibvioPlugin 创建新的LIBVIO插件实例
func NewLibvioPlugin() *LibvioPlugin {
// 检查调试模式
debugMode := false // 开启调试模式
p := &LibvioPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("libvio", 4, true ), // 优先级4
debugMode: debugMode,
cacheTTL: 30 * time.Minute,
}
return p
}
// Name 返回插件名称
func (p *LibvioPlugin) Name() string {
return "libvio"
}
// DisplayName 返回插件显示名称
func (p *LibvioPlugin) DisplayName() string {
return "LIBVIO"
}
// Description 返回插件描述
func (p *LibvioPlugin) Description() string {
return "LIBVIO - 影视资源网盘下载"
}
// Search 执行搜索并返回结果(兼容性方法)
func (p *LibvioPlugin) 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 执行搜索并返回包含IsFinal标记的结果
func (p *LibvioPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}
// setRequestHeaders 设置请求头
func (p *LibvioPlugin) setRequestHeaders(req *http.Request, referer string) {
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Accept-Encoding", "gzip, deflate")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
if referer != "" {
req.Header.Set("Referer", referer)
}
}
// doRequest 发送HTTP请求
func (p *LibvioPlugin) doRequest(client *http.Client, url string, referer string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
p.setRequestHeaders(req, referer)
if p.debugMode {
log.Printf("[Libvio] 发送请求: %s", url)
}
resp, err := client.Do(req)
if err != nil {
if p.debugMode {
log.Printf("[Libvio] 请求失败: %v", err)
}
return nil, err
}
if p.debugMode {
log.Printf("[Libvio] 响应状态: %d", resp.StatusCode)
}
return resp, nil
}
// searchImpl 实际的搜索实现
func (p *LibvioPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
searchURL := fmt.Sprintf("%s%s?wd=%s&submit=", BaseURL, SearchPath, url.QueryEscape(keyword))
if p.debugMode {
log.Printf("[Libvio] 开始搜索: %s", keyword)
log.Printf("[Libvio] 搜索URL: %s", searchURL)
}
// 发送搜索请求
resp, err := p.doRequest(client, searchURL, BaseURL)
if err != nil {
return nil, fmt.Errorf("发送搜索请求失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("搜索响应状态码异常: %d", resp.StatusCode)
}
// 处理响应体可能是gzip压缩的
reader, err := p.getResponseReader(resp)
if err != nil {
return nil, err
}
// 解析HTML
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// 提取搜索结果
results := p.extractSearchResults(doc, keyword)
if p.debugMode {
log.Printf("[Libvio] 找到 %d 个搜索结果", len(results))
}
// 并发获取详情页的下载链接
results = p.enrichWithDetailLinks(client, results, keyword)
if p.debugMode {
// 统计链接数量
totalLinks := 0
for i, r := range results {
log.Printf("[Libvio] 结果 %d: %s, 链接数: %d", i+1, r.Title, len(r.Links))
totalLinks += len(r.Links)
}
log.Printf("[Libvio] 总计: %d 个结果,%d 个链接", len(results), totalLinks)
}
// 过滤结果
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
if p.debugMode {
log.Printf("[Libvio] 过滤后剩余 %d 个结果", len(filteredResults))
}
return filteredResults, nil
}
// getResponseReader 获取响应读取器处理gzip压缩
func (p *LibvioPlugin) getResponseReader(resp *http.Response) (io.Reader, error) {
var reader io.Reader = resp.Body
// 检查Content-Encoding
contentEncoding := resp.Header.Get("Content-Encoding")
if p.debugMode {
log.Printf("[Libvio] Content-Encoding: %s", contentEncoding)
}
// 如果是gzip压缩手动解压
if contentEncoding == "gzip" {
gzReader, err := gzip.NewReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("创建gzip reader失败: %w", err)
}
// 注意不要在这里关闭gzReader它需要在外部使用
reader = gzReader
}
return reader, nil
}
// extractSearchResults 从HTML中提取搜索结果
func (p *LibvioPlugin) extractSearchResults(doc *goquery.Document, keyword string) []model.SearchResult {
var results []model.SearchResult
// 选择所有搜索结果项
doc.Find("ul.stui-vodlist li").Each(func(i int, s *goquery.Selection) {
// 提取标题和详情页链接
titleElem := s.Find(".stui-vodlist__detail h4 a")
title := strings.TrimSpace(titleElem.Text())
if title == "" {
title, _ = titleElem.Attr("title")
}
detailPath, _ := titleElem.Attr("href")
if detailPath == "" {
// 尝试从缩略图链接获取
thumbLink := s.Find("a.stui-vodlist__thumb")
detailPath, _ = thumbLink.Attr("href")
}
if title == "" || detailPath == "" {
return
}
// 构建完整的详情页URL
detailURL := BaseURL + detailPath
// 提取其他信息
episodeInfo := strings.TrimSpace(s.Find(".pic-text").Text())
rating := strings.TrimSpace(s.Find(".pic-tag").Text())
// 从详情页路径提取ID/detail/4095.html -> 4095
idMatch := regexp.MustCompile(`/detail/(\d+)\.html`).FindStringSubmatch(detailPath)
resourceID := ""
if len(idMatch) > 1 {
resourceID = idMatch[1]
} else {
resourceID = fmt.Sprintf("%d", time.Now().UnixNano())
}
if p.debugMode {
log.Printf("[Libvio] 提取结果 %d: %s, URL: %s", i+1, title, detailURL)
}
// 构建内容描述
content := ""
if episodeInfo != "" {
content = episodeInfo
}
if rating != "" {
if content != "" {
content += " | "
}
content += "评分: " + rating
}
result := model.SearchResult{
Title: title,
Content: content,
Channel: "",
MessageID: fmt.Sprintf("%s-%s", p.Name(), resourceID),
UniqueID: fmt.Sprintf("%s-%s", p.Name(), resourceID),
Datetime: time.Now(),
Links: []model.Link{}, // 稍后填充
}
// 将详情页URL存储在Tags中供后续使用
result.Tags = []string{detailURL}
results = append(results, result)
})
return results
}
// enrichWithDetailLinks 并发获取详情页的下载链接
func (p *LibvioPlugin) enrichWithDetailLinks(client *http.Client, results []model.SearchResult, keyword string) []model.SearchResult {
if len(results) == 0 {
return results
}
if p.debugMode {
log.Printf("[Libvio] 开始获取 %d 个详情页的下载链接", len(results))
}
var wg sync.WaitGroup
var mu sync.Mutex
semaphore := make(chan struct{}, MaxConcurrency)
for i := range results {
wg.Add(1)
go func(idx int) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
// 添加小延迟避免请求过快
time.Sleep(time.Duration(idx*50) * time.Millisecond)
// 从Tags中获取详情页URL
if len(results[idx].Tags) > 0 {
detailURL := results[idx].Tags[0]
links := p.fetchDetailPageLinks(client, detailURL, keyword)
mu.Lock()
results[idx].Links = links
// 清空Tags避免返回给用户
results[idx].Tags = nil
mu.Unlock()
if p.debugMode {
log.Printf("[Libvio] 详情页 %d/%d 获取到 %d 个链接", idx+1, len(results), len(links))
}
}
}(i)
}
wg.Wait()
return results
}
// fetchDetailPageLinks 获取详情页的下载链接
func (p *LibvioPlugin) fetchDetailPageLinks(client *http.Client, detailURL string, keyword string) []model.Link {
if p.debugMode {
log.Printf("[Libvio] 开始获取详情页: %s", detailURL)
}
// 检查缓存
if cached, ok := p.detailCache.Load(detailURL); ok {
if links, ok := cached.([]model.Link); ok {
if p.debugMode {
log.Printf("[Libvio] 使用缓存的详情页结果: %s, 链接数: %d", detailURL, len(links))
}
return links
}
}
// 访问详情页
resp, err := p.doRequest(client, detailURL, BaseURL)
if err != nil {
if p.debugMode {
log.Printf("[Libvio] 获取详情页失败: %s, 错误: %v", detailURL, err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if p.debugMode {
log.Printf("[Libvio] 详情页响应状态码异常: %s, 状态码: %d", detailURL, resp.StatusCode)
}
return nil
}
// 处理响应体
reader, err := p.getResponseReader(resp)
if err != nil {
return nil
}
// 解析HTML
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
if p.debugMode {
log.Printf("[Libvio] 解析详情页HTML失败: %v", err)
}
return nil
}
// 提取下载播放页链接(只提取包含"下载"的)
playLinks := p.extractDownloadPlayLinks(doc)
if p.debugMode {
log.Printf("[Libvio] 找到 %d 个下载播放页链接", len(playLinks))
}
if len(playLinks) == 0 {
if p.debugMode {
log.Printf("[Libvio] 未找到下载链接")
}
return nil
}
// 获取网盘链接
var links []model.Link
for _, playLink := range playLinks {
if p.debugMode {
log.Printf("[Libvio] 获取网盘链接: %s", playLink.URL)
}
panLink := p.fetchPanLink(client, playLink.URL, detailURL)
if panLink != nil {
links = append(links, *panLink)
} else if p.debugMode {
log.Printf("[Libvio] 未能获取网盘链接: %s", playLink.URL)
}
}
if p.debugMode {
log.Printf("[Libvio] 详情页 %s 最终获取到 %d 个网盘链接", detailURL, len(links))
}
// 缓存结果
p.detailCache.Store(detailURL, links)
// 设置缓存过期
go func() {
time.Sleep(p.cacheTTL)
p.detailCache.Delete(detailURL)
}()
return links
}
// PlayLinkInfo 播放链接信息
type PlayLinkInfo struct {
URL string
PanType string // 网盘类型(从标题提取)
}
// extractDownloadPlayLinks 提取下载播放页链接
func (p *LibvioPlugin) extractDownloadPlayLinks(doc *goquery.Document) []PlayLinkInfo {
var playLinks []PlayLinkInfo
// 查找所有播放源
allHeads := doc.Find(".stui-vodlist__head")
if p.debugMode {
log.Printf("[Libvio] 找到 %d 个播放源头部", allHeads.Length())
}
allHeads.Each(func(i int, s *goquery.Selection) {
// 获取标题
title := strings.TrimSpace(s.Find("h3").Text())
if p.debugMode {
log.Printf("[Libvio] 播放源 %d 标题: %s", i+1, title)
}
// 只处理包含"下载"的源
if !strings.Contains(title, "下载") {
if p.debugMode {
log.Printf("[Libvio] 跳过非下载源: %s", title)
}
return
}
// 提取网盘类型
panType := ""
if strings.Contains(title, "夸克") || strings.Contains(title, "quark") {
panType = "quark"
} else if strings.Contains(title, "UC") || strings.Contains(title, "uc") {
panType = "uc"
} else if strings.Contains(title, "百度") || strings.Contains(title, "baidu") {
panType = "baidu"
}
// 提取播放页链接
playlistLinks := s.Find(".stui-content__playlist li a")
if p.debugMode {
log.Printf("[Libvio] 播放列表中有 %d 个链接", playlistLinks.Length())
}
// 通常只取第一个链接(合集)
firstLink := playlistLinks.First()
if firstLink.Length() > 0 {
href, exists := firstLink.Attr("href")
if exists && href != "" {
// 构建完整URL
playURL := BaseURL + href
playLinks = append(playLinks, PlayLinkInfo{
URL: playURL,
PanType: panType,
})
if p.debugMode {
linkText := strings.TrimSpace(firstLink.Text())
log.Printf("[Libvio] 找到下载链接: %s (%s) [%s]", playURL, panType, linkText)
}
}
}
})
return playLinks
}
// fetchPanLink 获取网盘链接
func (p *LibvioPlugin) fetchPanLink(client *http.Client, playURL string, referer string) *model.Link {
// 检查缓存
if cached, ok := p.playCache.Load(playURL); ok {
if link, ok := cached.(*model.Link); ok {
if p.debugMode {
log.Printf("[Libvio] 使用缓存的播放页结果: %s", playURL)
}
return link
}
}
// 访问播放页
resp, err := p.doRequest(client, playURL, referer)
if err != nil {
if p.debugMode {
log.Printf("[Libvio] 获取播放页失败: %v", err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if p.debugMode {
log.Printf("[Libvio] 播放页响应状态码异常: %d", resp.StatusCode)
}
return nil
}
// 处理响应体可能是gzip压缩的
reader, err := p.getResponseReader(resp)
if err != nil {
return nil
}
// 读取响应体
body, err := io.ReadAll(reader)
if err != nil {
return nil
}
// 提取player_aaaa对象
playerDataRegex := regexp.MustCompile(`var\s+player_aaaa\s*=\s*({[^}]+})`)
matches := playerDataRegex.FindStringSubmatch(string(body))
if len(matches) < 2 {
if p.debugMode {
log.Printf("[Libvio] 未找到player_aaaa对象")
// 输出部分body内容用于调试
bodyStr := string(body)
if len(bodyStr) > 500 {
log.Printf("[Libvio] 页面内容前500字符: %s", bodyStr[:500])
} else {
log.Printf("[Libvio] 页面内容: %s", bodyStr)
}
}
return nil
}
// 解析JSON
playerJSON := matches[1]
if p.debugMode {
log.Printf("[Libvio] 找到player_aaaa: %s", playerJSON)
}
// 处理转义字符
playerJSON = strings.ReplaceAll(playerJSON, `\/`, `/`)
var playerData map[string]interface{}
if err := json.Unmarshal([]byte(playerJSON), &playerData); err != nil {
if p.debugMode {
log.Printf("[Libvio] 解析player_aaaa失败: %v, JSON: %s", err, playerJSON)
}
return nil
}
// 提取URL
panURL, ok := playerData["url"].(string)
if !ok || panURL == "" {
if p.debugMode {
log.Printf("[Libvio] player_aaaa中没有url字段")
}
return nil
}
// 提取网盘类型
from, _ := playerData["from"].(string)
linkType := p.mapPanType(from, panURL)
link := &model.Link{
URL: panURL,
Type: linkType,
}
if p.debugMode {
log.Printf("[Libvio] 提取到网盘链接: %s (from=%s, type=%s)", panURL, from, linkType)
}
// 缓存结果
p.playCache.Store(playURL, link)
// 设置缓存过期
go func() {
time.Sleep(p.cacheTTL)
p.playCache.Delete(playURL)
}()
return link
}
// mapPanType 映射网盘类型
func (p *LibvioPlugin) mapPanType(from string, url string) string {
// 首先根据from字段判断
switch strings.ToLower(from) {
case "uc":
return "uc"
case "quark":
return "quark"
case "baidu":
return "baidu"
case "aliyun", "alipan":
return "aliyun"
case "xunlei", "thunder":
return "xunlei"
case "115":
return "115"
case "123", "123pan":
return "123"
}
// 如果from字段不明确根据URL判断
url = strings.ToLower(url)
if strings.Contains(url, "drive.uc.cn") {
return "uc"
} else if strings.Contains(url, "pan.quark.cn") {
return "quark"
} else if strings.Contains(url, "pan.baidu.com") {
return "baidu"
} else if strings.Contains(url, "alipan.com") || strings.Contains(url, "aliyundrive.com") {
return "aliyun"
} else if strings.Contains(url, "pan.xunlei.com") {
return "xunlei"
} else if strings.Contains(url, "115.com") {
return "115"
} else if strings.Contains(url, "123pan.com") || strings.Contains(url, "123684.com") {
return "123"
} else if strings.Contains(url, "cloud.189.cn") {
return "tianyi"
}
// 默认返回others
return "others"
}
func init() {
plugin.RegisterGlobalPlugin(NewLibvioPlugin())
}

View File

@@ -204,7 +204,7 @@ func (p *PanyqPlugin) doSearch(client *http.Client, keyword string, ext map[stri
// 尝试获取或发现 Action ID
actionIDs, err := p.getOrDiscoverActionIDs()
if err != nil {
fmt.Println("panyq: failed to get Action IDs:", err)
// fmt.Println("panyq: failed to get Action IDs:", err)
return nil, fmt.Errorf("获取Action ID失败: %w", err)
}
@@ -261,7 +261,7 @@ func (p *PanyqPlugin) doSearch(client *http.Client, keyword string, ext map[stri
pageHits, _, err := p.getSearchResults(credentials.Sign, pageNum, client)
if err != nil {
fmt.Printf("panyq: failed to get page %d: %v\n", pageNum, err)
// fmt.Printf("panyq: failed to get page %d: %v\n", pageNum, err)
return
}
@@ -428,7 +428,7 @@ func (p *PanyqPlugin) discoverActionIDs() (map[string]string, error) {
}
if DebugLog {
fmt.Printf("panyq: 找到 %d 个潜在的 Action ID\n", len(potentialIDs))
// fmt.Printf("panyq: 找到 %d 个潜在的 Action ID\n", len(potentialIDs))
if len(potentialIDs) > 0 {
fmt.Printf("panyq: 样例ID: %s\n", potentialIDs[0])
}

View File

@@ -415,9 +415,6 @@ func (g *GlobalBufferManager) performCleanup() {
// 计算内存使用量
g.updateMemoryUsage()
if len(toDelete) > 0 {
fmt.Printf("🧹 [全局缓冲区] 清理完成,删除%d个过期缓冲区\n", len(toDelete))
}
}
// updateMemoryUsage 更新内存使用量估算