新增插件clxiong

This commit is contained in:
www.xueximeng.com
2025-08-25 18:56:33 +08:00
parent bd46a556ee
commit dfa9718f53
23 changed files with 5681 additions and 11 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 fish2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -384,6 +384,10 @@ GET /api/search?kw=速度与激情&channels=tgsearchers3,xxx&conc=2&refresh=true
}
```
## 📄 许可证
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。
## ⭐ Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=fish2018/pansou&type=Date)](https://star-history.com/#fish2018/pansou&Date)

View File

@@ -59,6 +59,10 @@ import (
_ "pansou/plugin/xys"
_ "pansou/plugin/ddys"
_ "pansou/plugin/hdmoli"
_ "pansou/plugin/javdb"
_ "pansou/plugin/yuhuage"
_ "pansou/plugin/u3c3"
_ "pansou/plugin/clxiong"
)
// 全局缓存写入管理器

301
plugin/clxiong copy 2/1.txt Normal file
View File

@@ -0,0 +1,301 @@
1.获取 location
post https://www.cilixiong.org/e/search/index.php
content-type application/x-www-form-urlencoded
referer https://www.cilixiong.org/
classid=1%2C2&show=title&tempid=1&keyboard=%E7%91%9E%E5%85%8B%E5%92%8C%E8%8E%AB%E8%92%82
返回值:
从返回的headers取location值
location result/?searchid=7549
2.搜索
get https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂 搜索结果 - 磁力熊</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="">
</form>
</div>
</div>
</header>
<div class="container">
<div class="text-white py-3">找到 8 条符合搜索条件 "<strong class="text-success">瑞克和莫蒂</strong>" 的结果</div>
<div class="row row-cols-2 row-cols-lg-4 align-items-stretch g-4 py-2">
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2024/12154.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3781.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11111.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第七季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.8</span></li>
<li class="d-flex align-items-center small">2023</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3780.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11109.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第六季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.3</span></li>
<li class="d-flex align-items-center small">2022</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1829.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2022/12085.jpg');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第五季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.5</span></li>
<li class="d-flex align-items-center small">2021</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1740.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11108.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第四季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2019</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1739.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11107.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第三季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2017</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1738.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11106.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第二季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2015</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1737.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11105.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第一季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2013</li>
</ul>
</div></a>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-1 py-3 px-5">
<ul class="pagination justify-content-center">
</ul>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
</body>
</html>
3.详情页
get https://www.cilixiong.org/drama/4466.html
referer https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂(2025) 美国电视剧1080P下载在线观看 - 磁力熊</title>
<meta name="keywords" content="瑞克和莫蒂 / Rick and Morty Season 8, 2025年美国|喜剧|冒险|科幻|动画|电视剧">
<meta name="description" content="剧集瑞克和莫蒂1080P磁力下载,瑞克和莫蒂magnet链接,瑞克和莫蒂在线播放" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:url" content="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:site_name" content="磁力熊"/>
<meta property="og:title" content="瑞克和莫蒂 - 磁力熊" />
<meta property="og:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="og:type" content="video.movie"/>
<meta property="og:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<meta property="og:locale" content="zh_CN" />
<meta property="twitter:title" content="瑞克和莫蒂 - 磁力熊"/>
<meta property="twitter:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="twitter:card" content="summary_large_image"/>
<meta property="twitter:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
<script type="application/ld+json">
{
"@context": "http://schema.org",
"name": "瑞克和莫蒂",
"url": "/drama/4466.html",
"image": "https://i.nacloud.cc/2024/12154.webp",
"datePublished": "2025-05-25(美国)",
"description": "瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已...",
"@type": "TVSeries"
}
</script>
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li class="selected"><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="" autocomplete="off">
</form>
</div>
</div>
</header>
<div class="container">
<div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 p-5 text-white">
<div class="p-3">
<img class="rounded-2" style="max-width:100%;" alt="瑞克和莫蒂" src="https://i.nacloud.cc/2024/12154.webp" />
</div>
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>瑞克和莫蒂</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名Rick and Morty Season 8</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:伊恩·卡多尼 / 哈利·贝尔登 / 克里斯·帕内尔 / 斯宾瑟·格拉默 / 萨拉·乔克</p>
<p class="mb-2">最后更新于2025-08-16</p>
<p class="mb-2"></p>
</div>
<div class="mv_card p-4">
<h2 class="pb-2">瑞克和莫蒂剧情简介:</h2>
<div class="mv_card_box">瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已经尝试过了!</div>
</div>
</div>
<div class="row col-md-12 embed_video">
<iframe width="100%" height="100%" src="/e/extend/jx.php?id=4466" frameborder="0" border="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen="allowfullscreen" mozallowfullscreen="mozallowfullscreen" msallowfullscreen="msallowfullscreen" oallowfullscreen="oallowfullscreen" webkitallowfullscreen="webkitallowfullscreen" sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts"></iframe>
</div>
<div class="row col-md-12 py-3 pb-0">
<p class="text-muted text-center small">剧集仅提供第一集在线播放预览。</p>
</div>
<div class="row col-md-12 text-white p-3 pt-1">
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">瑞克和莫蒂磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A">Rick.and.Morty.S08.Complete.1080p.10bit.WEBRip.6CH.x265.HEVC-PSA[2G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A" target="_blank">详情</a></div>
</div><div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF">Rick.and.Morty.S08.1080p.AMZN.WEB-DL.H.264-EniaHD[8.4G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF" target="_blank">详情</a></div>
</div>
<div class="container">
<div style="font-size:13px;" class="pt-1 pb-3 text-muted">为保证质量优先选择原声版本,下载字幕请前往 <a href="https://subhd.tv/search/瑞克和莫蒂" target="_blank" rel="nofollow">Subhd字幕</a>、<a href="https://zimuku.org/search?q=瑞克和莫蒂" target="_blank" rel="nofollow">Zimuku字幕库</a></div>
</div>
</div>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
<script src="/e/public/onclick/?enews=donews&classid=2&id=4466"></script></body>
</html>

View File

@@ -0,0 +1,506 @@
package clxiong
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://www.cilixiong.org"
SearchURL = "https://www.cilixiong.org/e/search/index.php"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxRetries = 3
RetryDelay = 2 * time.Second
MaxResults = 30
)
// ClxiongPlugin 磁力熊插件
type ClxiongPlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
}
func init() {
p := &ClxiongPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("clxiong", 1, true),
debugMode: true,
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *ClxiongPlugin) 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 *ClxiongPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (*model.PluginSearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 开始搜索: %s", keyword)
}
// 第一步POST搜索获取searchid
searchID, err := p.getSearchID(keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取searchid失败: %v", err)
}
return nil, fmt.Errorf("获取searchid失败: %v", err)
}
// 第二步GET搜索结果
results, err := p.getSearchResults(searchID, keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取搜索结果失败: %v", err)
}
return nil, err
}
// 第三步:同步获取详情页磁力链接
p.fetchDetailLinksSync(results)
if p.debugMode {
log.Printf("[CLXIONG] 搜索完成,获得 %d 个结果", len(results))
}
// 应用关键词过滤
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return &model.PluginSearchResult{
Results: filteredResults,
IsFinal: true,
Timestamp: time.Now(),
Source: p.Name(),
Message: fmt.Sprintf("找到 %d 个结果", len(filteredResults)),
}, nil
}
// getSearchID 第一步POST搜索获取searchid
func (p *ClxiongPlugin) getSearchID(keyword string) (string, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取searchid...")
}
client := &http.Client{
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 不自动跟随重定向,我们需要手动处理
return http.ErrUseLastResponse
},
}
// 准备POST数据
formData := url.Values{}
formData.Set("classid", "1,2") // 1=电影2=剧集
formData.Set("show", "title") // 搜索字段
formData.Set("tempid", "1") // 模板ID
formData.Set("keyboard", keyword) // 搜索关键词
req, err := http.NewRequest("POST", SearchURL, strings.NewReader(formData.Encode()))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && (resp.StatusCode == 302 || resp.StatusCode == 301) {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return "", lastErr
}
defer resp.Body.Close()
// 检查重定向响应
if resp.StatusCode != 302 && resp.StatusCode != 301 {
return "", fmt.Errorf("期望302重定向但得到状态码: %d", resp.StatusCode)
}
// 从Location头部提取searchid
location := resp.Header.Get("Location")
if location == "" {
return "", fmt.Errorf("重定向响应中没有Location头部")
}
// 解析searchid
searchID := p.extractSearchIDFromLocation(location)
if searchID == "" {
return "", fmt.Errorf("无法从Location中提取searchid: %s", location)
}
if p.debugMode {
log.Printf("[CLXIONG] 获取到searchid: %s", searchID)
}
return searchID, nil
}
// extractSearchIDFromLocation 从Location头部提取searchid
func (p *ClxiongPlugin) extractSearchIDFromLocation(location string) string {
// location格式: "result/?searchid=7549"
re := regexp.MustCompile(`searchid=(\d+)`)
matches := re.FindStringSubmatch(location)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// getSearchResults 第二步GET搜索结果
func (p *ClxiongPlugin) getSearchResults(searchID, keyword string) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取搜索结果searchid: %s", searchID)
}
// 构建结果页URL
resultURL := fmt.Sprintf("%s/e/search/result/?searchid=%s", BaseURL, searchID)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", resultURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return nil, lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("搜索结果请求失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return p.parseSearchResults(string(body))
}
// parseSearchResults 解析搜索结果页面
func (p *ClxiongPlugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
// 查找搜索结果项
doc.Find(".row.row-cols-2.row-cols-lg-4 .col").Each(func(i int, s *goquery.Selection) {
if i >= MaxResults {
return // 限制结果数量
}
// 提取详情页链接
linkEl := s.Find("a[href*='/drama/'], a[href*='/movie/']")
if linkEl.Length() == 0 {
return // 跳过无链接的项
}
detailPath, exists := linkEl.Attr("href")
if !exists || detailPath == "" {
return
}
// 构建完整的详情页URL
detailURL := BaseURL + detailPath
// 提取标题
title := strings.TrimSpace(linkEl.Find("h2.h4").Text())
if title == "" {
return // 跳过无标题的项
}
// 提取评分
rating := strings.TrimSpace(s.Find(".rank").Text())
// 提取年份
year := strings.TrimSpace(s.Find(".small").Last().Text())
// 提取海报图片
poster := ""
cardImg := s.Find(".card-img")
if cardImg.Length() > 0 {
if style, exists := cardImg.Attr("style"); exists {
poster = p.extractImageFromStyle(style)
}
}
// 构建内容信息
var contentParts []string
if rating != "" {
contentParts = append(contentParts, "评分: "+rating)
}
if year != "" {
contentParts = append(contentParts, "年份: "+year)
}
if poster != "" {
contentParts = append(contentParts, "海报: "+poster)
}
// 添加详情页链接到content中供后续提取磁力链接使用
contentParts = append(contentParts, "详情页: "+detailURL)
content := strings.Join(contentParts, " | ")
// 生成唯一ID
uniqueID := p.generateUniqueID(detailPath)
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空
Tags: []string{"磁力链接", "影视"},
Datetime: time.Now(), // 搜索时间
Links: []model.Link{}, // 初始为空,后续异步获取
UniqueID: uniqueID,
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[CLXIONG] 解析到 %d 个搜索结果", len(results))
}
return results, nil
}
// extractImageFromStyle 从style属性中提取背景图片URL
func (p *ClxiongPlugin) extractImageFromStyle(style string) string {
// style格式: "background-image: url('https://i.nacloud.cc/2024/12154.webp');"
re := regexp.MustCompile(`url\(['"]?([^'"]+)['"]?\)`)
matches := re.FindStringSubmatch(style)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailLinksSync 同步获取详情页磁力链接
func (p *ClxiongPlugin) fetchDetailLinksSync(results []model.SearchResult) {
if len(results) == 0 {
return
}
if p.debugMode {
log.Printf("[CLXIONG] 开始同步获取 %d 个详情页的磁力链接", len(results))
}
// 使用WaitGroup确保所有请求完成后再返回
var wg sync.WaitGroup
// 限制并发数,避免过多请求
semaphore := make(chan struct{}, 5) // 最多5个并发请求
for i := range results {
wg.Add(1)
go func(index int) {
defer wg.Done()
// 获取信号量
semaphore <- struct{}{}
defer func() { <-semaphore }()
detailURL := p.extractDetailURLFromContent(results[index].Content)
if detailURL != "" {
magnetLinks := p.fetchDetailPageMagnetLinks(detailURL)
if len(magnetLinks) > 0 {
results[index].Links = magnetLinks
if p.debugMode {
log.Printf("[CLXIONG] 为结果 %d 获取到 %d 个磁力链接", index+1, len(magnetLinks))
}
}
}
}(i)
}
// 等待所有goroutine完成
wg.Wait()
if p.debugMode {
totalLinks := 0
for _, result := range results {
totalLinks += len(result.Links)
}
log.Printf("[CLXIONG] 所有磁力链接获取完成,共获得 %d 个磁力链接", totalLinks)
}
}
// extractDetailURLFromContent 从content中提取详情页URL
func (p *ClxiongPlugin) extractDetailURLFromContent(content string) string {
// 查找"详情页: URL"模式
re := regexp.MustCompile(`详情页: (https?://[^\s|]+)`)
matches := re.FindStringSubmatch(content)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailPageMagnetLinks 获取详情页的磁力链接
func (p *ClxiongPlugin) fetchDetailPageMagnetLinks(detailURL string) []model.Link {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取详情页磁力链接: %s", detailURL)
}
client := &http.Client{Timeout: 20 * time.Second}
req, err := http.NewRequest("GET", detailURL, nil)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 创建详情页请求失败: %v", err)
}
return nil
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
resp, err := client.Do(req)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 详情页请求失败: %v", err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if p.debugMode {
log.Printf("[CLXIONG] 详情页HTTP状态错误: %d", resp.StatusCode)
}
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 读取详情页响应失败: %v", err)
}
return nil
}
return p.parseMagnetLinksFromDetail(string(body))
}
// parseMagnetLinksFromDetail 从详情页HTML中解析磁力链接
func (p *ClxiongPlugin) parseMagnetLinksFromDetail(html string) []model.Link {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 解析详情页HTML失败: %v", err)
}
return nil
}
var links []model.Link
// 查找磁力链接
doc.Find(".mv_down a[href^='magnet:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
// 获取文件名(链接文本)
fileName := strings.TrimSpace(s.Text())
link := model.Link{
URL: href,
Type: "magnet",
}
// 如果文件名包含大小信息可以存储在Password字段中作为备注
if fileName != "" && strings.Contains(fileName, "[") {
link.Password = fileName // 临时存储文件名和大小信息
}
links = append(links, link)
if p.debugMode {
log.Printf("[CLXIONG] 找到磁力链接: %s %s", fileName, href)
}
}
})
if p.debugMode {
log.Printf("[CLXIONG] 详情页共找到 %d 个磁力链接", len(links))
}
return links
}
// generateUniqueID 生成唯一ID
func (p *ClxiongPlugin) generateUniqueID(detailPath string) string {
// 从路径中提取ID如 "/drama/4466.html" -> "4466"
re := regexp.MustCompile(`/(?:drama|movie)/(\d+)\.html`)
matches := re.FindStringSubmatch(detailPath)
if len(matches) > 1 {
return fmt.Sprintf("clxiong-%s", matches[1])
}
// 备用方案:使用完整路径生成哈希
hash := 0
for _, char := range detailPath {
hash = hash*31 + int(char)
}
if hash < 0 {
hash = -hash
}
return fmt.Sprintf("clxiong-%d", hash)
}

