Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-26 14:40:30 +00:00
parent e6cee0f96e
commit 3ec7ec4d08
41 changed files with 11118 additions and 4513 deletions
+12
View File
@@ -808,6 +808,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/采集聚合成人.py"
},
{
"key": "猫咪TV",
"name": "🐬猫咪TV.py|🔞[成人]",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/猫咪TV.py"
},
{
"key": "BadNews",
"name": "🐬BadNews.py|🔞[成人](挂梯)",
@@ -928,6 +934,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/8X8X.py"
},
{
"key": "Jable",
"name": "🐬Jable.py|🔞[成人]",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/Jable.py"
},
{
"key": "Xvideos",
"name": "🐬Xvideos.py|🔞[成人]",
+12
View File
@@ -551,6 +551,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/采集聚合成人.py"
},
{
"key": "猫咪TV",
"name": "🐬猫咪TV.py|🔞[成人]",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/猫咪TV.py"
},
{
"key": "樱花传媒",
"name": "🐬樱花传媒.py|🔞[成人]",
@@ -671,6 +677,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/8X8X.py"
},
{
"key": "Jable",
"name": "🐬Jable.py|🔞[成人]",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/Jable.py"
},
{
"key": "Xvideos",
"name": "🐬Xvideos.py|🔞[成人]",
+12
View File
@@ -747,6 +747,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/蜜桃视频.py"
},
{
"key": "猫咪TV",
"name": "🐬猫咪TV.py|🔞",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/猫咪TV.py"
},
{
"key": "樱花传媒",
"name": "🐬樱花传媒.py|🔞",
@@ -867,6 +873,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/8X8X.py"
},
{
"key": "Jable",
"name": "🐬Jable.py|🔞",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/Jable.py"
},
{
"key": "Xvideos",
"name": "🐬Xvideos.py|🔞",
+13 -1
View File
@@ -1,5 +1,5 @@
{
"spider": "./tvbox.jar",
"spider": "",
"logo": "https://img.freepik.com/free-vector/cute-dolphin-swimming-cartoon-vector-icon-illustration-animal-nature-icon-isolated-flat-vector_138676-12582.jpg?semt=ais_hybrid&w=740&q=80",
"wallpaper":"http://tool.teyonds.com/api",
"warningText": "注意:如果别人倒卖海豚影视接口收费的都是骗子,没有qq群微信群,只有tg官方交流群 TG@hshsjk",
@@ -580,6 +580,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/蜜桃视频.py"
},
{
"key": "猫咪TV",
"name": "🐬猫咪TV.py|🔞",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/猫咪TV.py"
},
{
"key": "樱花传媒",
"name": "🐬樱花传媒.py|🔞",
@@ -700,6 +706,12 @@
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/8X8X.py"
},
{
"key": "Jable",
"name": "🐬Jable.py|🔞",
"type": 3,
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/Jable.py"
},
{
"key": "Xvideos",
"name": "🐬Xvideos.py|🔞",
+161
View File
@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
import re
import urllib.parse
import requests
try:
from base.spider import Spider as BaseSpider
except ImportError:
class BaseSpider:
pass
class Spider(BaseSpider):
BASE_URL = "https://jable.sbs"
FALLBACK_URLS = ["https://jable.sbs", "https://jable.tv"]
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Referer": BASE_URL + "/",
}
def __init__(self):
super().__init__()
self.name = "JableTV"
self.session = requests.Session()
self.session.headers.update(self.HEADERS)
self._class_cache = None
def init(self, extend="{}"):
return None
def getName(self):
return self.name
def homeContent(self, filter):
html = self._get(self.BASE_URL + "/latest-updates/")
return {"class": self._classes(), "filters": {}, "list": self._parse_list(html), "parse": 0, "jx": 0}
def homeVideoContent(self):
return {"list": self._parse_list(self._get(self.BASE_URL + "/latest-updates/"))}
def categoryContent(self, tid, pg, filter, extend):
page = self._to_int(pg, 1)
path = str(tid or "latest-updates").strip("/")
url = self.BASE_URL + "/" + path + "/" if page <= 1 else self.BASE_URL + "/" + path + "/" + str(page) + "/"
data = self._parse_list(self._get(url))
return {"page": page, "pagecount": page if len(data) < 10 else page + 1, "limit": 24, "total": 99999, "list": data, "parse": 0, "jx": 0}
def detailContent(self, ids):
result = {"list": [], "parse": 0, "jx": 0}
if not ids:
return result
url = self._fix_url(ids[0] if str(ids[0]).startswith("http") else self.BASE_URL + "/videos/" + str(ids[0]).strip("/") + "/")
html = self._get(url)
name = self._clean(self._match(html, r'<h4[^>]*>(.*?)</h4>') or self._match(html, r'<meta[^>]+property=["\']og:title["\'][^>]+content=["\']([^"\']+)') or self._match(html, r'<title>(.*?)</title>').split("-")[0])
pic = self._match(html, r'<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)') or self._match(html, r'<video[^>]+poster=["\']([^"\']+)') or self._match(html, r'<img[^>]+(?:data-src|src)=["\']([^"\']+)')
tags = ",".join([self._clean(x) for x in re.findall(r'<a[^>]+href=["\'][^"\']*/tags/[^"\']+["\'][^>]*>(.*?)</a>', html, re.S)])
remarks = self._clean(" ".join(re.findall(r'<h6[^>]*>(.*?)</h6>', html, re.S)[:3]))
content = self._clean(self._match(html, r'<div[^>]+class=["\'][^"\']*(?:description|info|text)[^"\']*["\'][^>]*>(.*?)</div>') or remarks or name)
m3u8 = self._m3u8(html)
result["list"].append({"vod_id": url, "vod_name": name, "vod_pic": urllib.parse.urljoin(self.BASE_URL, pic), "type_name": tags, "vod_year": "", "vod_area": "", "vod_remarks": remarks, "vod_actor": tags, "vod_director": "", "vod_content": content, "vod_play_from": "Jable", "vod_play_url": "正片$" + (m3u8 or url)})
return result
def searchContent(self, key, quick, pg="1"):
page = self._to_int(pg, 1)
q = urllib.parse.quote(str(key))
url = self.BASE_URL + "/search/" + q + "/" if page <= 1 else self.BASE_URL + "/search/" + q + "/" + str(page) + "/"
data = self._parse_list(self._get(url))
return {"page": page, "pagecount": page if len(data) < 10 else page + 1, "limit": 24, "total": 99999, "list": data, "parse": 0, "jx": 0}
def playerContent(self, flag, id, vipFlags):
result = {"parse": 0, "playUrl": "", "url": id or "", "jx": 0, "header": {"User-Agent": self.HEADERS["User-Agent"], "Referer": self.BASE_URL + "/"}}
if not id:
return result
if ".m3u8" in id or ".mp4" in id:
return result
play_page = self._fix_url(id if str(id).startswith("http") else self.BASE_URL + "/videos/" + str(id).strip("/") + "/")
html = self._get(play_page)
m3u8 = self._m3u8(html)
if m3u8:
result["url"] = m3u8
result["header"] = {"User-Agent": self.HEADERS["User-Agent"], "Referer": play_page, "Origin": self.BASE_URL}
else:
result["url"] = play_page
result["parse"] = 1
return result
def _classes(self, html=None):
if self._class_cache:
return self._class_cache
self._class_cache = [
{"type_id": "latest-updates", "type_name": "最近更新"},
{"type_id": "hot", "type_name": "热门影片"},
{"type_id": "new-release", "type_name": "全新上市"},
{"type_id": "tags/chinese-subtitle", "type_name": "中文字幕"},
{"type_id": "tags/drama", "type_name": "剧情"},
{"type_id": "tags/cosplay", "type_name": "角色扮演"},
]
return self._class_cache
def _parse_list(self, html):
data, seen = [], set()
cards = re.findall(r'(<div[^>]+class=["\'][^"\']*video-img-box[^"\']*["\'][\s\S]*?</h6>[\s\S]*?</div>\s*</div>)', html or "", re.S | re.I)
if not cards:
cards = re.findall(r'(<a[^>]+href=["\'][^"\']*/videos/[^"\']+["\'][\s\S]*?</a>)', html or "", re.S | re.I)
for item in cards:
href = self._match(item, r'href=["\']([^"\']*/videos/[^"\']+)["\']')
if not href:
continue
name = self._clean(self._match(item, r'<h6[^>]*class=["\'][^"\']*title[^"\']*["\'][^>]*>\s*<a[^>]*>(.*?)</a>') or self._match(item, r'title=["\']([^"\']+)') or self._match(item, r'alt=["\']([^"\']+)'))
pic = self._match(item, r'(?:data-src|data-original|data-lazy-src|data-lazyload)=["\']([^"\']+)') or self._match(item, r'<img[^>]+src=["\']([^"\']+)')
remarks = self._clean(self._match(item, r'<span[^>]+class=["\'][^"\']*(?:duration|label|badge)[^"\']*["\'][^>]*>(.*?)</span>') or self._match(item, r'(\d{1,2}:\d{2}(?::\d{2})?)'))
full = self._fix_url(urllib.parse.urljoin(self.BASE_URL, href))
if full not in seen and name and not re.fullmatch(r'\d{1,2}:\d{2}(?::\d{2})?', name):
seen.add(full)
data.append({"vod_id": full, "vod_name": name, "vod_pic": urllib.parse.urljoin(self.BASE_URL, pic), "vod_remarks": remarks})
return data
def _get(self, url, headers=None):
for real in self._candidate_urls(self._fix_url(url)):
h = dict(self.HEADERS)
h["Referer"] = self.BASE_URL + "/"
if headers:
h.update(headers)
try:
r = self.session.get(real, headers=h, timeout=15, verify=False)
r.encoding = "utf-8"
if r.status_code < 400 and "Just a moment" not in r.text and "cf-browser-verification" not in r.text:
return r.text
except Exception:
continue
return ""
def _candidate_urls(self, url):
urls = [url]
for host in self.FALLBACK_URLS:
p = urllib.parse.urlparse(url)
if p.netloc and host not in url:
urls.append(host + p.path + ("?" + p.query if p.query else ""))
return list(dict.fromkeys(urls))
def _fix_url(self, url):
return str(url or "").replace("https://jable.tv", self.BASE_URL).replace("http://jable.tv", self.BASE_URL).replace("https://www.jable.tv", self.BASE_URL)
def _m3u8(self, html):
return self._match(html, r'var\s+hlsUrl\s*=\s*["\']([^"\']+\.m3u8[^"\']*)') or self._match(html, r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']')
def _match(self, text, pattern):
m = re.search(pattern, text or "", re.S | re.I)
return m.group(1).strip() if m else ""
def _clean(self, text):
text = re.sub(r'<.*?>', '', text or '')
text = text.replace('&nbsp;', ' ').replace('&amp;', '&').replace('&#038;', '&').replace('&quot;', '"')
return re.sub(r'\s+', ' ', text).strip()
def _to_int(self, value, default=0):
try:
return int(value)
except Exception:
return default
+162
View File
@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
import re
import urllib.parse
import requests
try:
from base.spider import Spider as BaseSpider
except ImportError:
class BaseSpider:
def __init__(self):
return None
class Spider(BaseSpider):
BASE_URL = "https://maomi66.cc"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Linux; Android 12; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36",
"Referer": "https://maomi66.cc/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
def __init__(self):
self.session = requests.Session()
self.session.headers.update(self.HEADERS)
self._class_cache = []
def getName(self):
return "猫咪AV"
def init(self, extend=""):
return None
def isVideoFormat(self, url):
return bool(re.search(r'\.(m3u8|mp4|flv|avi|mkv|mov)(\?|$)', url or '', re.I))
def manualVideoCheck(self):
return True
def homeContent(self, filter):
html = self._get(self.BASE_URL)
classes = self._classes(html)
return {"class": classes, "list": self._parse_list(html), "filters": {}, "parse": 0, "jx": 0}
def homeVideoContent(self):
return {"list": self._parse_list(self._get(self.BASE_URL))}
def categoryContent(self, tid, pg, filter, extend):
page = self._to_int(pg, 1)
html = self._get(self.BASE_URL + "/list/%s-%s.html" % (tid, page))
data = self._parse_list(html)
return {"list": data, "page": page, "pagecount": page + 1 if data else page, "limit": len(data) or 20, "total": (page + 1) * (len(data) or 20)}
def detailContent(self, ids):
vid = ids[0] if isinstance(ids, list) and ids else str(ids)
url = vid if str(vid).startswith("http") else self.BASE_URL + "/video/%s.html" % vid
html = self._get(url)
title = self._clean(self._match(html, r'<h1[^>]*>(.*?)</h1>') or self._match(html, r'<h2[^>]*>(.*?)</h2>') or self._match(html, r'<title[^>]*>(.*?)</title>'))
if not title:
title = "视频%s" % re.sub(r'\D+', '', str(vid))
pic = self._fix(self._match(html, r'<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)') or self._match(html, r'<video[^>]+poster=["\']([^"\']+)') or self._match(html, r'(?:data-original|data-src|src)=["\']([^"\']+\.(?:jpg|jpeg|png|webp|gif)[^"\']*)'))
play = self._extract_play(html)
tags = []
for x in re.findall(r'<a[^>]+href=["\']/list/\d+-1\.html["\'][^>]*>(.*?)</a>', html, re.S):
t = self._clean(x)
if t and t not in tags:
tags.append(t)
content = self._clean(self._match(html, r'<div[^>]+class=["\'][^"\']*(?:des|intro|content|info)[^"\']*["\'][^>]*>(.*?)</div>')) or title
vod = {
"vod_id": str(vid).split("/")[-1].replace(".html", ""),
"vod_name": title,
"vod_pic": pic,
"type_name": "/".join(tags[:3]),
"vod_year": "",
"vod_area": "",
"vod_remarks": "",
"vod_actor": "",
"vod_director": "",
"vod_content": content,
"vod_play_from": "默认",
"vod_play_url": "播放$%s" % (play or url)
}
return {"list": [vod]}
def searchContent(self, key, quick, pg="1"):
q = urllib.parse.quote(str(key or ""))
page = self._to_int(pg, 1)
html = self._get(self.BASE_URL + "/search.php?content=%s&type=1&page=%s" % (q, page))
data = self._parse_list(html)
if not data:
html = self._get(self.BASE_URL + "/search.php?content=%s&type=1" % q)
data = self._parse_list(html)
return {"list": data, "page": page, "pagecount": page + 1 if data else page, "limit": len(data) or 20, "total": (page + 1) * (len(data) or 20)}
def playerContent(self, flag, id, vipFlags):
url = urllib.parse.unquote(str(id or ""))
if "/video/" in url or re.fullmatch(r'\d+', url):
page = url if url.startswith("http") else self.BASE_URL + "/video/%s.html" % url
play = self._extract_play(self._get(page))
url = play or page
return {"parse": 0, "playUrl": "", "url": self._fix(url), "header": self.HEADERS}
def _classes(self, html):
arr = []
for tid, name in re.findall(r'href=["\']/list/(\d+)-1\.html["\'][^>]*>(.*?)</a>', html or "", re.S):
name = self._clean(name)
if tid and name and not any(x["type_id"] == tid for x in arr):
arr.append({"type_id": tid, "type_name": name})
if not arr:
arr = [
{"type_id": "69829818", "type_name": "国产精品"},
{"type_id": "71188148", "type_name": "国产自拍"},
{"type_id": "43659662", "type_name": "日本精品"},
{"type_id": "37440125", "type_name": "欧美极品"},
{"type_id": "19211697", "type_name": "中文字幕"},
{"type_id": "77777777", "type_name": "动漫精品"}
]
self._class_cache = arr
return arr
def _parse_list(self, html):
out = []
blocks = re.findall(r'<li[\s\S]*?</li>', html or "", re.I)
if not blocks:
blocks = re.findall(r'<a[^>]+href=["\']/video/\d+\.html["\'][\s\S]*?</a>', html or "", re.I)
for item in blocks:
vid = self._match(item, r'href=["\'][^"\']*/video/(\d+)\.html["\']')
if not vid:
continue
name = self._clean(self._match(item, r'<h5[^>]*>\s*<a[^>]*>(.*?)</a>') or self._match(item, r'title=["\']([^"\']+)') or self._match(item, r'alt=["\']([^"\']+)'))
pic = self._fix(self._match(item, r'data-original=["\']([^"\']+)') or self._match(item, r'data-src=["\']([^"\']+)') or self._match(item, r'<img[^>]+src=["\']([^"\']+)'))
remark = self._clean(self._match(item, r'<span[^>]*>(.*?)</span>') or self._match(item, r'<em[^>]*>(.*?)</em>'))
if not name:
name = "视频%s" % vid
vod = {"vod_id": vid, "vod_name": name, "vod_pic": pic, "vod_remarks": remark}
if not any(x["vod_id"] == vid for x in out):
out.append(vod)
return out
def _extract_play(self, html):
play = self._match(html, r'hls\.loadSource\(["\']([^"\']+)["\']\)') or self._match(html, r'video\.src\s*=\s*["\']([^"\']+)["\']') or self._match(html, r'<source[^>]+src=["\']([^"\']+)["\']') or self._match(html, r'["\'](https?://[^"\']+play\.php\?[^"\']+)["\']') or self._match(html, r'["\'](/play\.php\?[^"\']+)["\']')
return self._fix(play)
def _get(self, url):
if not url:
return ""
url = self._fix(url)
headers = dict(self.HEADERS)
headers["Referer"] = self.BASE_URL + "/"
try:
r = self.session.get(url, headers=headers, timeout=12, verify=False)
if not r.encoding or r.encoding.lower() == "iso-8859-1":
r.encoding = r.apparent_encoding or "utf-8"
return r.text
except requests.RequestException:
return ""
def _match(self, text, pattern, default=""):
m = re.search(pattern, text or "", re.S | re.I)
if not m:
return default
return m.group(1) if m.lastindex else m.group(0)
def _clean(self, text):
text = re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>', ' ', text or '', flags=re.I)
text = re.sub(r'<[^>]+>', ' ', text)
text = text.replace('&nbsp;', ' ').replace('&amp;amp;', '&').replace('&amp;', '&').replace('&#038;', '&').replace('&quot;', '"').replace('&#39;', "'").replace('&lt;', '<').replace('&gt;', '>')
return re.sub(r'\s+', ' ', text).strip()
def _fix(self, url):
url = (url or "").strip().replace("\\/", "/")
if not url:
return ""
if url.startswith("//"):
return "https:" + url
if url.startswith("/"):
return self.BASE_URL + url
return url
def _to_int(self, value, default=1):
try:
return int(value)
except Exception:
return default