mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-24 19:12:50 +08:00
新增插件clxiong
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
||||
@@ -384,6 +384,10 @@ GET /api/search?kw=速度与激情&channels=tgsearchers3,xxx&conc=2&refresh=true
|
||||
}
|
||||
```
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。
|
||||
|
||||
## ⭐ Star 历史
|
||||
|
||||
[](https://star-history.com/#fish2018/pansou&Date)
|
||||
|
||||
4
main.go
4
main.go
@@ -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
301
plugin/clxiong copy 2/1.txt
Normal 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>
|
||||
506
plugin/clxiong copy 2/clxiong.go
Normal file
506
plugin/clxiong copy 2/clxiong.go
Normal 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)
|
||||
}
|
||||
168
plugin/clxiong copy 2/html结构分析.md
Normal file
168
plugin/clxiong copy 2/html结构分析.md
Normal 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
301
plugin/clxiong copy 3/1.txt
Normal 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>
|
||||
587
plugin/clxiong copy 3/clxiong.go
Normal file
587
plugin/clxiong copy 3/clxiong.go
Normal 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)
|
||||
}
|
||||
168
plugin/clxiong copy 3/html结构分析.md
Normal file
168
plugin/clxiong copy 3/html结构分析.md
Normal 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
301
plugin/clxiong copy/1.txt
Normal 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>
|
||||
483
plugin/clxiong copy/clxiong.go
Normal file
483
plugin/clxiong copy/clxiong.go
Normal 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)
|
||||
}
|
||||
168
plugin/clxiong copy/html结构分析.md
Normal file
168
plugin/clxiong copy/html结构分析.md
Normal 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
645
plugin/clxiong/clxiong.go
Normal 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)
|
||||
}
|
||||
168
plugin/clxiong/html结构分析.md
Normal file
168
plugin/clxiong/html结构分析.md
Normal 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信息
|
||||
- 不要自动跟随重定向,需要手动解析
|
||||
|
||||
### 异步处理
|
||||
- 搜索结果可以先返回基本信息
|
||||
- 磁力链接通过异步请求详情页获取
|
||||
- 设置合理的并发限制和超时时间
|
||||
@@ -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"
|
||||
|
||||
@@ -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
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
BIN
plugin/u3c3/.DS_Store
vendored
Normal file
Binary file not shown.
422
plugin/u3c3/u3c3.go
Normal file
422
plugin/u3c3/u3c3.go
Normal 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
BIN
plugin/yuhuage/.DS_Store
vendored
Normal file
Binary file not shown.
415
plugin/yuhuage/yuhuage.go
Normal file
415
plugin/yuhuage/yuhuage.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
4
util/cache/buffer_status_monitor.go
vendored
4
util/cache/buffer_status_monitor.go
vendored
@@ -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 更新预测
|
||||
|
||||
Reference in New Issue
Block a user