View File

@@ -0,0 +1,168 @@
# 磁力熊(CiLiXiong) HTML结构分析文档
## 网站信息
- **域名**: `www.cilixiong.org`
- **名称**: 磁力熊
- **类型**: 影视磁力链接搜索网站
- **特点**: 两步式搜索流程需要先POST获取searchid再GET搜索结果
## 搜索流程分析
### 第一步:提交搜索请求
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/index.php`
- **方法**: POST
- **Content-Type**: `application/x-www-form-urlencoded`
- **Referer**: `https://www.cilixiong.org/`
#### POST参数
```
classid=1%2C2&show=title&tempid=1&keyboard={URL编码的关键词}
```
参数说明:
- `classid=1,2` - 搜索分类1=电影2=剧集)
- `show=title` - 搜索字段
- `tempid=1` - 模板ID
- `keyboard` - 搜索关键词需URL编码
#### 响应处理
- **状态码**: 302重定向
- **关键信息**: 从响应头`Location`字段获取searchid
- **格式**: `result/?searchid=7549`
### 第二步:获取搜索结果
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/result/?searchid={searchid}`
- **方法**: GET
- **Referer**: `https://www.cilixiong.org/`
## 搜索结果页面结构
### 页面布局
- **容器**: `.container`
- **结果提示**: `.text-white.py-3` - 显示"找到 X 条符合搜索条件"
- **结果网格**: `.row.row-cols-2.row-cols-lg-4.align-items-stretch.g-4.py-2`
### 单个结果项结构
```html
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html">
<div class="card-img" style="background-image: url('海报图片URL');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">影片标题</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div>
</a>
</div>
</div>
```
### 数据提取选择器
#### 结果列表
- **选择器**: `.row.row-cols-2.row-cols-lg-4 .col`
- **排除**: 空白或无效的卡片
#### 单项数据提取
1. **详情链接**: `.col a[href*="/drama/"]``.col a[href*="/movie/"]`
2. **标题**: `.col h2.h4`
3. **评分**: `.col .rank`
4. **年份**: `.col .small`最后一个li元素
5. **海报**: `.col .card-img[style*="background-image"]` - 从style属性提取url
#### 链接格式
- 电影:`/movie/ID.html`
- 剧集:`/drama/ID.html`
- 需补全为绝对URL`https://www.cilixiong.org/drama/ID.html`
## 详情页面结构
### 基本信息区域
```html
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>影片标题</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名:英文名称</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:演员列表</p>
</div>
```
### 磁力链接区域
```html
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">影片名磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3">
<a href="magnet:?xt=urn:btih:HASH">文件名.mkv[文件大小]</a>
<a class="ms-3 text-muted small" href="/magnet.php?url=..." target="_blank">详情</a>
</div>
</div>
</div>
```
### 磁力链接提取
- **容器**: `.mv_down .container`
- **链接项**: `.border-bottom`
- **磁力链接**: `a[href^="magnet:"]`
- **文件名**: 链接的文本内容
- **大小信息**: 通常包含在文件名的方括号中
## 错误处理
### 常见问题
1. **搜索无结果**: 页面会显示"找到 0 条符合搜索条件"
2. **searchid失效**: 可能需要重新发起搜索请求
3. **详情页无磁力链接**: 某些内容可能暂时无下载资源
### 限流检测
- **状态码**: 检测429或403状态码
- **页面内容**: 检测是否包含"访问频繁"等提示
## 实现要点
### 请求头设置
```http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded (POST)
Referer: https://www.cilixiong.org/
```
### Cookie处理
- 网站可能需要维持会话状态
- 建议在客户端中启用Cookie存储
### 搜索策略
1. **首次搜索**: POST提交 → 解析Location → GET结果页
2. **结果解析**: 提取基本信息,构建搜索结果
3. **详情获取**: 可选,异步获取磁力链接
### 数据字段映射
- **Title**: 影片中文标题
- **Content**: 评分、年份、类型等信息组合
- **UniqueID**: 使用详情页URL的ID部分
- **Links**: 磁力链接数组
- **Tags**: 影片类型标签
## 技术注意事项
### URL编码
- 搜索关键词必须进行URL编码
- 中文字符使用UTF-8编码
### 重定向处理
- POST请求会返回302重定向
- 需要从响应头提取Location信息
- 不要自动跟随重定向,需要手动解析
### 异步处理
- 搜索结果可以先返回基本信息
- 磁力链接通过异步请求详情页获取
- 设置合理的并发限制和超时时间

301
plugin/clxiong copy 3/1.txt Normal file
View File

@@ -0,0 +1,301 @@
1.获取 location
post https://www.cilixiong.org/e/search/index.php
content-type application/x-www-form-urlencoded
referer https://www.cilixiong.org/
classid=1%2C2&show=title&tempid=1&keyboard=%E7%91%9E%E5%85%8B%E5%92%8C%E8%8E%AB%E8%92%82
返回值:
从返回的headers取location值
location result/?searchid=7549
2.搜索
get https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂 搜索结果 - 磁力熊</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="">
</form>
</div>
</div>
</header>
<div class="container">
<div class="text-white py-3">找到 8 条符合搜索条件 "<strong class="text-success">瑞克和莫蒂</strong>" 的结果</div>
<div class="row row-cols-2 row-cols-lg-4 align-items-stretch g-4 py-2">
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2024/12154.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3781.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11111.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第七季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.8</span></li>
<li class="d-flex align-items-center small">2023</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3780.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11109.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第六季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.3</span></li>
<li class="d-flex align-items-center small">2022</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1829.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2022/12085.jpg');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第五季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.5</span></li>
<li class="d-flex align-items-center small">2021</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1740.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11108.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第四季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2019</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1739.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11107.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第三季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2017</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1738.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11106.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第二季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2015</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1737.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11105.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第一季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2013</li>
</ul>
</div></a>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-1 py-3 px-5">
<ul class="pagination justify-content-center">
</ul>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
</body>
</html>
3.详情页
get https://www.cilixiong.org/drama/4466.html
referer https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂(2025) 美国电视剧1080P下载在线观看 - 磁力熊</title>
<meta name="keywords" content="瑞克和莫蒂 / Rick and Morty Season 8, 2025年美国|喜剧|冒险|科幻|动画|电视剧">
<meta name="description" content="剧集瑞克和莫蒂1080P磁力下载,瑞克和莫蒂magnet链接,瑞克和莫蒂在线播放" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:url" content="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:site_name" content="磁力熊"/>
<meta property="og:title" content="瑞克和莫蒂 - 磁力熊" />
<meta property="og:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="og:type" content="video.movie"/>
<meta property="og:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<meta property="og:locale" content="zh_CN" />
<meta property="twitter:title" content="瑞克和莫蒂 - 磁力熊"/>
<meta property="twitter:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="twitter:card" content="summary_large_image"/>
<meta property="twitter:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
<script type="application/ld+json">
{
"@context": "http://schema.org",
"name": "瑞克和莫蒂",
"url": "/drama/4466.html",
"image": "https://i.nacloud.cc/2024/12154.webp",
"datePublished": "2025-05-25(美国)",
"description": "瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已...",
"@type": "TVSeries"
}
</script>
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li class="selected"><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="" autocomplete="off">
</form>
</div>
</div>
</header>
<div class="container">
<div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 p-5 text-white">
<div class="p-3">
<img class="rounded-2" style="max-width:100%;" alt="瑞克和莫蒂" src="https://i.nacloud.cc/2024/12154.webp" />
</div>
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>瑞克和莫蒂</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名Rick and Morty Season 8</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:伊恩·卡多尼 / 哈利·贝尔登 / 克里斯·帕内尔 / 斯宾瑟·格拉默 / 萨拉·乔克</p>
<p class="mb-2">最后更新于2025-08-16</p>
<p class="mb-2"></p>
</div>
<div class="mv_card p-4">
<h2 class="pb-2">瑞克和莫蒂剧情简介:</h2>
<div class="mv_card_box">瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已经尝试过了!</div>
</div>
</div>
<div class="row col-md-12 embed_video">
<iframe width="100%" height="100%" src="/e/extend/jx.php?id=4466" frameborder="0" border="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen="allowfullscreen" mozallowfullscreen="mozallowfullscreen" msallowfullscreen="msallowfullscreen" oallowfullscreen="oallowfullscreen" webkitallowfullscreen="webkitallowfullscreen" sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts"></iframe>
</div>
<div class="row col-md-12 py-3 pb-0">
<p class="text-muted text-center small">剧集仅提供第一集在线播放预览。</p>
</div>
<div class="row col-md-12 text-white p-3 pt-1">
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">瑞克和莫蒂磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A">Rick.and.Morty.S08.Complete.1080p.10bit.WEBRip.6CH.x265.HEVC-PSA[2G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A" target="_blank">详情</a></div>
</div><div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF">Rick.and.Morty.S08.1080p.AMZN.WEB-DL.H.264-EniaHD[8.4G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF" target="_blank">详情</a></div>
</div>
<div class="container">
<div style="font-size:13px;" class="pt-1 pb-3 text-muted">为保证质量优先选择原声版本,下载字幕请前往 <a href="https://subhd.tv/search/瑞克和莫蒂" target="_blank" rel="nofollow">Subhd字幕</a>、<a href="https://zimuku.org/search?q=瑞克和莫蒂" target="_blank" rel="nofollow">Zimuku字幕库</a></div>
</div>
</div>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
<script src="/e/public/onclick/?enews=donews&classid=2&id=4466"></script></body>
</html>

View File

@@ -0,0 +1,587 @@
package clxiong
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://www.cilixiong.org"
SearchURL = "https://www.cilixiong.org/e/search/index.php"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxRetries = 3
RetryDelay = 2 * time.Second
MaxResults = 30
)
// DetailPageInfo 详情页信息结构体
type DetailPageInfo struct {
MagnetLinks []model.Link
UpdateTime time.Time
Title string
FirstFileName string // 第一个文件的名称用于生成note
}
// ClxiongPlugin 磁力熊插件
type ClxiongPlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
}
func init() {
p := &ClxiongPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("clxiong", 1, true),
debugMode: false,
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *ClxiongPlugin) 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 *ClxiongPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (*model.PluginSearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 开始搜索: %s", keyword)
}
// 第一步POST搜索获取searchid
searchID, err := p.getSearchID(keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取searchid失败: %v", err)
}
return nil, fmt.Errorf("获取searchid失败: %v", err)
}
// 第二步GET搜索结果
results, err := p.getSearchResults(searchID, keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取搜索结果失败: %v", err)
}
return nil, err
}
// 第三步:同步获取详情页磁力链接
p.fetchDetailLinksSync(results)
if p.debugMode {
log.Printf("[CLXIONG] 搜索完成,获得 %d 个结果", len(results))
}
// 应用关键词过滤
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return &model.PluginSearchResult{
Results: filteredResults,
IsFinal: true,
Timestamp: time.Now(),
Source: p.Name(),
Message: fmt.Sprintf("找到 %d 个结果", len(filteredResults)),
}, nil
}
// getSearchID 第一步POST搜索获取searchid
func (p *ClxiongPlugin) getSearchID(keyword string) (string, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取searchid...")
}
client := &http.Client{
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 不自动跟随重定向,我们需要手动处理
return http.ErrUseLastResponse
},
}
// 准备POST数据
formData := url.Values{}
formData.Set("classid", "1,2") // 1=电影2=剧集
formData.Set("show", "title") // 搜索字段
formData.Set("tempid", "1") // 模板ID
formData.Set("keyboard", keyword) // 搜索关键词
req, err := http.NewRequest("POST", SearchURL, strings.NewReader(formData.Encode()))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && (resp.StatusCode == 302 || resp.StatusCode == 301) {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return "", lastErr
}
defer resp.Body.Close()
// 检查重定向响应
if resp.StatusCode != 302 && resp.StatusCode != 301 {
return "", fmt.Errorf("期望302重定向但得到状态码: %d", resp.StatusCode)
}
// 从Location头部提取searchid
location := resp.Header.Get("Location")
if location == "" {
return "", fmt.Errorf("重定向响应中没有Location头部")
}
// 解析searchid
searchID := p.extractSearchIDFromLocation(location)
if searchID == "" {
return "", fmt.Errorf("无法从Location中提取searchid: %s", location)
}
if p.debugMode {
log.Printf("[CLXIONG] 获取到searchid: %s", searchID)
}
return searchID, nil
}
// extractSearchIDFromLocation 从Location头部提取searchid
func (p *ClxiongPlugin) extractSearchIDFromLocation(location string) string {
// location格式: "result/?searchid=7549"
re := regexp.MustCompile(`searchid=(\d+)`)
matches := re.FindStringSubmatch(location)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// getSearchResults 第二步GET搜索结果
func (p *ClxiongPlugin) getSearchResults(searchID, keyword string) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取搜索结果searchid: %s", searchID)
}
// 构建结果页URL
resultURL := fmt.Sprintf("%s/e/search/result/?searchid=%s", BaseURL, searchID)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", resultURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return nil, lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("搜索结果请求失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return p.parseSearchResults(string(body))
}
// parseSearchResults 解析搜索结果页面
func (p *ClxiongPlugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
// 查找搜索结果项
doc.Find(".row.row-cols-2.row-cols-lg-4 .col").Each(func(i int, s *goquery.Selection) {
if i >= MaxResults {
return // 限制结果数量
}
// 提取详情页链接
linkEl := s.Find("a[href*='/drama/'], a[href*='/movie/']")
if linkEl.Length() == 0 {
return // 跳过无链接的项
}
detailPath, exists := linkEl.Attr("href")
if !exists || detailPath == "" {
return
}
// 构建完整的详情页URL
detailURL := BaseURL + detailPath
// 提取标题
title := strings.TrimSpace(linkEl.Find("h2.h4").Text())
if title == "" {
return // 跳过无标题的项
}
// 提取评分
rating := strings.TrimSpace(s.Find(".rank").Text())
// 提取年份
year := strings.TrimSpace(s.Find(".small").Last().Text())
// 提取海报图片
poster := ""
cardImg := s.Find(".card-img")
if cardImg.Length() > 0 {
if style, exists := cardImg.Attr("style"); exists {
poster = p.extractImageFromStyle(style)
}
}
// 构建内容信息
var contentParts []string
if rating != "" {
contentParts = append(contentParts, "评分: "+rating)
}
if year != "" {
contentParts = append(contentParts, "年份: "+year)
}
if poster != "" {
contentParts = append(contentParts, "海报: "+poster)
}
// 添加详情页链接到content中供后续提取磁力链接使用
contentParts = append(contentParts, "详情页: "+detailURL)
content := strings.Join(contentParts, " | ")
// 生成唯一ID
uniqueID := p.generateUniqueID(detailPath)
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空
Tags: []string{"磁力链接", "影视"},
Datetime: time.Now(), // 搜索时间
Links: []model.Link{}, // 初始为空,后续异步获取
UniqueID: uniqueID,
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[CLXIONG] 解析到 %d 个搜索结果", len(results))
}
return results, nil
}
// extractImageFromStyle 从style属性中提取背景图片URL
func (p *ClxiongPlugin) extractImageFromStyle(style string) string {
// style格式: "background-image: url('https://i.nacloud.cc/2024/12154.webp');"
re := regexp.MustCompile(`url\(['"]?([^'"]+)['"]?\)`)
matches := re.FindStringSubmatch(style)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailLinksSync 同步获取详情页磁力链接
func (p *ClxiongPlugin) fetchDetailLinksSync(results []model.SearchResult) {
if len(results) == 0 {
return
}
if p.debugMode {
log.Printf("[CLXIONG] 开始同步获取 %d 个详情页的磁力链接", len(results))
}
// 使用WaitGroup确保所有请求完成后再返回
var wg sync.WaitGroup
// 限制并发数,避免过多请求
semaphore := make(chan struct{}, 5) // 最多5个并发请求
for i := range results {
wg.Add(1)
go func(index int) {
defer wg.Done()
// 获取信号量
semaphore <- struct{}{}
defer func() { <-semaphore }()
detailURL := p.extractDetailURLFromContent(results[index].Content)
if detailURL != "" {
detailInfo := p.fetchDetailPageInfo(detailURL, results[index].Title)
if detailInfo != nil && len(detailInfo.MagnetLinks) > 0 {
results[index].Links = detailInfo.MagnetLinks
// 更新日期时间为详情页的更新时间
if !detailInfo.UpdateTime.IsZero() {
results[index].Datetime = detailInfo.UpdateTime
}
// 更新标题使其包含第一个文件信息用于生成正确的note
if detailInfo.FirstFileName != "" {
results[index].Title = fmt.Sprintf("%s-%s", results[index].Title, detailInfo.FirstFileName)
}
if p.debugMode {
log.Printf("[CLXIONG] 为结果 %d 获取到 %d 个磁力链接", index+1, len(detailInfo.MagnetLinks))
}
}
}
}(i)
}
// 等待所有goroutine完成
wg.Wait()
if p.debugMode {
totalLinks := 0
for _, result := range results {
totalLinks += len(result.Links)
}
log.Printf("[CLXIONG] 所有磁力链接获取完成,共获得 %d 个磁力链接", totalLinks)
}
}
// extractDetailURLFromContent 从content中提取详情页URL
func (p *ClxiongPlugin) extractDetailURLFromContent(content string) string {
// 查找"详情页: URL"模式
re := regexp.MustCompile(`详情页: (https?://[^\s|]+)`)
matches := re.FindStringSubmatch(content)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailPageInfo 获取详情页的完整信息
func (p *ClxiongPlugin) fetchDetailPageInfo(detailURL string, movieTitle string) *DetailPageInfo {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取详情页信息: %s", detailURL)
}
client := &http.Client{Timeout: 20 * time.Second}
req, err := http.NewRequest("GET", detailURL, nil)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 创建详情页请求失败: %v", err)
}
return nil
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
resp, err := client.Do(req)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 详情页请求失败: %v", err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if p.debugMode {
log.Printf("[CLXIONG] 详情页HTTP状态错误: %d", resp.StatusCode)
}
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 读取详情页响应失败: %v", err)
}
return nil
}
return p.parseDetailPageInfo(string(body), movieTitle)
}
// parseDetailPageInfo 从详情页HTML中解析完整信息
func (p *ClxiongPlugin) parseDetailPageInfo(html string, movieTitle string) *DetailPageInfo {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 解析详情页HTML失败: %v", err)
}
return nil
}
detailInfo := &DetailPageInfo{
Title: movieTitle,
}
// 解析更新时间
detailInfo.UpdateTime = p.parseUpdateTimeFromDetail(doc)
// 解析磁力链接
magnetLinks, firstFileName := p.parseMagnetLinksFromDetailDoc(doc, movieTitle)
detailInfo.MagnetLinks = magnetLinks
detailInfo.FirstFileName = firstFileName
if p.debugMode {
log.Printf("[CLXIONG] 详情页解析完成: 磁力链接 %d 个,更新时间: %v",
len(detailInfo.MagnetLinks), detailInfo.UpdateTime)
}
return detailInfo
}
// parseUpdateTimeFromDetail 从详情页解析更新时间
func (p *ClxiongPlugin) parseUpdateTimeFromDetail(doc *goquery.Document) time.Time {
// 查找"最后更新于2025-08-16"这样的文本
var updateTime time.Time
doc.Find(".mv_detail p").Each(func(i int, s *goquery.Selection) {
text := strings.TrimSpace(s.Text())
if strings.Contains(text, "最后更新于:") {
// 提取日期部分
dateStr := strings.Replace(text, "最后更新于:", "", 1)
dateStr = strings.TrimSpace(dateStr)
// 解析日期,支持多种格式
layouts := []string{
"2006-01-02",
"2006-1-2",
"2006/01/02",
"2006/1/2",
}
for _, layout := range layouts {
if t, err := time.Parse(layout, dateStr); err == nil {
updateTime = t
if p.debugMode {
log.Printf("[CLXIONG] 解析到更新时间: %s -> %v", dateStr, updateTime)
}
return
}
}
if p.debugMode {
log.Printf("[CLXIONG] 无法解析更新时间: %s", dateStr)
}
}
})
return updateTime
}
// parseMagnetLinksFromDetailDoc 从详情页DOM解析磁力链接
func (p *ClxiongPlugin) parseMagnetLinksFromDetailDoc(doc *goquery.Document, movieTitle string) ([]model.Link, string) {
var links []model.Link
var firstFileName string
// 查找磁力链接
doc.Find(".mv_down a[href^='magnet:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
// 获取文件名(链接文本)
fileName := strings.TrimSpace(s.Text())
// 记录第一个文件名
if i == 0 && fileName != "" {
firstFileName = fileName
}
link := model.Link{
URL: href,
Type: "magnet",
}
// 磁力链接密码字段设置为空(按用户要求)
link.Password = ""
links = append(links, link)
if p.debugMode {
log.Printf("[CLXIONG] 找到磁力链接: %s", fileName)
}
}
})
if p.debugMode {
log.Printf("[CLXIONG] 详情页共找到 %d 个磁力链接", len(links))
}
return links, firstFileName
}
// generateUniqueID 生成唯一ID
func (p *ClxiongPlugin) generateUniqueID(detailPath string) string {
// 从路径中提取ID如 "/drama/4466.html" -> "4466"
re := regexp.MustCompile(`/(?:drama|movie)/(\d+)\.html`)
matches := re.FindStringSubmatch(detailPath)
if len(matches) > 1 {
return fmt.Sprintf("clxiong-%s", matches[1])
}
// 备用方案:使用完整路径生成哈希
hash := 0
for _, char := range detailPath {
hash = hash*31 + int(char)
}
if hash < 0 {
hash = -hash
}
return fmt.Sprintf("clxiong-%d", hash)
}

View File

@@ -0,0 +1,168 @@
# 磁力熊(CiLiXiong) HTML结构分析文档
## 网站信息
- **域名**: `www.cilixiong.org`
- **名称**: 磁力熊
- **类型**: 影视磁力链接搜索网站
- **特点**: 两步式搜索流程需要先POST获取searchid再GET搜索结果
## 搜索流程分析
### 第一步:提交搜索请求
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/index.php`
- **方法**: POST
- **Content-Type**: `application/x-www-form-urlencoded`
- **Referer**: `https://www.cilixiong.org/`
#### POST参数
```
classid=1%2C2&show=title&tempid=1&keyboard={URL编码的关键词}
```
参数说明:
- `classid=1,2` - 搜索分类1=电影2=剧集)
- `show=title` - 搜索字段
- `tempid=1` - 模板ID
- `keyboard` - 搜索关键词需URL编码
#### 响应处理
- **状态码**: 302重定向
- **关键信息**: 从响应头`Location`字段获取searchid
- **格式**: `result/?searchid=7549`
### 第二步:获取搜索结果
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/result/?searchid={searchid}`
- **方法**: GET
- **Referer**: `https://www.cilixiong.org/`
## 搜索结果页面结构
### 页面布局
- **容器**: `.container`
- **结果提示**: `.text-white.py-3` - 显示"找到 X 条符合搜索条件"
- **结果网格**: `.row.row-cols-2.row-cols-lg-4.align-items-stretch.g-4.py-2`
### 单个结果项结构
```html
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html">
<div class="card-img" style="background-image: url('海报图片URL');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">影片标题</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div>
</a>
</div>
</div>
```
### 数据提取选择器
#### 结果列表
- **选择器**: `.row.row-cols-2.row-cols-lg-4 .col`
- **排除**: 空白或无效的卡片
#### 单项数据提取
1. **详情链接**: `.col a[href*="/drama/"]``.col a[href*="/movie/"]`
2. **标题**: `.col h2.h4`
3. **评分**: `.col .rank`
4. **年份**: `.col .small`最后一个li元素
5. **海报**: `.col .card-img[style*="background-image"]` - 从style属性提取url
#### 链接格式
- 电影:`/movie/ID.html`
- 剧集:`/drama/ID.html`
- 需补全为绝对URL`https://www.cilixiong.org/drama/ID.html`
## 详情页面结构
### 基本信息区域
```html
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>影片标题</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名:英文名称</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:演员列表</p>
</div>
```
### 磁力链接区域
```html
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">影片名磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3">
<a href="magnet:?xt=urn:btih:HASH">文件名.mkv[文件大小]</a>
<a class="ms-3 text-muted small" href="/magnet.php?url=..." target="_blank">详情</a>
</div>
</div>
</div>
```
### 磁力链接提取
- **容器**: `.mv_down .container`
- **链接项**: `.border-bottom`
- **磁力链接**: `a[href^="magnet:"]`
- **文件名**: 链接的文本内容
- **大小信息**: 通常包含在文件名的方括号中
## 错误处理
### 常见问题
1. **搜索无结果**: 页面会显示"找到 0 条符合搜索条件"
2. **searchid失效**: 可能需要重新发起搜索请求
3. **详情页无磁力链接**: 某些内容可能暂时无下载资源
### 限流检测
- **状态码**: 检测429或403状态码
- **页面内容**: 检测是否包含"访问频繁"等提示
## 实现要点
### 请求头设置
```http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded (POST)
Referer: https://www.cilixiong.org/
```
### Cookie处理
- 网站可能需要维持会话状态
- 建议在客户端中启用Cookie存储
### 搜索策略
1. **首次搜索**: POST提交 → 解析Location → GET结果页
2. **结果解析**: 提取基本信息,构建搜索结果
3. **详情获取**: 可选,异步获取磁力链接
### 数据字段映射
- **Title**: 影片中文标题
- **Content**: 评分、年份、类型等信息组合
- **UniqueID**: 使用详情页URL的ID部分
- **Links**: 磁力链接数组
- **Tags**: 影片类型标签
## 技术注意事项
### URL编码
- 搜索关键词必须进行URL编码
- 中文字符使用UTF-8编码
### 重定向处理
- POST请求会返回302重定向
- 需要从响应头提取Location信息
- 不要自动跟随重定向,需要手动解析
### 异步处理
- 搜索结果可以先返回基本信息
- 磁力链接通过异步请求详情页获取
- 设置合理的并发限制和超时时间

301
plugin/clxiong copy/1.txt Normal file
View File

@@ -0,0 +1,301 @@
1.获取 location
post https://www.cilixiong.org/e/search/index.php
content-type application/x-www-form-urlencoded
referer https://www.cilixiong.org/
classid=1%2C2&show=title&tempid=1&keyboard=%E7%91%9E%E5%85%8B%E5%92%8C%E8%8E%AB%E8%92%82
返回值:
从返回的headers取location值
location result/?searchid=7549
2.搜索
get https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂 搜索结果 - 磁力熊</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="">
</form>
</div>
</div>
</header>
<div class="container">
<div class="text-white py-3">找到 8 条符合搜索条件 "<strong class="text-success">瑞克和莫蒂</strong>" 的结果</div>
<div class="row row-cols-2 row-cols-lg-4 align-items-stretch g-4 py-2">
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2024/12154.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3781.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11111.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第七季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.8</span></li>
<li class="d-flex align-items-center small">2023</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/3780.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11109.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第六季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.3</span></li>
<li class="d-flex align-items-center small">2022</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1829.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2022/12085.jpg');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第五季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.5</span></li>
<li class="d-flex align-items-center small">2021</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1740.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11108.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第四季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2019</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1739.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11107.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第三季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2017</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1738.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11106.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第二季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.8</span></li>
<li class="d-flex align-items-center small">2015</li>
</ul>
</div></a>
</div>
</div>
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/1737.html"><div class="card-img" style="background-image: url('https://i.nacloud.cc/2020/11105.webp');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">瑞克和莫蒂 第一季</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">9.7</span></li>
<li class="d-flex align-items-center small">2013</li>
</ul>
</div></a>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-1 py-3 px-5">
<ul class="pagination justify-content-center">
</ul>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
</body>
</html>
3.详情页
get https://www.cilixiong.org/drama/4466.html
referer https://www.cilixiong.org/e/search/result/?searchid=7549
返回值:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>瑞克和莫蒂(2025) 美国电视剧1080P下载在线观看 - 磁力熊</title>
<meta name="keywords" content="瑞克和莫蒂 / Rick and Morty Season 8, 2025年美国|喜剧|冒险|科幻|动画|电视剧">
<meta name="description" content="剧集瑞克和莫蒂1080P磁力下载,瑞克和莫蒂magnet链接,瑞克和莫蒂在线播放" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:url" content="https://www.cilixiong.org/drama/4466.html"/>
<meta property="og:site_name" content="磁力熊"/>
<meta property="og:title" content="瑞克和莫蒂 - 磁力熊" />
<meta property="og:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="og:type" content="video.movie"/>
<meta property="og:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<meta property="og:locale" content="zh_CN" />
<meta property="twitter:title" content="瑞克和莫蒂 - 磁力熊"/>
<meta property="twitter:description" content="瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Mo"/>
<meta property="twitter:card" content="summary_large_image"/>
<meta property="twitter:image" content="https://i.nacloud.cc/2024/12154.webp"/>
<link href="https://lib.baomitu.com/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="/skin/cili/style.css?1116" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
<script type="application/ld+json">
{
"@context": "http://schema.org",
"name": "瑞克和莫蒂",
"url": "/drama/4466.html",
"image": "https://i.nacloud.cc/2024/12154.webp",
"datePublished": "2025-05-25(美国)",
"description": "瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已...",
"@type": "TVSeries"
}
</script>
</head>
<body class="bg-dark">
<div class="bg-cover"></div>
<div class="bg-overlay"></div>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a class="d-flex align-items-center mb-2 mb-lg-0 px-5 logo" href="/"><img src="/skin/cili/logo.png?0418"/></a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a class="nav-link px-3" href="/">首页</a></li>
<li><a class="nav-link px-3" href="/movie/">电影</a></li>
<li class="selected"><a class="nav-link px-3" href="/drama/">剧集</a></li>
<li><a class="nav-link px-3" href="/subject.html">榜单</a></li>
<li><a class="nav-link px-3" href="/e/tool/gbook/?bid=1">留言</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="serach" action="/e/search/index.php" method="post" name="searchform" id="searchform">
<input type="hidden" name="classid" value="1,2" />
<input type="hidden" name="show" value="title" />
<input type="hidden" name="tempid" value="1" />
<input type="text" name="keyboard" class="form-control form-control-dark text-bg-dark" value="" placeholder="搜索影片" aria-label="Search" value="" autocomplete="off">
</form>
</div>
</div>
</header>
<div class="container">
<div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 p-5 text-white">
<div class="p-3">
<img class="rounded-2" style="max-width:100%;" alt="瑞克和莫蒂" src="https://i.nacloud.cc/2024/12154.webp" />
</div>
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>瑞克和莫蒂</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名Rick and Morty Season 8</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:伊恩·卡多尼 / 哈利·贝尔登 / 克里斯·帕内尔 / 斯宾瑟·格拉默 / 萨拉·乔克</p>
<p class="mb-2">最后更新于2025-08-16</p>
<p class="mb-2"></p>
</div>
<div class="mv_card p-4">
<h2 class="pb-2">瑞克和莫蒂剧情简介:</h2>
<div class="mv_card_box">瑞克和莫蒂第八季回来了!生活又有了意义!一切皆有可能!留意 Summer、Jerry、Beth 和其他 Beth 的冒险。也许 Butter Bot 会得到一个新的任务?无论发生什么,你都不能让 Rick 和 Morty 失望太久。人们已经尝试过了!</div>
</div>
</div>
<div class="row col-md-12 embed_video">
<iframe width="100%" height="100%" src="/e/extend/jx.php?id=4466" frameborder="0" border="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen="allowfullscreen" mozallowfullscreen="mozallowfullscreen" msallowfullscreen="msallowfullscreen" oallowfullscreen="oallowfullscreen" webkitallowfullscreen="webkitallowfullscreen" sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts"></iframe>
</div>
<div class="row col-md-12 py-3 pb-0">
<p class="text-muted text-center small">剧集仅提供第一集在线播放预览。</p>
</div>
<div class="row col-md-12 text-white p-3 pt-1">
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">瑞克和莫蒂磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A">Rick.and.Morty.S08.Complete.1080p.10bit.WEBRip.6CH.x265.HEVC-PSA[2G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:4D41F09A483FF739FBEC7A7701744F11A73DFC7A" target="_blank">详情</a></div>
</div><div class="container">
<div class="border-bottom pt-2 pb-4 mb-3"><a href="magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF">Rick.and.Morty.S08.1080p.AMZN.WEB-DL.H.264-EniaHD[8.4G]</a><a class="ms-3 text-muted small" href="/magnet.php?url=magnet:?xt=urn:btih:96AF0465BF60A88A8C9F4E9ABF98D4CC84E81DBF" target="_blank">详情</a></div>
</div>
<div class="container">
<div style="font-size:13px;" class="pt-1 pb-3 text-muted">为保证质量优先选择原声版本,下载字幕请前往 <a href="https://subhd.tv/search/瑞克和莫蒂" target="_blank" rel="nofollow">Subhd字幕</a>、<a href="https://zimuku.org/search?q=瑞克和莫蒂" target="_blank" rel="nofollow">Zimuku字幕库</a></div>
</div>
</div>
</div>
</div>
<div class="p-3 pb-2 text-center small">
<span class="small" style="color:#dc3545;">//</span> <a class="text-muted" href="/e/public/Click?adid=5" target="_blank">激情小视频在线观看</a>
</div>
<footer class="container py-3">
<div class="d-flex flex-column flex-sm-row justify-content-between my-4 text-secondary">
<p class="small text-secondary pt-2">©2025 磁力熊 <a class="px-2 text-muted" href="/about.html">关于我们</a></p>
<ul class="nav col-md-4 justify-content-end list-unstyled small">
<li class="nav-item"><a class="nav-link px-2 text-muted" href="https://movie.douban.com/" target="_blank">豆瓣电影</a></li>
</ul>
</div>
</footer>
<script src="/e/public/onclick/?enews=donews&classid=2&id=4466"></script></body>
</html>

View File

@@ -0,0 +1,483 @@
package clxiong
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://www.cilixiong.org"
SearchURL = "https://www.cilixiong.org/e/search/index.php"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxRetries = 3
RetryDelay = 2 * time.Second
MaxResults = 30
)
// ClxiongPlugin 磁力熊插件
type ClxiongPlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
}
func init() {
p := &ClxiongPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("clxiong", 1, true),
debugMode: true,
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *ClxiongPlugin) 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 *ClxiongPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (*model.PluginSearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 开始搜索: %s", keyword)
}
// 第一步POST搜索获取searchid
searchID, err := p.getSearchID(keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取searchid失败: %v", err)
}
return nil, fmt.Errorf("获取searchid失败: %v", err)
}
// 第二步GET搜索结果
results, err := p.getSearchResults(searchID, keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取搜索结果失败: %v", err)
}
return nil, err
}
// 第三步:异步获取详情页磁力链接
p.fetchDetailLinksAsync(results)
if p.debugMode {
log.Printf("[CLXIONG] 搜索完成,获得 %d 个结果", len(results))
}
// 应用关键词过滤
fmt.Printf("results: %v\n", results)
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return &model.PluginSearchResult{
Results: filteredResults,
IsFinal: true,
Timestamp: time.Now(),
Source: p.Name(),
Message: fmt.Sprintf("找到 %d 个结果", len(filteredResults)),
}, nil
}
// getSearchID 第一步POST搜索获取searchid
func (p *ClxiongPlugin) getSearchID(keyword string) (string, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取searchid...")
}
client := &http.Client{
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 不自动跟随重定向,我们需要手动处理
return http.ErrUseLastResponse
},
}
// 准备POST数据
formData := url.Values{}
formData.Set("classid", "1,2") // 1=电影2=剧集
formData.Set("show", "title") // 搜索字段
formData.Set("tempid", "1") // 模板ID
formData.Set("keyboard", keyword) // 搜索关键词
req, err := http.NewRequest("POST", SearchURL, strings.NewReader(formData.Encode()))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && (resp.StatusCode == 302 || resp.StatusCode == 301) {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return "", lastErr
}
defer resp.Body.Close()
// 检查重定向响应
if resp.StatusCode != 302 && resp.StatusCode != 301 {
return "", fmt.Errorf("期望302重定向但得到状态码: %d", resp.StatusCode)
}
// 从Location头部提取searchid
location := resp.Header.Get("Location")
if location == "" {
return "", fmt.Errorf("重定向响应中没有Location头部")
}
// 解析searchid
searchID := p.extractSearchIDFromLocation(location)
if searchID == "" {
return "", fmt.Errorf("无法从Location中提取searchid: %s", location)
}
if p.debugMode {
log.Printf("[CLXIONG] 获取到searchid: %s", searchID)
}
return searchID, nil
}
// extractSearchIDFromLocation 从Location头部提取searchid
func (p *ClxiongPlugin) extractSearchIDFromLocation(location string) string {
// location格式: "result/?searchid=7549"
re := regexp.MustCompile(`searchid=(\d+)`)
matches := re.FindStringSubmatch(location)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// getSearchResults 第二步GET搜索结果
func (p *ClxiongPlugin) getSearchResults(searchID, keyword string) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取搜索结果searchid: %s", searchID)
}
// 构建结果页URL
resultURL := fmt.Sprintf("%s/e/search/result/?searchid=%s", BaseURL, searchID)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", resultURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return nil, lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("搜索结果请求失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return p.parseSearchResults(string(body))
}
// parseSearchResults 解析搜索结果页面
func (p *ClxiongPlugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
// 查找搜索结果项
doc.Find(".row.row-cols-2.row-cols-lg-4 .col").Each(func(i int, s *goquery.Selection) {
if i >= MaxResults {
return // 限制结果数量
}
// 提取详情页链接
linkEl := s.Find("a[href*='/drama/'], a[href*='/movie/']")
if linkEl.Length() == 0 {
return // 跳过无链接的项
}
detailPath, exists := linkEl.Attr("href")
if !exists || detailPath == "" {
return
}
// 构建完整的详情页URL
detailURL := BaseURL + detailPath
// 提取标题
title := strings.TrimSpace(linkEl.Find("h2.h4").Text())
if title == "" {
return // 跳过无标题的项
}
// 提取评分
rating := strings.TrimSpace(s.Find(".rank").Text())
// 提取年份
year := strings.TrimSpace(s.Find(".small").Last().Text())
// 提取海报图片
poster := ""
cardImg := s.Find(".card-img")
if cardImg.Length() > 0 {
if style, exists := cardImg.Attr("style"); exists {
poster = p.extractImageFromStyle(style)
}
}
// 构建内容信息
var contentParts []string
if rating != "" {
contentParts = append(contentParts, "评分: "+rating)
}
if year != "" {
contentParts = append(contentParts, "年份: "+year)
}
if poster != "" {
contentParts = append(contentParts, "海报: "+poster)
}
// 添加详情页链接到content中供后续提取磁力链接使用
contentParts = append(contentParts, "详情页: "+detailURL)
content := strings.Join(contentParts, " | ")
// 生成唯一ID
uniqueID := p.generateUniqueID(detailPath)
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空
Tags: []string{"磁力链接", "影视"},
Datetime: time.Now(), // 搜索时间
Links: []model.Link{}, // 初始为空,后续异步获取
UniqueID: uniqueID,
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[CLXIONG] 解析到 %d 个搜索结果", len(results))
}
return results, nil
}
// extractImageFromStyle 从style属性中提取背景图片URL
func (p *ClxiongPlugin) extractImageFromStyle(style string) string {
// style格式: "background-image: url('https://i.nacloud.cc/2024/12154.webp');"
re := regexp.MustCompile(`url\(['"]?([^'"]+)['"]?\)`)
matches := re.FindStringSubmatch(style)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailLinksAsync 异步获取详情页磁力链接
func (p *ClxiongPlugin) fetchDetailLinksAsync(results []model.SearchResult) {
if len(results) == 0 {
return
}
if p.debugMode {
log.Printf("[CLXIONG] 开始异步获取 %d 个详情页的磁力链接", len(results))
}
// 使用goroutine异步获取避免阻塞主搜索流程
for i := range results {
go func(index int) {
detailURL := p.extractDetailURLFromContent(results[index].Content)
if detailURL != "" {
magnetLinks := p.fetchDetailPageMagnetLinks(detailURL)
if len(magnetLinks) > 0 {
results[index].Links = magnetLinks
if p.debugMode {
log.Printf("[CLXIONG] 为结果 %d 获取到 %d 个磁力链接", index+1, len(magnetLinks))
}
}
}
}(i)
}
}
// extractDetailURLFromContent 从content中提取详情页URL
func (p *ClxiongPlugin) extractDetailURLFromContent(content string) string {
// 查找"详情页: URL"模式
re := regexp.MustCompile(`详情页: (https?://[^\s|]+)`)
matches := re.FindStringSubmatch(content)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailPageMagnetLinks 获取详情页的磁力链接
func (p *ClxiongPlugin) fetchDetailPageMagnetLinks(detailURL string) []model.Link {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取详情页磁力链接: %s", detailURL)
}
client := &http.Client{Timeout: 20 * time.Second}
req, err := http.NewRequest("GET", detailURL, nil)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 创建详情页请求失败: %v", err)
}
return nil
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
resp, err := client.Do(req)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 详情页请求失败: %v", err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if p.debugMode {
log.Printf("[CLXIONG] 详情页HTTP状态错误: %d", resp.StatusCode)
}
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 读取详情页响应失败: %v", err)
}
return nil
}
return p.parseMagnetLinksFromDetail(string(body))
}
// parseMagnetLinksFromDetail 从详情页HTML中解析磁力链接
func (p *ClxiongPlugin) parseMagnetLinksFromDetail(html string) []model.Link {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 解析详情页HTML失败: %v", err)
}
return nil
}
var links []model.Link
// 查找磁力链接
doc.Find(".mv_down a[href^='magnet:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
// 获取文件名(链接文本)
fileName := strings.TrimSpace(s.Text())
link := model.Link{
URL: href,
Type: "magnet",
}
// 如果文件名包含大小信息可以存储在Password字段中作为备注
if fileName != "" && strings.Contains(fileName, "[") {
link.Password = fileName // 临时存储文件名和大小信息
}
links = append(links, link)
if p.debugMode {
log.Printf("[CLXIONG] 找到磁力链接: %s %s", fileName, href)
}
}
})
if p.debugMode {
log.Printf("[CLXIONG] 详情页共找到 %d 个磁力链接", len(links))
}
return links
}
// generateUniqueID 生成唯一ID
func (p *ClxiongPlugin) generateUniqueID(detailPath string) string {
// 从路径中提取ID如 "/drama/4466.html" -> "4466"
re := regexp.MustCompile(`/(?:drama|movie)/(\d+)\.html`)
matches := re.FindStringSubmatch(detailPath)
if len(matches) > 1 {
return fmt.Sprintf("clxiong-%s", matches[1])
}
// 备用方案:使用完整路径生成哈希
hash := 0
for _, char := range detailPath {
hash = hash*31 + int(char)
}
if hash < 0 {
hash = -hash
}
return fmt.Sprintf("clxiong-%d", hash)
}

View File

@@ -0,0 +1,168 @@
# 磁力熊(CiLiXiong) HTML结构分析文档
## 网站信息
- **域名**: `www.cilixiong.org`
- **名称**: 磁力熊
- **类型**: 影视磁力链接搜索网站
- **特点**: 两步式搜索流程需要先POST获取searchid再GET搜索结果
## 搜索流程分析
### 第一步:提交搜索请求
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/index.php`
- **方法**: POST
- **Content-Type**: `application/x-www-form-urlencoded`
- **Referer**: `https://www.cilixiong.org/`
#### POST参数
```
classid=1%2C2&show=title&tempid=1&keyboard={URL编码的关键词}
```
参数说明:
- `classid=1,2` - 搜索分类1=电影2=剧集)
- `show=title` - 搜索字段
- `tempid=1` - 模板ID
- `keyboard` - 搜索关键词需URL编码
#### 响应处理
- **状态码**: 302重定向
- **关键信息**: 从响应头`Location`字段获取searchid
- **格式**: `result/?searchid=7549`
### 第二步:获取搜索结果
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/result/?searchid={searchid}`
- **方法**: GET
- **Referer**: `https://www.cilixiong.org/`
## 搜索结果页面结构
### 页面布局
- **容器**: `.container`
- **结果提示**: `.text-white.py-3` - 显示"找到 X 条符合搜索条件"
- **结果网格**: `.row.row-cols-2.row-cols-lg-4.align-items-stretch.g-4.py-2`
### 单个结果项结构
```html
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html">
<div class="card-img" style="background-image: url('海报图片URL');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">影片标题</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div>
</a>
</div>
</div>
```
### 数据提取选择器
#### 结果列表
- **选择器**: `.row.row-cols-2.row-cols-lg-4 .col`
- **排除**: 空白或无效的卡片
#### 单项数据提取
1. **详情链接**: `.col a[href*="/drama/"]``.col a[href*="/movie/"]`
2. **标题**: `.col h2.h4`
3. **评分**: `.col .rank`
4. **年份**: `.col .small`最后一个li元素
5. **海报**: `.col .card-img[style*="background-image"]` - 从style属性提取url
#### 链接格式
- 电影:`/movie/ID.html`
- 剧集:`/drama/ID.html`
- 需补全为绝对URL`https://www.cilixiong.org/drama/ID.html`
## 详情页面结构
### 基本信息区域
```html
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>影片标题</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名:英文名称</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:演员列表</p>
</div>
```
### 磁力链接区域
```html
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">影片名磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3">
<a href="magnet:?xt=urn:btih:HASH">文件名.mkv[文件大小]</a>
<a class="ms-3 text-muted small" href="/magnet.php?url=..." target="_blank">详情</a>
</div>
</div>
</div>
```
### 磁力链接提取
- **容器**: `.mv_down .container`
- **链接项**: `.border-bottom`
- **磁力链接**: `a[href^="magnet:"]`
- **文件名**: 链接的文本内容
- **大小信息**: 通常包含在文件名的方括号中
## 错误处理
### 常见问题
1. **搜索无结果**: 页面会显示"找到 0 条符合搜索条件"
2. **searchid失效**: 可能需要重新发起搜索请求
3. **详情页无磁力链接**: 某些内容可能暂时无下载资源
### 限流检测
- **状态码**: 检测429或403状态码
- **页面内容**: 检测是否包含"访问频繁"等提示
## 实现要点
### 请求头设置
```http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded (POST)
Referer: https://www.cilixiong.org/
```
### Cookie处理
- 网站可能需要维持会话状态
- 建议在客户端中启用Cookie存储
### 搜索策略
1. **首次搜索**: POST提交 → 解析Location → GET结果页
2. **结果解析**: 提取基本信息,构建搜索结果
3. **详情获取**: 可选,异步获取磁力链接
### 数据字段映射
- **Title**: 影片中文标题
- **Content**: 评分、年份、类型等信息组合
- **UniqueID**: 使用详情页URL的ID部分
- **Links**: 磁力链接数组
- **Tags**: 影片类型标签
## 技术注意事项
### URL编码
- 搜索关键词必须进行URL编码
- 中文字符使用UTF-8编码
### 重定向处理
- POST请求会返回302重定向
- 需要从响应头提取Location信息
- 不要自动跟随重定向,需要手动解析
### 异步处理
- 搜索结果可以先返回基本信息
- 磁力链接通过异步请求详情页获取
- 设置合理的并发限制和超时时间

645
plugin/clxiong/clxiong.go Normal file
View File

@@ -0,0 +1,645 @@
package clxiong
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://www.cilixiong.org"
SearchURL = "https://www.cilixiong.org/e/search/index.php"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxRetries = 3
RetryDelay = 2 * time.Second
MaxResults = 30
)
// DetailPageInfo 详情页信息结构体
type DetailPageInfo struct {
MagnetLinks []model.Link
UpdateTime time.Time
Title string
FileNames []string // 所有文件的名称,与磁力链接对应
}
// ClxiongPlugin 磁力熊插件
type ClxiongPlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
}
func init() {
p := &ClxiongPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("clxiong", 2, true),
debugMode: false, // 开启调试模式检查磁力链接提取问题
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *ClxiongPlugin) 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 *ClxiongPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (*model.PluginSearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 开始搜索: %s", keyword)
}
// 第一步POST搜索获取searchid
searchID, err := p.getSearchID(keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取searchid失败: %v", err)
}
return nil, fmt.Errorf("获取searchid失败: %v", err)
}
// 第二步GET搜索结果
results, err := p.getSearchResults(searchID, keyword)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 获取搜索结果失败: %v", err)
}
return nil, err
}
// 第三步:同步获取详情页磁力链接
results = p.fetchDetailLinksSync(results)
if p.debugMode {
log.Printf("[CLXIONG] 搜索完成,获得 %d 个结果", len(results))
}
// 应用关键词过滤
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return &model.PluginSearchResult{
Results: filteredResults,
IsFinal: true,
Timestamp: time.Now(),
Source: p.Name(),
Message: fmt.Sprintf("找到 %d 个结果", len(filteredResults)),
}, nil
}
// getSearchID 第一步POST搜索获取searchid
func (p *ClxiongPlugin) getSearchID(keyword string) (string, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取searchid...")
}
client := &http.Client{
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 不自动跟随重定向,我们需要手动处理
return http.ErrUseLastResponse
},
}
// 准备POST数据
formData := url.Values{}
formData.Set("classid", "1,2") // 1=电影2=剧集
formData.Set("show", "title") // 搜索字段
formData.Set("tempid", "1") // 模板ID
formData.Set("keyboard", keyword) // 搜索关键词
req, err := http.NewRequest("POST", SearchURL, strings.NewReader(formData.Encode()))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && (resp.StatusCode == 302 || resp.StatusCode == 301) {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return "", lastErr
}
defer resp.Body.Close()
// 检查重定向响应
if resp.StatusCode != 302 && resp.StatusCode != 301 {
return "", fmt.Errorf("期望302重定向但得到状态码: %d", resp.StatusCode)
}
// 从Location头部提取searchid
location := resp.Header.Get("Location")
if location == "" {
return "", fmt.Errorf("重定向响应中没有Location头部")
}
// 解析searchid
searchID := p.extractSearchIDFromLocation(location)
if searchID == "" {
return "", fmt.Errorf("无法从Location中提取searchid: %s", location)
}
if p.debugMode {
log.Printf("[CLXIONG] 获取到searchid: %s", searchID)
}
return searchID, nil
}
// extractSearchIDFromLocation 从Location头部提取searchid
func (p *ClxiongPlugin) extractSearchIDFromLocation(location string) string {
// location格式: "result/?searchid=7549"
re := regexp.MustCompile(`searchid=(\d+)`)
matches := re.FindStringSubmatch(location)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// getSearchResults 第二步GET搜索结果
func (p *ClxiongPlugin) getSearchResults(searchID, keyword string) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取搜索结果searchid: %s", searchID)
}
// 构建结果页URL
resultURL := fmt.Sprintf("%s/e/search/result/?searchid=%s", BaseURL, searchID)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", resultURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return nil, lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("搜索结果请求失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return p.parseSearchResults(string(body))
}
// parseSearchResults 解析搜索结果页面
func (p *ClxiongPlugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
// 查找搜索结果项
doc.Find(".row.row-cols-2.row-cols-lg-4 .col").Each(func(i int, s *goquery.Selection) {
if i >= MaxResults {
return // 限制结果数量
}
// 提取详情页链接
linkEl := s.Find("a[href*='/drama/'], a[href*='/movie/']")
if linkEl.Length() == 0 {
return // 跳过无链接的项
}
detailPath, exists := linkEl.Attr("href")
if !exists || detailPath == "" {
return
}
// 构建完整的详情页URL
detailURL := BaseURL + detailPath
// 提取标题
title := strings.TrimSpace(linkEl.Find("h2.h4").Text())
if title == "" {
return // 跳过无标题的项
}
// 提取评分
rating := strings.TrimSpace(s.Find(".rank").Text())
// 提取年份
year := strings.TrimSpace(s.Find(".small").Last().Text())
// 提取海报图片
poster := ""
cardImg := s.Find(".card-img")
if cardImg.Length() > 0 {
if style, exists := cardImg.Attr("style"); exists {
poster = p.extractImageFromStyle(style)
}
}
// 构建内容信息
var contentParts []string
if rating != "" {
contentParts = append(contentParts, "评分: "+rating)
}
if year != "" {
contentParts = append(contentParts, "年份: "+year)
}
if poster != "" {
contentParts = append(contentParts, "海报: "+poster)
}
// 添加详情页链接到content中供后续提取磁力链接使用
contentParts = append(contentParts, "详情页: "+detailURL)
content := strings.Join(contentParts, " | ")
// 生成唯一ID
uniqueID := p.generateUniqueID(detailPath)
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空
Tags: []string{"磁力链接", "影视"},
Datetime: time.Now(), // 搜索时间
Links: []model.Link{}, // 初始为空,后续异步获取
UniqueID: uniqueID,
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[CLXIONG] 解析到 %d 个搜索结果", len(results))
}
return results, nil
}
// extractImageFromStyle 从style属性中提取背景图片URL
func (p *ClxiongPlugin) extractImageFromStyle(style string) string {
// style格式: "background-image: url('https://i.nacloud.cc/2024/12154.webp');"
re := regexp.MustCompile(`url\(['"]?([^'"]+)['"]?\)`)
matches := re.FindStringSubmatch(style)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailLinksSync 同步获取详情页磁力链接
func (p *ClxiongPlugin) fetchDetailLinksSync(results []model.SearchResult) []model.SearchResult {
if len(results) == 0 {
return results
}
if p.debugMode {
log.Printf("[CLXIONG] 开始同步获取 %d 个详情页的磁力链接", len(results))
}
// 使用WaitGroup确保所有请求完成后再返回
var wg sync.WaitGroup
var mu sync.Mutex // 保护results切片的互斥锁
var additionalResults []model.SearchResult // 存储额外创建的搜索结果
// 限制并发数,避免过多请求
semaphore := make(chan struct{}, 5) // 最多5个并发请求
for i := range results {
wg.Add(1)
go func(index int) {
defer wg.Done()
// 获取信号量
semaphore <- struct{}{}
defer func() { <-semaphore }()
detailURL := p.extractDetailURLFromContent(results[index].Content)
if detailURL != "" {
detailInfo := p.fetchDetailPageInfo(detailURL, results[index].Title)
if detailInfo != nil && len(detailInfo.MagnetLinks) > 0 {
// 为每个磁力链接创建独立的搜索结果这样每个链接都有自己的note
baseResult := results[index]
// 第一个链接更新原结果
if len(detailInfo.FileNames) > 0 {
results[index].Title = fmt.Sprintf("%s-%s", baseResult.Title, detailInfo.FileNames[0])
}
results[index].Links = []model.Link{detailInfo.MagnetLinks[0]}
if !detailInfo.UpdateTime.IsZero() {
results[index].Datetime = detailInfo.UpdateTime
}
// 其他链接创建新的搜索结果
var newResults []model.SearchResult
for i := 1; i < len(detailInfo.MagnetLinks); i++ {
newResult := model.SearchResult{
MessageID: fmt.Sprintf("%s-%d", baseResult.MessageID, i+1),
UniqueID: fmt.Sprintf("%s-%d", baseResult.UniqueID, i+1),
Channel: baseResult.Channel,
Content: baseResult.Content,
Tags: baseResult.Tags,
Images: baseResult.Images,
Links: []model.Link{detailInfo.MagnetLinks[i]},
}
// 设置独特的标题和时间
if i < len(detailInfo.FileNames) {
newResult.Title = fmt.Sprintf("%s-%s", baseResult.Title, detailInfo.FileNames[i])
} else {
newResult.Title = baseResult.Title
}
if !detailInfo.UpdateTime.IsZero() {
newResult.Datetime = detailInfo.UpdateTime
} else {
newResult.Datetime = baseResult.Datetime
}
newResults = append(newResults, newResult)
}
// 使用锁保护切片的修改
if len(newResults) > 0 {
mu.Lock()
additionalResults = append(additionalResults, newResults...)
mu.Unlock()
}
if p.debugMode {
log.Printf("[CLXIONG] 为结果 %d 获取到 %d 个磁力链接,创建了 %d 个搜索结果", index+1, len(detailInfo.MagnetLinks), len(detailInfo.MagnetLinks))
}
}
}
}(i)
}
// 等待所有goroutine完成
wg.Wait()
// 合并额外创建的搜索结果
results = append(results, additionalResults...)
if p.debugMode {
totalLinks := 0
for _, result := range results {
totalLinks += len(result.Links)
}
log.Printf("[CLXIONG] 所有磁力链接获取完成,共获得 %d 个磁力链接,总搜索结果 %d 个", totalLinks, len(results))
}
return results
}
// extractDetailURLFromContent 从content中提取详情页URL
func (p *ClxiongPlugin) extractDetailURLFromContent(content string) string {
// 查找"详情页: URL"模式
re := regexp.MustCompile(`详情页: (https?://[^\s|]+)`)
matches := re.FindStringSubmatch(content)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// fetchDetailPageInfo 获取详情页的完整信息
func (p *ClxiongPlugin) fetchDetailPageInfo(detailURL string, movieTitle string) *DetailPageInfo {
if p.debugMode {
log.Printf("[CLXIONG] 正在获取详情页信息: %s", detailURL)
}
client := &http.Client{Timeout: 20 * time.Second}
req, err := http.NewRequest("GET", detailURL, nil)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 创建详情页请求失败: %v", err)
}
return nil
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
resp, err := client.Do(req)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 详情页请求失败: %v", err)
}
return nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if p.debugMode {
log.Printf("[CLXIONG] 详情页HTTP状态错误: %d", resp.StatusCode)
}
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 读取详情页响应失败: %v", err)
}
return nil
}
return p.parseDetailPageInfo(string(body), movieTitle)
}
// parseDetailPageInfo 从详情页HTML中解析完整信息
func (p *ClxiongPlugin) parseDetailPageInfo(html string, movieTitle string) *DetailPageInfo {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
if p.debugMode {
log.Printf("[CLXIONG] 解析详情页HTML失败: %v", err)
}
return nil
}
detailInfo := &DetailPageInfo{
Title: movieTitle,
}
// 解析更新时间
detailInfo.UpdateTime = p.parseUpdateTimeFromDetail(doc)
// 解析磁力链接
magnetLinks, fileNames := p.parseMagnetLinksFromDetailDoc(doc, movieTitle)
detailInfo.MagnetLinks = magnetLinks
detailInfo.FileNames = fileNames
if p.debugMode {
log.Printf("[CLXIONG] 详情页解析完成: 磁力链接 %d 个,更新时间: %v",
len(detailInfo.MagnetLinks), detailInfo.UpdateTime)
}
return detailInfo
}
// parseUpdateTimeFromDetail 从详情页解析更新时间
func (p *ClxiongPlugin) parseUpdateTimeFromDetail(doc *goquery.Document) time.Time {
// 查找"最后更新于2025-08-16"这样的文本
var updateTime time.Time
doc.Find(".mv_detail p").Each(func(i int, s *goquery.Selection) {
text := strings.TrimSpace(s.Text())
if strings.Contains(text, "最后更新于:") {
// 提取日期部分
dateStr := strings.Replace(text, "最后更新于:", "", 1)
dateStr = strings.TrimSpace(dateStr)
// 解析日期,支持多种格式
layouts := []string{
"2006-01-02",
"2006-1-2",
"2006/01/02",
"2006/1/2",
}
for _, layout := range layouts {
if t, err := time.Parse(layout, dateStr); err == nil {
updateTime = t
if p.debugMode {
log.Printf("[CLXIONG] 解析到更新时间: %s -> %v", dateStr, updateTime)
}
return
}
}
if p.debugMode {
log.Printf("[CLXIONG] 无法解析更新时间: %s", dateStr)
}
}
})
return updateTime
}
// parseMagnetLinksFromDetailDoc 从详情页DOM解析磁力链接
func (p *ClxiongPlugin) parseMagnetLinksFromDetailDoc(doc *goquery.Document, movieTitle string) ([]model.Link, []string) {
var links []model.Link
var fileNames []string
if p.debugMode {
// 调试:检查是否找到磁力下载区域
mvDown := doc.Find(".mv_down")
log.Printf("[CLXIONG] 找到 .mv_down 区域数量: %d", mvDown.Length())
// 调试:检查磁力链接数量
magnetLinks := doc.Find(".mv_down a[href^='magnet:']")
log.Printf("[CLXIONG] 找到磁力链接数量: %d", magnetLinks.Length())
// 如果没找到,尝试其他可能的选择器
if magnetLinks.Length() == 0 {
allMagnetLinks := doc.Find("a[href^='magnet:']")
log.Printf("[CLXIONG] 页面总磁力链接数量: %d", allMagnetLinks.Length())
}
}
// 查找磁力链接
doc.Find(".mv_down a[href^='magnet:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
// 获取文件名(链接文本)
fileName := strings.TrimSpace(s.Text())
link := model.Link{
URL: href,
Type: "magnet",
}
// 磁力链接密码字段设置为空(按用户要求)
link.Password = ""
links = append(links, link)
fileNames = append(fileNames, fileName)
if p.debugMode {
log.Printf("[CLXIONG] 找到磁力链接: %s", fileName)
}
}
})
if p.debugMode {
log.Printf("[CLXIONG] 详情页共找到 %d 个磁力链接", len(links))
}
return links, fileNames
}
// generateUniqueID 生成唯一ID
func (p *ClxiongPlugin) generateUniqueID(detailPath string) string {
// 从路径中提取ID如 "/drama/4466.html" -> "4466"
re := regexp.MustCompile(`/(?:drama|movie)/(\d+)\.html`)
matches := re.FindStringSubmatch(detailPath)
if len(matches) > 1 {
return fmt.Sprintf("clxiong-%s", matches[1])
}
// 备用方案:使用完整路径生成哈希
hash := 0
for _, char := range detailPath {
hash = hash*31 + int(char)
}
if hash < 0 {
hash = -hash
}
return fmt.Sprintf("clxiong-%d", hash)
}

View File

@@ -0,0 +1,168 @@
# 磁力熊(CiLiXiong) HTML结构分析文档
## 网站信息
- **域名**: `www.cilixiong.org`
- **名称**: 磁力熊
- **类型**: 影视磁力链接搜索网站
- **特点**: 两步式搜索流程需要先POST获取searchid再GET搜索结果
## 搜索流程分析
### 第一步:提交搜索请求
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/index.php`
- **方法**: POST
- **Content-Type**: `application/x-www-form-urlencoded`
- **Referer**: `https://www.cilixiong.org/`
#### POST参数
```
classid=1%2C2&show=title&tempid=1&keyboard={URL编码的关键词}
```
参数说明:
- `classid=1,2` - 搜索分类1=电影2=剧集)
- `show=title` - 搜索字段
- `tempid=1` - 模板ID
- `keyboard` - 搜索关键词需URL编码
#### 响应处理
- **状态码**: 302重定向
- **关键信息**: 从响应头`Location`字段获取searchid
- **格式**: `result/?searchid=7549`
### 第二步:获取搜索结果
#### 请求信息
- **URL**: `https://www.cilixiong.org/e/search/result/?searchid={searchid}`
- **方法**: GET
- **Referer**: `https://www.cilixiong.org/`
## 搜索结果页面结构
### 页面布局
- **容器**: `.container`
- **结果提示**: `.text-white.py-3` - 显示"找到 X 条符合搜索条件"
- **结果网格**: `.row.row-cols-2.row-cols-lg-4.align-items-stretch.g-4.py-2`
### 单个结果项结构
```html
<div class="col">
<div class="card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg position-relative">
<a href="/drama/4466.html">
<div class="card-img" style="background-image: url('海报图片URL');"><span></span></div>
<div class="card-body position-absolute d-flex w-100 flex-column text-white">
<h2 class="pt-5 lh-1 pb-2 h4">影片标题</h2>
<ul class="d-flex list-unstyled mb-0">
<li class="me-auto"><span class="rank bg-success p-1">8.9</span></li>
<li class="d-flex align-items-center small">2025</li>
</ul>
</div>
</a>
</div>
</div>
```
### 数据提取选择器
#### 结果列表
- **选择器**: `.row.row-cols-2.row-cols-lg-4 .col`
- **排除**: 空白或无效的卡片
#### 单项数据提取
1. **详情链接**: `.col a[href*="/drama/"]``.col a[href*="/movie/"]`
2. **标题**: `.col h2.h4`
3. **评分**: `.col .rank`
4. **年份**: `.col .small`最后一个li元素
5. **海报**: `.col .card-img[style*="background-image"]` - 从style属性提取url
#### 链接格式
- 电影:`/movie/ID.html`
- 剧集:`/drama/ID.html`
- 需补全为绝对URL`https://www.cilixiong.org/drama/ID.html`
## 详情页面结构
### 基本信息区域
```html
<div class="mv_detail lh-2 px-3">
<p class="mb-2"><h1>影片标题</h1></p>
<p class="mb-2">豆瓣评分: <span class="db_rank">8.9</span></p>
<p class="mb-2">又名:英文名称</p>
<p class="mb-2">上映日期2025-05-25(美国)</p>
<p class="mb-2">类型:|喜剧|冒险|科幻|动画|</p>
<p class="mb-2">单集片长22分钟</p>
<p class="mb-2">上映地区:美国</p>
<p class="mb-2">主演:演员列表</p>
</div>
```
### 磁力链接区域
```html
<div class="mv_down p-5 pb-3 rounded-4 text-center">
<h2 class="h6 pb-3">影片名磁力下载地址</h2>
<div class="container">
<div class="border-bottom pt-2 pb-4 mb-3">
<a href="magnet:?xt=urn:btih:HASH">文件名.mkv[文件大小]</a>
<a class="ms-3 text-muted small" href="/magnet.php?url=..." target="_blank">详情</a>
</div>
</div>
</div>
```
### 磁力链接提取
- **容器**: `.mv_down .container`
- **链接项**: `.border-bottom`
- **磁力链接**: `a[href^="magnet:"]`
- **文件名**: 链接的文本内容
- **大小信息**: 通常包含在文件名的方括号中
## 错误处理
### 常见问题
1. **搜索无结果**: 页面会显示"找到 0 条符合搜索条件"
2. **searchid失效**: 可能需要重新发起搜索请求
3. **详情页无磁力链接**: 某些内容可能暂时无下载资源
### 限流检测
- **状态码**: 检测429或403状态码
- **页面内容**: 检测是否包含"访问频繁"等提示
## 实现要点
### 请求头设置
```http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded (POST)
Referer: https://www.cilixiong.org/
```
### Cookie处理
- 网站可能需要维持会话状态
- 建议在客户端中启用Cookie存储
### 搜索策略
1. **首次搜索**: POST提交 → 解析Location → GET结果页
2. **结果解析**: 提取基本信息,构建搜索结果
3. **详情获取**: 可选,异步获取磁力链接
### 数据字段映射
- **Title**: 影片中文标题
- **Content**: 评分、年份、类型等信息组合
- **UniqueID**: 使用详情页URL的ID部分
- **Links**: 磁力链接数组
- **Tags**: 影片类型标签
## 技术注意事项
### URL编码
- 搜索关键词必须进行URL编码
- 中文字符使用UTF-8编码
### 重定向处理
- POST请求会返回302重定向
- 需要从响应头提取Location信息
- 不要自动跟随重定向,需要手动解析
### 异步处理
- 搜索结果可以先返回基本信息
- 磁力链接通过异步请求详情页获取
- 设置合理的并发限制和超时时间

View File

@@ -27,7 +27,7 @@ var (
xunleiLinkRegex = regexp.MustCompile(`https?://pan\.xunlei\.com/s/[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]+`)
mobileLinkRegex = regexp.MustCompile(`https?://(caiyun\.feixin\.10086\.cn|caiyun\.139\.com|yun\.139\.com|cloud\.139\.com|pan\.139\.com)/.*`)
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]+`)
@@ -350,7 +350,7 @@ func (p *CygPlugin) determineCloudType(name string) string {
return "tianyi"
case "115", "115网盘":
return "115"
case "移动云盘", "移动", "mobile", "和彩云":
case "移动云盘", "移动", "mobile", "和彩云", "139云盘", "139", "中国移动云盘":
return "mobile"
case "微云", "腾讯微云", "weiyun":
return "weiyun"

View File

@@ -473,9 +473,9 @@ if err != nil {
```
**处理策略**
- 📝 详细错误日志
- 🔄 降级处理机制
- 📊 监控告警
- 详细错误日志
- 降级处理机制
- 监控告警
#### 3. 数据错误
@@ -880,7 +880,7 @@ log.Debug("缓存命中", "key", cacheKey, "type", "detail_page")
---
## 📝 开发指南
## 开发指南
### 代码规范

1008
plugin/javdb/javdb.go Normal file

File diff suppressed because it is too large Load Diff

BIN
plugin/u3c3/.DS_Store vendored Normal file

Binary file not shown.

422
plugin/u3c3/u3c3.go Normal file
View File

@@ -0,0 +1,422 @@
package u3c3
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://u3c3u3c3.u3c3u3c3u3c3.com"
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxRetries = 3
RetryDelay = 2 * time.Second
)
// U3c3Plugin U3C3插件
type U3c3Plugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
search2 string // 缓存的search2参数
lastSync time.Time
}
func init() {
p := &U3c3Plugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("u3c3", 5, true),
debugMode: false,
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *U3c3Plugin) 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 *U3c3Plugin) SearchWithResult(keyword string, ext map[string]interface{}) (*model.PluginSearchResult, error) {
if p.debugMode {
log.Printf("[U3C3] 开始搜索: %s", keyword)
}
// 第一步获取search2参数
search2, err := p.getSearch2Parameter()
if err != nil {
if p.debugMode {
log.Printf("[U3C3] 获取search2参数失败: %v", err)
}
return nil, fmt.Errorf("获取search2参数失败: %v", err)
}
// 第二步:执行搜索
results, err := p.doSearch(keyword, search2)
if err != nil {
if p.debugMode {
log.Printf("[U3C3] 搜索失败: %v", err)
}
return nil, err
}
if p.debugMode {
log.Printf("[U3C3] 搜索完成,获得 %d 个结果", len(results))
}
// 应用关键词过滤
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return &model.PluginSearchResult{
Results: filteredResults,
IsFinal: true,
Timestamp: time.Now(),
Source: p.Name(),
Message: fmt.Sprintf("找到 %d 个结果", len(filteredResults)),
}, nil
}
// getSearch2Parameter 获取search2参数
func (p *U3c3Plugin) getSearch2Parameter() (string, error) {
// 如果缓存有效1小时内直接返回
if p.search2 != "" && time.Since(p.lastSync) < time.Hour {
return p.search2, nil
}
if p.debugMode {
log.Printf("[U3C3] 正在获取search2参数...")
}
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("GET", BaseURL, nil)
if err != nil {
return "", err
}
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")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return "", lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码错误: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// 从JavaScript中提取search2参数
search2 := p.extractSearch2FromHTML(string(body))
if search2 == "" {
return "", fmt.Errorf("无法从首页提取search2参数")
}
// 缓存参数
p.search2 = search2
p.lastSync = time.Now()
if p.debugMode {
log.Printf("[U3C3] 获取到search2参数: %s", search2)
}
return search2, nil
}
// extractSearch2FromHTML 从HTML中提取search2参数
func (p *U3c3Plugin) extractSearch2FromHTML(html string) string {
// 按行处理,排除注释行
lines := strings.Split(html, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
// 跳过注释行
if strings.HasPrefix(line, "//") {
continue
}
// 查找包含nmefafej的行
if strings.Contains(line, "nmefafej") && strings.Contains(line, `"`) {
// 使用正则提取引号内的值
re := regexp.MustCompile(`var\s+nmefafej\s*=\s*"([^"]+)"`)
matches := re.FindStringSubmatch(line)
if len(matches) > 1 && len(matches[1]) > 5 {
if p.debugMode {
log.Printf("[U3C3] 提取到search2参数: %s (来自行: %s)", matches[1], line)
}
return matches[1]
}
// 备用方案:直接提取引号内容
start := strings.Index(line, `"`)
if start != -1 {
end := strings.Index(line[start+1:], `"`)
if end != -1 && end > 5 {
candidate := line[start+1 : start+1+end]
if len(candidate) > 5 {
if p.debugMode {
log.Printf("[U3C3] 备用方案提取search2: %s (来自行: %s)", candidate, line)
}
return candidate
}
}
}
}
}
if p.debugMode {
log.Printf("[U3C3] 未能找到search2参数")
}
return ""
}
// doSearch 执行搜索
func (p *U3c3Plugin) doSearch(keyword, search2 string) ([]model.SearchResult, error) {
// 构建搜索URL
encodedKeyword := url.QueryEscape(keyword)
searchURL := fmt.Sprintf("%s/?search2=%s&search=%s", BaseURL, search2, encodedKeyword)
if p.debugMode {
log.Printf("[U3C3] 搜索URL: %s", searchURL)
}
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("GET", searchURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
var resp *http.Response
var lastErr error
// 重试机制
for i := 0; i < MaxRetries; i++ {
resp, lastErr = client.Do(req)
if lastErr == nil && resp.StatusCode == 200 {
break
}
if resp != nil {
resp.Body.Close()
}
if i < MaxRetries-1 {
time.Sleep(RetryDelay)
}
}
if lastErr != nil {
return nil, lastErr
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("搜索请求失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return p.parseSearchResults(string(body))
}
// parseSearchResults 解析搜索结果
func (p *U3c3Plugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
// 查找搜索结果表格行
doc.Find("tbody tr.default").Each(func(i int, s *goquery.Selection) {
// 跳过广告行(通常包含置顶标识)
titleCell := s.Find("td:nth-child(2)")
titleText := titleCell.Text()
if strings.Contains(titleText, "[置顶]") {
return // 跳过置顶广告
}
// 提取标题和详情链接
titleLink := titleCell.Find("a")
title := strings.TrimSpace(titleLink.Text())
if title == "" {
return // 跳过空标题
}
// 清理标题中的HTML标签和特殊字符
title = p.cleanTitle(title)
// 提取详情页链接(可选,用于后续扩展)
detailURL, _ := titleLink.Attr("href")
if detailURL != "" && !strings.HasPrefix(detailURL, "http") {
detailURL = BaseURL + detailURL
}
// 提取链接信息
linkCell := s.Find("td:nth-child(3)")
var links []model.Link
// 磁力链接
linkCell.Find("a[href^='magnet:']").Each(func(j int, link *goquery.Selection) {
href, exists := link.Attr("href")
if exists && href != "" {
links = append(links, model.Link{
URL: href,
Type: "magnet",
})
}
})
// 种子文件链接
linkCell.Find("a[href$='.torrent']").Each(func(j int, link *goquery.Selection) {
href, exists := link.Attr("href")
if exists && href != "" {
if !strings.HasPrefix(href, "http") {
href = BaseURL + href
}
links = append(links, model.Link{
URL: href,
Type: "torrent",
})
}
})
// 提取文件大小
sizeText := strings.TrimSpace(s.Find("td:nth-child(4)").Text())
// 提取上传时间
dateText := strings.TrimSpace(s.Find("td:nth-child(5)").Text())
// 提取分类
categoryText := s.Find("td:nth-child(1) a").AttrOr("title", "")
// 构建内容信息
var contentParts []string
if categoryText != "" {
contentParts = append(contentParts, "分类: "+categoryText)
}
if sizeText != "" {
contentParts = append(contentParts, "大小: "+sizeText)
}
if dateText != "" {
contentParts = append(contentParts, "时间: "+dateText)
}
content := strings.Join(contentParts, " | ")
// 生成唯一ID
uniqueID := p.generateUniqueID(title, sizeText)
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空
Tags: []string{"种子", "磁力链接"},
Datetime: p.parseDateTime(dateText),
Links: links,
UniqueID: uniqueID,
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[U3C3] 解析到 %d 个搜索结果", len(results))
}
return results, nil
}
// cleanTitle 清理标题文本
func (p *U3c3Plugin) cleanTitle(title string) string {
// 移除HTML标签
title = regexp.MustCompile(`<[^>]*>`).ReplaceAllString(title, "")
// 移除多余的空白字符
title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ")
// 移除前后空白
title = strings.TrimSpace(title)
return title
}
// parseDateTime 解析日期时间
func (p *U3c3Plugin) parseDateTime(dateStr string) time.Time {
if dateStr == "" {
return time.Time{}
}
// 尝试解析常见的日期格式
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02",
"01-02 15:04",
}
for _, format := range formats {
if t, err := time.Parse(format, dateStr); err == nil {
return t
}
}
// 如果解析失败,返回零值
return time.Time{}
}
// generateUniqueID 生成唯一ID
func (p *U3c3Plugin) generateUniqueID(title, size string) string {
// 使用插件名、标题和大小生成唯一ID
source := fmt.Sprintf("%s-%s-%s", p.Name(), title, size)
// 简单的哈希处理(实际项目中可使用更复杂的哈希算法)
hash := 0
for _, char := range source {
hash = hash*31 + int(char)
}
if hash < 0 {
hash = -hash
}
return fmt.Sprintf("u3c3-%d", hash)
}

BIN
plugin/yuhuage/.DS_Store vendored Normal file

Binary file not shown.

415
plugin/yuhuage/yuhuage.go Normal file
View File

@@ -0,0 +1,415 @@
package yuhuage
import (
"context"
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/PuerkitoBio/goquery"
"pansou/model"
"pansou/plugin"
)
const (
BaseURL = "https://www.iyuhuage.fun"
SearchPath = "/search/"
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
MaxConcurrency = 5 // 详情页最大并发数
MaxRetryCount = 2 // 最大重试次数
)
// YuhuagePlugin 雨花阁插件
type YuhuagePlugin struct {
*plugin.BaseAsyncPlugin
debugMode bool
detailCache sync.Map // 缓存详情页结果
cacheTTL time.Duration
rateLimited int32 // 429限流标志位
}
func init() {
p := &YuhuagePlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPluginWithFilter("yuhuage", 3, true),
debugMode: false,
cacheTTL: 30 * time.Minute,
}
plugin.RegisterGlobalPlugin(p)
}
// Search 搜索接口实现
func (p *YuhuagePlugin) 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 *YuhuagePlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}
// searchImpl 搜索实现方法
func (p *YuhuagePlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[YUHUAGE] 开始搜索: %s", keyword)
}
// 检查限流状态
if atomic.LoadInt32(&p.rateLimited) == 1 {
if p.debugMode {
log.Printf("[YUHUAGE] 当前处于限流状态,跳过搜索")
}
return nil, fmt.Errorf("rate limited")
}
// 构建搜索URL
encodedQuery := url.QueryEscape(keyword)
searchURL := fmt.Sprintf("%s%s%s-%d-time.html", BaseURL, SearchPath, encodedQuery, 1)
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
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", 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("Connection", "keep-alive")
req.Header.Set("Referer", BaseURL+"/")
// 发送HTTP请求
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode == 429 {
atomic.StoreInt32(&p.rateLimited, 1)
go func() {
time.Sleep(60 * time.Second)
atomic.StoreInt32(&p.rateLimited, 0)
}()
return nil, fmt.Errorf("[%s] 请求被限流", p.Name())
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] HTTP错误: %d", p.Name(), resp.StatusCode)
}
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
// 解析搜索结果
results, err := p.parseSearchResults(string(body))
if err != nil {
return nil, err
}
if p.debugMode {
log.Printf("[YUHUAGE] 搜索完成,获得 %d 个结果", len(results))
}
// 关键词过滤
return plugin.FilterResultsByKeyword(results, keyword), nil
}
// parseSearchResults 解析搜索结果
func (p *YuhuagePlugin) parseSearchResults(html string) ([]model.SearchResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
var results []model.SearchResult
var detailURLs []string
// 提取搜索结果
doc.Find(".search-item.detail-width").Each(func(i int, s *goquery.Selection) {
title := strings.TrimSpace(p.cleanTitle(s.Find(".item-title h3 a").Text()))
detailHref, exists := s.Find(".item-title h3 a").Attr("href")
if !exists || title == "" {
return
}
detailURL := BaseURL + detailHref
detailURLs = append(detailURLs, detailURL)
// 提取基本信息
createTime := strings.TrimSpace(s.Find(".item-bar span:contains('创建时间') b").Text())
size := strings.TrimSpace(s.Find(".item-bar .cpill.blue-pill").Text())
fileCount := strings.TrimSpace(s.Find(".item-bar .cpill.yellow-pill").Text())
hot := strings.TrimSpace(s.Find(".item-bar span:contains('热度') b").Text())
lastDownload := strings.TrimSpace(s.Find(".item-bar span:contains('最近下载') b").Text())
// 构建内容描述
content := fmt.Sprintf("创建时间: %s | 大小: %s | 文件数: %s | 热度: %s",
createTime, size, fileCount, hot)
if lastDownload != "" {
content += fmt.Sprintf(" | 最近下载: %s", lastDownload)
}
result := model.SearchResult{
Title: title,
Content: content,
Channel: "", // 插件搜索结果必须为空字符串
Tags: []string{"磁力链接"},
Datetime: p.parseDateTime(createTime),
UniqueID: fmt.Sprintf("%s-%s", p.Name(), p.extractHashFromURL(detailURL)),
}
results = append(results, result)
})
if p.debugMode {
log.Printf("[YUHUAGE] 解析到 %d 个搜索结果,准备获取详情", len(results))
}
// 同步获取详情页链接
p.fetchDetailsSync(detailURLs, results)
return results, nil
}
// fetchDetailsSync 同步获取详情页信息
func (p *YuhuagePlugin) fetchDetailsSync(detailURLs []string, results []model.SearchResult) {
if len(detailURLs) == 0 {
return
}
semaphore := make(chan struct{}, MaxConcurrency)
var wg sync.WaitGroup
for i, detailURL := range detailURLs {
if i >= len(results) {
break
}
wg.Add(1)
go func(url string, result *model.SearchResult) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
links := p.fetchDetailLinks(url)
if len(links) > 0 {
result.Links = links
if p.debugMode {
log.Printf("[YUHUAGE] 为结果设置了 %d 个链接", len(links))
}
} else if p.debugMode {
log.Printf("[YUHUAGE] 详情页没有找到有效链接: %s", url)
}
}(detailURL, &results[i])
}
wg.Wait()
if p.debugMode {
log.Printf("[YUHUAGE] 详情页获取完成")
}
}
// fetchDetailLinks 获取详情页链接
func (p *YuhuagePlugin) fetchDetailLinks(detailURL string) []model.Link {
// 检查缓存
if cached, exists := p.detailCache.Load(detailURL); exists {
if links, ok := cached.([]model.Link); ok {
return links
}
}
client := &http.Client{Timeout: 15 * time.Second}
for retry := 0; retry <= MaxRetryCount; retry++ {
req, err := http.NewRequest("GET", detailURL, nil)
if err != nil {
continue
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Referer", BaseURL+"/")
resp, err := client.Do(req)
if err != nil {
if retry < MaxRetryCount {
time.Sleep(time.Duration(retry+1) * time.Second)
continue
}
break
}
if resp.StatusCode != 200 {
resp.Body.Close()
if retry < MaxRetryCount {
time.Sleep(time.Duration(retry+1) * time.Second)
continue
}
break
}
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
if retry < MaxRetryCount {
time.Sleep(time.Duration(retry+1) * time.Second)
continue
}
break
}
links := p.parseDetailLinks(string(body))
// 缓存结果
if len(links) > 0 {
p.detailCache.Store(detailURL, links)
// 设置缓存过期
go func() {
time.Sleep(p.cacheTTL)
p.detailCache.Delete(detailURL)
}()
}
return links
}
return nil
}
// parseDetailLinks 解析详情页链接
func (p *YuhuagePlugin) parseDetailLinks(html string) []model.Link {
var links []model.Link
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return links
}
// 提取磁力链接
doc.Find("a.download[href^='magnet:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
if p.debugMode {
log.Printf("[YUHUAGE] 找到磁力链接: %s", href)
}
links = append(links, model.Link{
URL: href,
Type: "magnet",
})
}
})
// 提取迅雷链接
doc.Find("a.download[href^='thunder:']").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
if p.debugMode {
log.Printf("[YUHUAGE] 找到迅雷链接: %s", href)
}
links = append(links, model.Link{
URL: href,
Type: "thunder",
})
}
})
if p.debugMode && len(links) > 0 {
log.Printf("[YUHUAGE] 从详情页解析到 %d 个链接", len(links))
}
return links
}
// extractHashFromURL 从URL中提取哈希ID
func (p *YuhuagePlugin) extractHashFromURL(detailURL string) string {
re := regexp.MustCompile(`/hash/(\d+)\.html`)
matches := re.FindStringSubmatch(detailURL)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// cleanTitle 清理标题
func (p *YuhuagePlugin) cleanTitle(title string) string {
title = strings.TrimSpace(title)
// 移除HTML标签如<b>标签)
re := regexp.MustCompile(`<[^>]*>`)
title = re.ReplaceAllString(title, "")
// 移除多余的空格
re = regexp.MustCompile(`\s+`)
title = re.ReplaceAllString(title, " ")
return strings.TrimSpace(title)
}
// parseDateTime 解析时间字符串
func (p *YuhuagePlugin) parseDateTime(timeStr string) time.Time {
if timeStr == "" {
return time.Time{}
}
// 尝试不同的时间格式
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02",
"2006/01/02 15:04:05",
"2006/01/02",
}
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t
}
}
return time.Time{}
}
// doRequestWithRetry 带重试机制的HTTP请求
func (p *YuhuagePlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
maxRetries := 3
var lastErr error
for i := 0; i < maxRetries; i++ {
if i > 0 {
// 指数退避重试
backoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond
time.Sleep(backoff)
}
// 克隆请求避免并发问题
reqClone := req.Clone(req.Context())
resp, err := client.Do(reqClone)
if err == nil && resp.StatusCode == 200 {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
lastErr = err
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
}

View File

@@ -268,9 +268,9 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("📝 [异步插件 %s] 初始缓存创建: %s(关键词:%s) | 结果数: %d\n", pluginName, displayKey, keyword, len(newResults))
fmt.Printf("[异步插件 %s] 初始缓存创建: %s(关键词:%s) | 结果数: %d\n", pluginName, displayKey, keyword, len(newResults))
} else {
fmt.Printf("📝 [异步插件 %s] 初始缓存创建: %s | 结果数: %d\n", pluginName, key, len(newResults))
fmt.Printf("[异步插件 %s] 初始缓存创建: %s | 结果数: %d\n", pluginName, key, len(newResults))
}
}
}
@@ -1293,7 +1293,7 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
// 🔥 修复:使用同步方式确保数据写入磁盘
enhancedTwoLevelCache.SetBothLevels(key, data, ttl)
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
fmt.Printf("📝 [主程序] 缓存更新完成: %s | 结果数: %d",
fmt.Printf("[主程序] 缓存更新完成: %s | 结果数: %d",
key, len(res))
}
}

View File

@@ -248,7 +248,7 @@ func NewBufferStatusMonitor() *BufferStatusMonitor {
alertThresholds: &AlertThresholds{
MemoryUsageWarning: 50 * 1024 * 1024, // 50MB
MemoryUsageCritical: 100 * 1024 * 1024, // 100MB
BufferCountWarning: 30,
BufferCountWarning: 40,
BufferCountCritical: 50,
OperationQueueWarning: 500,
OperationQueueCritical: 1000,
@@ -559,7 +559,7 @@ func (b *BufferStatusMonitor) triggerAlert(component, level, message string) {
b.alertManager.mutex.Unlock()
// 输出报警日志
fmt.Printf("🚨 [报警] %s - %s: %s\n", level, component, message)
// fmt.Printf("🚨 [报警] %s - %s: %s\n", level, component, message)
}
// updatePredictions 更新预测